mjai 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,7 +62,7 @@ module Mjai
62
62
  mota()
63
63
  @actor = @players[(@actor.id + 1) % 4]
64
64
  end
65
- process_ryukyoku()
65
+ process_fanpai()
66
66
  end
67
67
  do_action({:type => :end_kyoku})
68
68
  end
@@ -78,6 +78,10 @@ module Mjai
78
78
  # actions.size >= 2 in case of double/triple ron
79
79
  process_hora(actions)
80
80
  throw(:end_kyoku)
81
+ elsif actions[0].type == :ryukyoku
82
+ raise("should not happen") if actions.size != 1
83
+ process_kyushukyuhai(actions[0].actor)
84
+ throw(:end_kyoku)
81
85
  else
82
86
  raise("should not happen") if actions.size != 1
83
87
  action = actions[0]
@@ -178,7 +182,28 @@ module Mjai
178
182
  update_oya(actions.any?(){ |a| a.actor == self.oya }, false)
179
183
  end
180
184
 
181
- def process_ryukyoku()
185
+ def process_kyushukyuhai(actor)
186
+ tehais = []
187
+ for player in players
188
+ if player == actor
189
+ tehais.push(player.tehais)
190
+ else
191
+ tehais.push([Pai::UNKNOWN] * player.tehais.size)
192
+ end
193
+ end
194
+ do_action({
195
+ :type => :ryukyoku,
196
+ :actor => actor,
197
+ :reason => :kyushukyuhai,
198
+ :tenpais => [false, false, false, false],
199
+ :tehais => tehais,
200
+ :deltas => [0, 0, 0, 0],
201
+ :scores => players.map(){ |player| player.score }
202
+ })
203
+ update_oya(true, true)
204
+ end
205
+
206
+ def process_fanpai()
182
207
  tenpais = []
183
208
  tehais = []
184
209
  for player in players
@@ -157,6 +157,15 @@ module Mjai
157
157
  else
158
158
  return action
159
159
  end
160
+ when :reach
161
+ if action.actor == player
162
+ return action.merge({
163
+ :cannot_dahai =>
164
+ with_response_hint ? (player.tehais.uniq() - player.possible_dahais) : nil,
165
+ })
166
+ else
167
+ return action
168
+ end
160
169
  else
161
170
  return action
162
171
  end
@@ -191,7 +200,7 @@ module Mjai
191
200
  when :tsumo
192
201
  if is_actor
193
202
  valid = response &&
194
- [:dahai, :reach, :ankan, :kakan, :hora].include?(response.type)
203
+ [:dahai, :reach, :ankan, :kakan, :hora, :ryukyoku].include?(response.type)
195
204
  else
196
205
  valid = !response
197
206
  end
@@ -306,6 +315,11 @@ module Mjai
306
315
  end
307
316
  validate(response.actor.can_hora?, "Cannot hora.")
308
317
 
318
+ when :ryukyoku
319
+ validate_fields_exist(response, [:reason])
320
+ validate(response.reason == :kyushukyuhai, "reason must be kyushukyuhai.")
321
+ validate(response.actor.can_ryukyoku?, "Cannot ryukyoku.")
322
+
309
323
  end
310
324
 
311
325
  end
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  require "mjai/archive"
2
4
  require "mjai/confidence_interval"
3
5
 
@@ -6,32 +8,135 @@ module Mjai
6
8
 
7
9
  class GameStats
8
10
 
11
+ YAKU_JA_NAMES = {
12
+ :menzenchin_tsumoho => "面前清自摸和", :reach => "立直", :ippatsu => "一発",
13
+ :chankan => "槍槓", :rinshankaiho => "嶺上開花", :haiteiraoyue => "海底摸月",
14
+ :hoteiraoyui => "河底撈魚", :pinfu => "平和", :tanyaochu => "断么九",
15
+ :ipeko => "一盃口", :jikaze => "面風牌", :bakaze => "圏風牌",
16
+ :sangenpai => "三元牌", :double_reach => "ダブル立直", :chitoitsu => "七対子",
17
+ :honchantaiyao => "混全帯么九", :ikkitsukan => "一気通貫",
18
+ :sanshokudojun => "三色同順", :sanshokudoko => "三色同刻", :sankantsu => "三槓子",
19
+ :toitoiho => "対々和", :sananko => "三暗刻", :shosangen => "小三元",
20
+ :honroto => "混老頭", :ryanpeko => "二盃口", :junchantaiyao => "純全帯么九",
21
+ :honiso => "混一色", :chiniso => "清一色", :renho => "人和", :tenho => "天和",
22
+ :chiho => "地和", :daisangen => "大三元", :suanko => "四暗刻",
23
+ :tsuiso => "字一色", :ryuiso => "緑一色", :chinroto => "清老頭",
24
+ :churenpoton => "九蓮宝燈", :kokushimuso => "国士無双",
25
+ :daisushi => "大四喜", :shosushi => "小四喜", :sukantsu => "四槓子",
26
+ :dora => "ドラ", :uradora => "裏ドラ", :akadora => "赤ドラ",
27
+ }
28
+
9
29
  def self.print(mjson_paths)
30
+
10
31
  num_errors = 0
11
32
  name_to_ranks = {}
33
+ name_to_scores = {}
34
+ name_to_kyoku_count = {}
35
+ name_to_hora_count = {}
36
+ name_to_yaku_stats = {}
37
+ name_to_dora_stats = {}
38
+ name_to_hoju_count = {}
39
+ name_to_furo_kyoku_count = {}
40
+ name_to_reach_count = {}
41
+ name_to_hora_points = {}
42
+
12
43
  for path in mjson_paths
44
+
13
45
  archive = Archive.load(path)
14
46
  first_action = archive.raw_actions[0]
15
47
  last_action = archive.raw_actions[-1]
16
- archive.do_action(first_action)
17
- if last_action.type != :end_game
48
+ if !last_action || last_action.type != :end_game
18
49
  num_errors += 1
19
50
  next
20
51
  end
52
+ archive.do_action(first_action)
53
+
54
+ scores = last_action.scores
55
+ id_to_name = first_action.names
56
+
21
57
  chicha_id = archive.raw_actions[1].oya.id
22
58
  ranked_player_ids =
23
- (0...4).sort_by(){ |i| [-last_action.scores[i], (i + 4 - chicha_id) % 4] }
59
+ (0...4).sort_by(){ |i| [-scores[i], (i + 4 - chicha_id) % 4] }
24
60
  for r in 0...4
25
- name = first_action.names[ranked_player_ids[r]]
61
+ name = id_to_name[ranked_player_ids[r]]
26
62
  name_to_ranks[name] ||= []
27
63
  name_to_ranks[name].push(r + 1)
28
64
  end
65
+
66
+ for p in 0...4
67
+ name = id_to_name[p]
68
+ name_to_scores[name] ||= []
69
+ name_to_scores[name].push(scores[p])
70
+ end
71
+
72
+ # Kyoku specific fields.
73
+ id_to_done_reach = {}
74
+ id_to_done_furo = {}
75
+ for raw_action in archive.raw_actions
76
+ if raw_action.type == :hora
77
+ name = id_to_name[raw_action.actor.id]
78
+ name_to_hora_count[name] ||= 0
79
+ name_to_hora_count[name] += 1
80
+ name_to_hora_points[name] ||= []
81
+ name_to_hora_points[name].push(raw_action.hora_points)
82
+ for yaku, fan in raw_action.yakus
83
+ if yaku == :dora || yaku == :akadora || yaku == :uradora
84
+ name_to_dora_stats[name] ||= {}
85
+ name_to_dora_stats[name][yaku] ||= 0
86
+ name_to_dora_stats[name][yaku] += fan
87
+ next
88
+ end
89
+ name_to_yaku_stats[name] ||= {}
90
+ name_to_yaku_stats[name][yaku] ||= 0
91
+ name_to_yaku_stats[name][yaku] += 1
92
+ end
93
+ if raw_action.actor.id != raw_action.target.id
94
+ target_name = id_to_name[raw_action.target.id]
95
+ name_to_hoju_count[target_name] ||= 0
96
+ name_to_hoju_count[target_name] += 1
97
+ end
98
+ end
99
+ if raw_action.type == :reach_accepted
100
+ id_to_done_reach[raw_action.actor.id] = true
101
+ end
102
+ if raw_action.type == :pon
103
+ id_to_done_furo[raw_action.actor.id] = true
104
+ end
105
+ if raw_action.type == :chi
106
+ id_to_done_furo[raw_action.actor.id] = true
107
+ end
108
+ if raw_action.type == :daiminkan
109
+ id_to_done_furo[raw_action.actor.id] = true
110
+ end
111
+ if raw_action.type == :end_kyoku
112
+ for p in 0...4
113
+ name = id_to_name[p]
114
+
115
+ if id_to_done_furo[p]
116
+ name_to_furo_kyoku_count[name] ||= 0
117
+ name_to_furo_kyoku_count[name] += 1
118
+ end
119
+ if id_to_done_reach[p]
120
+ name_to_reach_count[name] ||= 0
121
+ name_to_reach_count[name] += 1
122
+ end
123
+
124
+ name_to_kyoku_count[name] ||= 0
125
+ name_to_kyoku_count[name] += 1
126
+ end
127
+
128
+ # Reset kyoku specific fields.
129
+ id_to_done_furo = {}
130
+ id_to_done_reach = {}
131
+ end
132
+ end
29
133
  end
30
134
  if num_errors > 0
31
135
  puts("errors: %d / %d" % [num_errors, mjson_paths.size])
32
136
  end
33
- puts("ranks:")
34
- for name, ranks in name_to_ranks
137
+
138
+ puts("Ranks:")
139
+ for name, ranks in name_to_ranks.sort
35
140
  rank_conf_interval = ConfidenceInterval.calculate(ranks, :min => 1.0, :max => 4.0)
36
141
  puts(" %s: %.3f [%.3f, %.3f]" % [
37
142
  name,
@@ -40,8 +145,77 @@ module Mjai
40
145
  rank_conf_interval[1],
41
146
  ])
42
147
  end
43
- end
44
148
 
149
+ puts("Scores:")
150
+ for name, scores in name_to_scores.sort
151
+ puts(" %s: %d" % [
152
+ name,
153
+ scores.inject(0, :+).to_i() / scores.size,
154
+ ])
155
+ end
156
+
157
+ puts("Hora rates:")
158
+ for name, hora_count in name_to_hora_count.sort
159
+ puts(" %s: %.1f%%" % [
160
+ name,
161
+ 100.0 * hora_count / name_to_kyoku_count[name]
162
+ ])
163
+ end
164
+
165
+ puts("Hoju rates:")
166
+ for name, hoju_count in name_to_hoju_count.sort
167
+ puts(" %s: %.1f%%" % [
168
+ name,
169
+ 100.0 * hoju_count / name_to_kyoku_count[name]
170
+ ])
171
+ end
172
+
173
+ puts("Furo rates:")
174
+ for name, furo_kyoku_count in name_to_furo_kyoku_count.sort
175
+ puts(" %s: %.1f%%" % [
176
+ name,
177
+ 100.0 * furo_kyoku_count / name_to_kyoku_count[name]
178
+ ])
179
+ end
180
+
181
+ puts("Reach rates:")
182
+ for name, reach_count in name_to_reach_count.sort
183
+ puts(" %s: %.1f%%" % [
184
+ name,
185
+ 100.0 * reach_count / name_to_kyoku_count[name]
186
+ ])
187
+ end
188
+
189
+ puts("Average hora points:")
190
+ for name, hora_points in name_to_hora_points.sort
191
+ puts(" %s: %d" % [
192
+ name,
193
+ hora_points.inject(0, :+).to_i() / hora_points.size,
194
+ ])
195
+ end
196
+
197
+ puts("Yaku stats:")
198
+ for name, yaku_stats in name_to_yaku_stats.sort
199
+ hora_count = name_to_hora_count[name]
200
+ puts(" %s (%d horas):" % [name, hora_count])
201
+ for yaku, count in yaku_stats.sort_by{|yaku, count| -count}
202
+ yaku_name = YAKU_JA_NAMES[yaku]
203
+ puts(" %s: %d (%.1f%%)" % [yaku_name, count, 100.0 * count / hora_count])
204
+ end
205
+ end
206
+
207
+ puts("Dora stats:")
208
+ for name, dora_stats in name_to_dora_stats.sort
209
+ hora_count = name_to_hora_count[name]
210
+ puts(" %s (%d horas):" % [name, hora_count])
211
+ for dora, count in dora_stats.sort_by{|dora, count| -count}
212
+ dora_name = YAKU_JA_NAMES[dora]
213
+ puts(" %s: %d (%.3f/hora)" % [dora_name, count, count.to_f() / hora_count])
214
+ end
215
+ end
216
+
217
+ end
218
+
45
219
  end
46
220
 
47
221
  end
@@ -192,7 +192,14 @@ module Mjai
192
192
  @game.get_hora(hora_action, {:previous_action => action}).valid? &&
193
193
  (hora_type == :tsumo || !self.furiten?)
194
194
  end
195
-
195
+
196
+ def can_ryukyoku?
197
+ return @game.current_action.type == :tsumo &&
198
+ @game.current_action.actor == self &&
199
+ @game.first_turn? &&
200
+ @tehais.select(){ |pai| pai.yaochu? }.uniq().size >= 9
201
+ end
202
+
196
203
  # Possible actions except for dahai.
197
204
  def possible_actions
198
205
  action = @game.current_action
@@ -209,6 +216,9 @@ module Mjai
209
216
  if can_reach?
210
217
  result.push(create_action({:type => :reach}))
211
218
  end
219
+ if can_ryukyoku?
220
+ result.push(create_action({:type => :ryukyoku, :reason => :kyushukyuhai}))
221
+ end
212
222
  end
213
223
  result += self.possible_furo_actions
214
224
  return result
@@ -40,7 +40,7 @@ module Mjai
40
40
  send(socket, {
41
41
  "type" => "hello",
42
42
  "protocol" => "mjsonp",
43
- "protocol_version" => 2,
43
+ "protocol_version" => 3,
44
44
  })
45
45
  line = socket.gets()
46
46
  if !line
@@ -170,7 +170,7 @@ module Mjai
170
170
  })
171
171
  if elem["owari"]
172
172
  do_action({:type => :end_kyoku})
173
- do_action({:type => :end_game})
173
+ do_action({:type => :end_game, :scores => points_params[:scores]})
174
174
  end
175
175
  return nil
176
176
  when "RYUUKYOKU"
@@ -209,7 +209,7 @@ module Mjai
209
209
  })
210
210
  if elem["owari"]
211
211
  do_action({:type => :end_kyoku})
212
- do_action({:type => :end_game})
212
+ do_action({:type => :end_game, :scores => points_params[:scores]})
213
213
  end
214
214
  return nil
215
215
  when "N"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mjai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-01 00:00:00.000000000 Z
12
+ date: 2013-12-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json