mjai 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mjai/active_game.rb +27 -2
- data/lib/mjai/game.rb +15 -1
- data/lib/mjai/game_stats.rb +181 -7
- data/lib/mjai/player.rb +11 -1
- data/lib/mjai/tcp_game_server.rb +1 -1
- data/lib/mjai/tenhou_archive.rb +2 -2
- metadata +2 -2
data/lib/mjai/active_game.rb
CHANGED
@@ -62,7 +62,7 @@ module Mjai
|
|
62
62
|
mota()
|
63
63
|
@actor = @players[(@actor.id + 1) % 4]
|
64
64
|
end
|
65
|
-
|
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
|
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
|
data/lib/mjai/game.rb
CHANGED
@@ -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
|
data/lib/mjai/game_stats.rb
CHANGED
@@ -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
|
-
|
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| [-
|
59
|
+
(0...4).sort_by(){ |i| [-scores[i], (i + 4 - chicha_id) % 4] }
|
24
60
|
for r in 0...4
|
25
|
-
name =
|
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
|
-
|
34
|
-
|
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
|
data/lib/mjai/player.rb
CHANGED
@@ -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
|
data/lib/mjai/tcp_game_server.rb
CHANGED
data/lib/mjai/tenhou_archive.rb
CHANGED
@@ -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.
|
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-
|
12
|
+
date: 2013-12-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|