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
- #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 |
# 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 else return action end end |
#distance(player1, player2) ⇒ Object
366 367 368 |
# File 'lib/mjai/game.rb', line 366 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
325 326 327 |
# File 'lib/mjai/game.rb', line 325 def doras return @dora_markers ? @dora_markers.map(){ |pai| pai.succ } : nil end |
#dump_action(action, io = $stdout) ⇒ Object
370 371 372 373 |
# File 'lib/mjai/game.rb', line 370 def dump_action(action, io = $stdout) io.puts(action.to_json()) io.print(render_board()) end |
#first_turn? ⇒ Boolean
358 359 360 |
# File 'lib/mjai/game.rb', line 358 def first_turn? return @first_turn end |
#get_hora(action, params = {}) ⇒ Object
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/mjai/game.rb', line 329 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
362 363 364 |
# File 'lib/mjai/game.rb', line 362 def ranked_players return @players.sort_by(){ |pl| [-pl.score, distance(pl, @chicha)] } end |
#render_board ⇒ Object
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/mjai/game.rb', line 375 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
313 314 315 |
# File 'lib/mjai/game.rb', line 313 def validate(criterion, ) raise(ValidationError, ) if !criterion end |
#validate_fields_exist(response, field_names) ⇒ Object
317 318 319 320 321 322 323 |
# File 'lib/mjai/game.rb', line 317 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
235 236 237 238 239 240 241 242 243 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 |
# File 'lib/mjai/game.rb', line 235 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.") end end |
#validate_response_type(response, player, action) ⇒ Object
181 182 183 184 185 186 187 188 189 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 |
# File 'lib/mjai/game.rb', line 181 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].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
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/mjai/game.rb', line 165 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 |