mjai 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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