Class: Mjai::Game
- Inherits:
-
Object
- Object
- Mjai::Game
- Defined in:
- lib/mjai/game.rb
Direct Known Subclasses
Instance Attribute Summary collapse
-
#all_pais ⇒ Object
readonly
Returns the value of attribute all_pais.
-
#bakaze ⇒ Object
readonly
Returns the value of attribute bakaze.
-
#current_action ⇒ Object
readonly
Returns the value of attribute current_action.
-
#dora_markers ⇒ Object
readonly
ドラ表示牌.
-
#honba ⇒ Object
readonly
Returns the value of attribute honba.
-
#last ⇒ Object
kari.
-
#num_pipais ⇒ Object
readonly
Returns the value of attribute num_pipais.
-
#oya ⇒ Object
readonly
Returns the value of attribute oya.
-
#players ⇒ Object
Returns the value of attribute players.
-
#previous_action ⇒ Object
readonly
Returns the value of attribute previous_action.
Instance Method Summary collapse
- #action_in_view(action, player_id, for_response) ⇒ Object
- #can_kan? ⇒ Boolean
- #distance(player1, player2) ⇒ Object
-
#do_action(action) ⇒ Object
Executes the action and returns responses for it from players.
- #doras ⇒ Object
- #dump_action(action, io = $stdout) ⇒ Object
- #first_turn? ⇒ Boolean
- #get_hora(action, params = {}) ⇒ Object
-
#initialize(players = nil) ⇒ Game
constructor
A new instance of Game.
- #on_action(&block) ⇒ Object
- #on_responses(&block) ⇒ Object
- #ranked_players ⇒ Object
- #render_board ⇒ Object
-
#update_state(action) ⇒ Object
Updates internal state of Game and Player objects by the action.
- #validate(criterion, message) ⇒ Object
- #validate_fields_exist(response, field_names) ⇒ Object
- #validate_response_content(response, action) ⇒ Object
- #validate_response_type(response, player, action) ⇒ Object
- #validate_responses(responses, action) ⇒ Object
Constructor Details
#initialize(players = nil) ⇒ Game
Returns a new instance of Game.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/mjai/game.rb', line 12 def initialize(players = nil) self.players = players if players @bakaze = nil @kyoku_num = nil @honba = nil @chicha = nil @oya = nil @dora_markers = nil @current_action = nil @previous_action = nil @num_pipais = nil @num_initial_pipais = nil @first_turn = false end |
Instance Attribute Details
#all_pais ⇒ Object (readonly)
Returns the value of attribute all_pais.
28 29 30 |
# File 'lib/mjai/game.rb', line 28 def all_pais @all_pais end |
#bakaze ⇒ Object (readonly)
Returns the value of attribute bakaze.
29 30 31 |
# File 'lib/mjai/game.rb', line 29 def bakaze @bakaze end |
#current_action ⇒ Object (readonly)
Returns the value of attribute current_action.
33 34 35 |
# File 'lib/mjai/game.rb', line 33 def current_action @current_action end |
#dora_markers ⇒ Object (readonly)
ドラ表示牌
32 33 34 |
# File 'lib/mjai/game.rb', line 32 def dora_markers @dora_markers end |
#honba ⇒ Object (readonly)
Returns the value of attribute honba.
31 32 33 |
# File 'lib/mjai/game.rb', line 31 def honba @honba end |
#last ⇒ Object
kari
37 38 39 |
# File 'lib/mjai/game.rb', line 37 def last @last end |
#num_pipais ⇒ Object (readonly)
Returns the value of attribute num_pipais.
36 37 38 |
# File 'lib/mjai/game.rb', line 36 def num_pipais @num_pipais end |
#oya ⇒ Object (readonly)
Returns the value of attribute oya.
30 31 32 |
# File 'lib/mjai/game.rb', line 30 def oya @oya end |
#players ⇒ Object
Returns the value of attribute players.
27 28 29 |
# File 'lib/mjai/game.rb', line 27 def players @players end |
#previous_action ⇒ Object (readonly)
Returns the value of attribute previous_action.
34 35 36 |
# File 'lib/mjai/game.rb', line 34 def previous_action @previous_action end |
Instance Method Details
#action_in_view(action, player_id, for_response) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/mjai/game.rb', line 119 def action_in_view(action, player_id, for_response) player = @players[player_id] with_response_hint = for_response && expect_response_from?(player) case action.type when :start_game return action.merge({:id => player_id}) when :start_kyoku tehais_list = action.tehais.dup() for i in 0...4 if i != player_id tehais_list[i] = [Pai::UNKNOWN] * tehais_list[i].size end end return action.merge({:tehais => tehais_list}) when :tsumo if action.actor == player return action.merge({ :possible_actions => with_response_hint ? player.possible_actions : nil, }) else return action.merge({:pai => Pai::UNKNOWN}) end when :dahai, :kakan if action.actor != player return action.merge({ :possible_actions => with_response_hint ? player.possible_actions : nil, }) else return action end when :chi, :pon if action.actor == player return action.merge({ :cannot_dahai => with_response_hint ? player.kuikae_dahais : nil, }) else return action end when :reach if action.actor == player return action.merge({ :cannot_dahai => with_response_hint ? (player.tehais.uniq() - player.possible_dahais) : nil, }) else return action end else return action end end |
#can_kan? ⇒ Boolean
376 377 378 |
# File 'lib/mjai/game.rb', line 376 def can_kan? return @dora_markers.size < 5 end |
#distance(player1, player2) ⇒ Object
384 385 386 |
# File 'lib/mjai/game.rb', line 384 def distance(player1, player2) return (4 + player1.id - player2.id) % 4 end |
#do_action(action) ⇒ Object
Executes the action and returns responses for it from players.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/mjai/game.rb', line 55 def do_action(action) if action.is_a?(Hash) action = Action.new(action) end update_state(action) @on_action.call(action) if @on_action responses = (0...4).map() do |i| @players[i].respond_to_action(action_in_view(action, i, true)) end action_with_logs = action.merge({:logs => responses.map(){ |r| r && r.log }}) responses = responses.map(){ |r| (!r || r.type == :none) ? nil : r.merge({:log => nil}) } @on_responses.call(action_with_logs, responses) if @on_responses @previous_action = action validate_responses(responses, action) return responses end |
#doras ⇒ Object
339 340 341 |
# File 'lib/mjai/game.rb', line 339 def doras return @dora_markers ? @dora_markers.map(){ |pai| pai.succ } : nil end |
#dump_action(action, io = $stdout) ⇒ Object
388 389 390 391 |
# File 'lib/mjai/game.rb', line 388 def dump_action(action, io = $stdout) io.puts(action.to_json()) io.print(render_board()) end |
#first_turn? ⇒ Boolean
372 373 374 |
# File 'lib/mjai/game.rb', line 372 def first_turn? return @first_turn end |
#get_hora(action, params = {}) ⇒ Object
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/mjai/game.rb', line 343 def get_hora(action, params = {}) raise("should not happen") if action.type != :hora hora_type = action.actor == action.target ? :tsumo : :ron if hora_type == :tsumo tehais = action.actor.tehais[0...-1] else tehais = action.actor.tehais end uradoras = (params[:uradora_markers] || []).map(){ |pai| pai.succ } return Hora.new({ :tehais => tehais, :furos => action.actor.furos, :taken => action.pai, :hora_type => hora_type, :oya => action.actor == self.oya, :bakaze => self.bakaze, :jikaze => action.actor.jikaze, :doras => self.doras, :uradoras => uradoras, :reach => action.actor.reach?, :double_reach => action.actor.double_reach?, :ippatsu => action.actor.ippatsu_chance?, :rinshan => action.actor.rinshan?, :haitei => self.num_pipais == 0, :first_turn => @first_turn, :chankan => params[:previous_action].type == :kakan, }) end |
#on_action(&block) ⇒ Object
46 47 48 |
# File 'lib/mjai/game.rb', line 46 def on_action(&block) @on_action = block end |
#on_responses(&block) ⇒ Object
50 51 52 |
# File 'lib/mjai/game.rb', line 50 def on_responses(&block) @on_responses = block end |
#ranked_players ⇒ Object
380 381 382 |
# File 'lib/mjai/game.rb', line 380 def ranked_players return @players.sort_by(){ |pl| [-pl.score, distance(pl, @chicha)] } end |
#render_board ⇒ Object
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/mjai/game.rb', line 393 def render_board() result = "" if @bakaze && @kyoku_num && @honba result << ("%s-%d kyoku %d honba " % [@bakaze, @kyoku_num, @honba]) end result << ("pipai: %d " % self.num_pipais) if self.num_pipais result << ("dora_marker: %s " % @dora_markers.join(" ")) if @dora_markers result << "\n" @players.each_with_index() do |player, i| if player.tehais result << ("%s%s%d%s tehai: %s %s\n" % [player == @actor ? "*" : " ", player == @oya ? "{" : "[", i, player == @oya ? "}" : "]", Pai.dump_pais(player.tehais), player.furos.join(" ")]) if player.reach_ho_index ho_str = Pai.dump_pais(player.ho[0...player.reach_ho_index]) + "=" + Pai.dump_pais(player.ho[player.reach_ho_index..-1]) else ho_str = Pai.dump_pais(player.ho) end result << (" ho: %s\n" % ho_str) end end result << ("-" * 80) << "\n" return result end |
#update_state(action) ⇒ Object
Updates internal state of Game and Player objects by the action.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/mjai/game.rb', line 80 def update_state(action) @current_action = action @actor = action.actor if action.actor case action.type when :start_game # TODO change this by red config pais = (0...4).map() do |i| ["m", "p", "s"].map(){ |t| (1..9).map(){ |n| Pai.new(t, n, n == 5 && i == 0) } } + (1..7).map(){ |n| Pai.new("t", n) } end @all_pais = pais.flatten().sort() when :start_kyoku @bakaze = action.bakaze @kyoku_num = action.kyoku @honba = action.honba @oya = action.oya @chicha ||= @oya @dora_markers = [action.dora_marker] @num_pipais = @num_initial_pipais = @all_pais.size - 13 * 4 - 14 @first_turn = true when :tsumo @num_pipais -= 1 if @num_initial_pipais - @num_pipais > 4 @first_turn = false end when :chi, :pon, :daiminkan, :kakan, :ankan @first_turn = false when :dora @dora_markers.push(action.dora_marker) end for i in 0...4 @players[i].update_state(action_in_view(action, i, false)) end end |
#validate(criterion, message) ⇒ Object
327 328 329 |
# File 'lib/mjai/game.rb', line 327 def validate(criterion, ) raise(ValidationError, ) if !criterion end |
#validate_fields_exist(response, field_names) ⇒ Object
331 332 333 334 335 336 337 |
# File 'lib/mjai/game.rb', line 331 def validate_fields_exist(response, field_names) for name in field_names if !response.fields.has_key?(name) raise(ValidationError, "%s missing." % name) end end end |
#validate_response_content(response, action) ⇒ Object
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/mjai/game.rb', line 244 def validate_response_content(response, action) case response.type when :dahai validate_fields_exist(response, [:pai, :tsumogiri]) if action.actor.reach? # possible_dahais check doesn't subsume this check. Consider karagiri # (with tsumogiri=false) after reach. validate(response.tsumogiri, "tsumogiri must be true after reach.") end validate( response.actor.possible_dahais.include?(response.pai), "Cannot dahai this pai. The pai is not in the tehais, " + "it's kuikae, or it causes noten reach.") # Validates that pai and tsumogiri fields are consistent. if [:tsumo, :reach].include?(action.type) if response.tsumogiri tsumo_pai = response.actor.tehais[-1] validate( response.pai == tsumo_pai, "tsumogiri is true but the pai is not tsumo pai: %s != %s" % [response.pai, tsumo_pai]) else validate( response.actor.tehais[0...-1].include?(response.pai), "tsumogiri is false but the pai is not in tehais.") end else # after furo validate( !response.tsumogiri, "tsumogiri must be false on dahai after furo.") end when :chi, :pon, :daiminkan, :ankan, :kakan if response.type == :ankan validate_fields_exist(response, [:consumed]) elsif response.type == :kakan validate_fields_exist(response, [:pai, :consumed]) else validate_fields_exist(response, [:target, :pai, :consumed]) validate( response.target == action.actor, "target must be %d." % action.actor.id) end valid = response.actor.possible_furo_actions.any?() do |a| a.type == response.type && a.pai == response.pai && a.consumed.sort() == response.consumed.sort() end validate(valid, "The furo is not allowed.") when :reach validate(response.actor.can_reach?, "Cannot reach.") when :hora validate_fields_exist(response, [:target, :pai]) validate( response.target == action.actor, "target must be %d." % action.actor.id) if response.target == response.actor tsumo_pai = response.actor.tehais[-1] validate( response.pai == tsumo_pai, "pai is not tsumo pai: %s != %s" % [response.pai, tsumo_pai]) else validate( response.pai == action.pai, "pai is not previous dahai: %s != %s" % [response.pai, action.pai]) end validate(response.actor.can_hora?, "Cannot hora.") when :ryukyoku validate_fields_exist(response, [:reason]) validate(response.reason == :kyushukyuhai, "reason must be kyushukyuhai.") validate(response.actor.can_ryukyoku?, "Cannot ryukyoku.") end end |
#validate_response_type(response, player, action) ⇒ Object
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/mjai/game.rb', line 190 def validate_response_type(response, player, action) if response && response.type == :error raise(ValidationError, response.) end is_actor = player == action.actor if expect_response_from?(player) case action.type when :start_game, :start_kyoku, :end_kyoku, :end_game, :error, :hora, :ryukyoku, :dora, :reach_accepted valid = !response when :tsumo if is_actor valid = response && [:dahai, :reach, :ankan, :kakan, :hora, :ryukyoku].include?(response.type) else valid = !response end when :dahai if is_actor valid = !response else valid = !response || [:chi, :pon, :daiminkan, :hora].include?(response.type) end when :chi, :pon, :reach if is_actor valid = response && response.type == :dahai else valid = !response end when :ankan, :daiminkan # Actor should wait for tsumo. valid = !response when :kakan if is_actor # Actor should wait for tsumo. valid = !response else # hora is for chankan. valid = !response || response.type == :hora end when :log valid = !response else raise(ValidationError, "Unknown action type: '#{action.type}'") end else valid = !response end if !valid raise(ValidationError, "Unexpected response type '%s' for %s." % [response ? response.type : :none, action]) end end |
#validate_responses(responses, action) ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/mjai/game.rb', line 174 def validate_responses(responses, action) for i in 0...4 response = responses[i] begin if response && response.actor != @players[i] raise(ValidationError, "Invalid actor.") end validate_response_type(response, @players[i], action) validate_response_content(response, action) if response rescue ValidationError => ex raise(ValidationError, "Error in player %d's response: %s Response: %s" % [i, ex., response]) end end end |