Class: AcpcTableManager::Match
- Inherits:
-
Object
- Object
- AcpcTableManager::Match
- Includes:
- Mongoid::Document, Mongoid::Timestamps::Updated
- Defined in:
- lib/acpc_table_manager/match.rb
Constant Summary collapse
- UNIQUENESS_GUARANTEE_CHARACTER =
'_'
Class Method Summary collapse
- .create_with_defaults(user_name: 'Guest', game_definition_key: :two_player_limit, port_numbers: []) ⇒ Object
- .default_opponent_names(num_players) ⇒ Object
- .delete_finished_matches! ⇒ Object
- .delete_match!(match_id) ⇒ Object
-
.delete_matches_older_than!(lifespan) ⇒ Object
Deletion.
- .finished(matches = all) ⇒ Object
- .id_exists?(match_id, matches = all) ⇒ Boolean
- .include_game_definition ⇒ Object
-
.include_name ⇒ Object
Schema.
- .include_name_from_user ⇒ Object
- .include_number_of_hands ⇒ Object
- .include_opponent_names ⇒ Object
- .include_seat ⇒ Object
- .include_user_name ⇒ Object
- .kill_all_orphan_processes!(matches = all) ⇒ Object
- .kill_all_orphan_proxies!(matches = all) ⇒ Object
- .kill_process_if_running(pid) ⇒ Object
-
.new_name(user_name, game_def_key: nil, num_hands: nil, seed: nil, seat: nil, time: true) ⇒ Object
Generators.
- .new_random_seat(num_players) ⇒ Object
- .new_random_seed ⇒ Object
- .not_running(matches = all) ⇒ Object
- .ports_in_use(matches = all) ⇒ Object
- .quiet_find(match_id) ⇒ Object
-
.running(matches = all) ⇒ Object
Almost scopes.
- .safe_kill(pid) ⇒ Object
- .started_and_unfinished ⇒ Object
- .unfinished(matches = all) ⇒ Object
Instance Method Summary collapse
- #all_slices_up_to_hand_end_viewed? ⇒ Boolean
- #all_slices_viewed? ⇒ Boolean
- #bot_special_port_requirements ⇒ Object
- #bots(dealer_host) ⇒ Object
- #copy? ⇒ Boolean
- #copy_for_next_human_player(next_user_name, next_seat) ⇒ Object
- #dealer_running? ⇒ Boolean
- #defunkt? ⇒ Boolean
- #finish_starting! ⇒ Object
- #finished? ⇒ Boolean
- #game_def ⇒ Object
- #game_def_file_name_from_key ⇒ Object
- #game_def_hash_from_key ⇒ Object
-
#game_info ⇒ Object
Convenience accessors.
- #hand_number ⇒ Object
- #kill_dealer! ⇒ Object
- #kill_orphan_processes! ⇒ Object
- #kill_orphan_proxy! ⇒ Object
- #kill_proxy! ⇒ Object
- #no_limit? ⇒ Boolean
- #opponent_ports ⇒ Object
- #opponent_ports_with_condition ⇒ Object
- #opponent_ports_without_condition ⇒ Object
- #opponent_seats(opponent_name) ⇒ Object
- #opponent_seats_with_condition ⇒ Object
- #player_names ⇒ Object
- #proxy_running? ⇒ Boolean
-
#queue ⇒ Object
The matches to be started (have not been started and not currently running) ordered from newest to oldest.
- #rejoinable_seats(user_name) ⇒ Object
- #running? ⇒ Boolean
- #sanitized_name ⇒ Object
-
#set_dealer_options!(options) ⇒ Object
Initializers.
- #set_game_definition_file_name!(file_name = ) ⇒ Object
- #set_game_definition_hash!(hash = self.game_def_hash) ⇒ Object
- #set_name!(name_ = self.name_from_user) ⇒ Object
- #set_seat!(seat_ = self.seat) ⇒ Object
- #started? ⇒ Boolean
- #users_port ⇒ Object
Class Method Details
.create_with_defaults(user_name: 'Guest', game_definition_key: :two_player_limit, port_numbers: []) ⇒ Object
196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/acpc_table_manager/match.rb', line 196 def create_with_defaults( user_name: 'Guest', game_definition_key: :two_player_limit, port_numbers: [] ) new( name_from_user: new_name(user_name), user_name: user_name, port_numbers: port_numbers, game_definition_key: game_definition_key ).finish_starting! end |
.default_opponent_names(num_players) ⇒ Object
192 193 194 |
# File 'lib/acpc_table_manager/match.rb', line 192 def default_opponent_names(num_players) (num_players - 1).times.map { |i| "Tester" } end |
.delete_finished_matches! ⇒ Object
214 215 216 217 218 219 |
# File 'lib/acpc_table_manager/match.rb', line 214 def delete_finished_matches! finished.each do |m| m.delete if m.all_slices_viewed? end self end |
.delete_match!(match_id) ⇒ Object
220 221 222 223 224 225 226 227 228 |
# File 'lib/acpc_table_manager/match.rb', line 220 def delete_match!(match_id) begin match = find match_id rescue Mongoid::Errors::DocumentNotFound else match.delete end self end |
.delete_matches_older_than!(lifespan) ⇒ Object
Deletion
210 211 212 213 |
# File 'lib/acpc_table_manager/match.rb', line 210 def delete_matches_older_than!(lifespan) old(lifespan).delete_all self end |
.finished(matches = all) ⇒ Object
105 106 107 |
# File 'lib/acpc_table_manager/match.rb', line 105 def finished(matches=all) matches.select { |match| match.finished? } end |
.id_exists?(match_id, matches = all) ⇒ Boolean
86 87 88 |
# File 'lib/acpc_table_manager/match.rb', line 86 def id_exists?(match_id, matches=all) matches.where(id: match_id).exists? end |
.include_game_definition ⇒ Object
143 144 145 146 147 148 |
# File 'lib/acpc_table_manager/match.rb', line 143 def include_game_definition field :game_definition_key, type: Symbol validates_presence_of :game_definition_key field :game_definition_file_name field :game_def_hash, type: Hash end |
.include_name ⇒ Object
Schema
132 133 134 135 136 |
# File 'lib/acpc_table_manager/match.rb', line 132 def include_name field :name validates_presence_of :name validates_format_of :name, without: /\A\s*\z/ end |
.include_name_from_user ⇒ Object
137 138 139 140 141 142 |
# File 'lib/acpc_table_manager/match.rb', line 137 def include_name_from_user field :name_from_user validates_presence_of :name_from_user validates_format_of :name_from_user, without: /\A\s*\z/ validates_uniqueness_of :name_from_user end |
.include_number_of_hands ⇒ Object
149 150 151 152 153 |
# File 'lib/acpc_table_manager/match.rb', line 149 def include_number_of_hands field :number_of_hands, type: Integer validates_presence_of :number_of_hands validates_numericality_of :number_of_hands, greater_than: 0, only_integer: true end |
.include_opponent_names ⇒ Object
154 155 156 157 |
# File 'lib/acpc_table_manager/match.rb', line 154 def include_opponent_names field :opponent_names, type: Array validates_presence_of :opponent_names end |
.include_seat ⇒ Object
158 159 160 |
# File 'lib/acpc_table_manager/match.rb', line 158 def include_seat field :seat, type: Integer end |
.include_user_name ⇒ Object
161 162 163 164 165 |
# File 'lib/acpc_table_manager/match.rb', line 161 def include_user_name field :user_name validates_presence_of :user_name validates_format_of :user_name, without: /\A\s*\z/ end |
.kill_all_orphan_processes!(matches = all) ⇒ Object
123 124 125 |
# File 'lib/acpc_table_manager/match.rb', line 123 def kill_all_orphan_processes!(matches=all) matches.each { |m| m.kill_orphan_processes! } end |
.kill_all_orphan_proxies!(matches = all) ⇒ Object
127 128 129 |
# File 'lib/acpc_table_manager/match.rb', line 127 def kill_all_orphan_proxies!(matches=all) matches.each { |m| m.kill_orphan_proxy! } end |
.kill_process_if_running(pid) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/acpc_table_manager/match.rb', line 69 def kill_process_if_running(pid) if pid && pid > 0 begin safe_kill pid if AcpcDealer::process_exists?(pid) AcpcDealer::force_kill_process pid sleep 1 # Give the process a chance to exit if AcpcDealer::process_exists?(pid) yield if block_given? end end rescue Errno::ESRCH end end end |
.new_name(user_name, game_def_key: nil, num_hands: nil, seed: nil, seat: nil, time: true) ⇒ Object
Generators
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/acpc_table_manager/match.rb', line 168 def new_name( user_name, game_def_key: nil, num_hands: nil, seed: nil, seat: nil, time: true ) name = "match.#{user_name}" name += ".#{game_def_key}" if game_def_key name += ".#{num_hands}h" if num_hands name += ".#{seat}s" if seat name += ".#{seed}r" if seed name += ".#{Time.now_as_string}" if time name end |
.new_random_seat(num_players) ⇒ Object
189 190 191 |
# File 'lib/acpc_table_manager/match.rb', line 189 def new_random_seat(num_players) rand(num_players) + 1 end |
.new_random_seed ⇒ Object
184 185 186 187 188 |
# File 'lib/acpc_table_manager/match.rb', line 184 def new_random_seed # The ACPC dealer requires 32 bit random seeds # TODO The bound to rand is exclusive so this should be 2**33 rand(2**33 - 1) end |
.not_running(matches = all) ⇒ Object
102 103 104 |
# File 'lib/acpc_table_manager/match.rb', line 102 def not_running(matches=all) matches.select { |match| !match.running? } end |
.ports_in_use(matches = all) ⇒ Object
115 116 117 118 119 120 121 |
# File 'lib/acpc_table_manager/match.rb', line 115 def ports_in_use(matches=all) ports = [] matches.possibly_running.each do |m| ports += m.port_numbers if m.running? end ports end |
.quiet_find(match_id) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/acpc_table_manager/match.rb', line 90 def quiet_find(match_id) begin match = Match.find match_id rescue Mongoid::Errors::DocumentNotFound nil end end |
.running(matches = all) ⇒ Object
Almost scopes
99 100 101 |
# File 'lib/acpc_table_manager/match.rb', line 99 def running(matches=all) matches.possibly_running.select { |match| match.running? } end |
.safe_kill(pid) ⇒ Object
63 64 65 66 67 68 |
# File 'lib/acpc_table_manager/match.rb', line 63 def safe_kill(pid) if pid && pid > 0 AcpcDealer::kill_process pid sleep 1 # Give the process a chance to exit end end |
.started_and_unfinished ⇒ Object
111 112 113 |
# File 'lib/acpc_table_manager/match.rb', line 111 def started_and_unfinished started.to_a.select { |match| !match.finished? } end |
.unfinished(matches = all) ⇒ Object
108 109 110 |
# File 'lib/acpc_table_manager/match.rb', line 108 def unfinished(matches=all) matches.select { |match| !match.finished? } end |
Instance Method Details
#all_slices_up_to_hand_end_viewed? ⇒ Boolean
370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/acpc_table_manager/match.rb', line 370 def all_slices_up_to_hand_end_viewed? (self.slices.length - 1).downto(0).each do |slice_index| slice = self.slices[slice_index] if slice.hand_has_ended if self.last_slice_viewed >= slice_index return true else return false end end end return all_slices_viewed? end |
#all_slices_viewed? ⇒ Boolean
367 368 369 |
# File 'lib/acpc_table_manager/match.rb', line 367 def all_slices_viewed? self.last_slice_viewed >= (self.slices.length - 1) end |
#bot_special_port_requirements ⇒ Object
386 387 388 389 390 |
# File 'lib/acpc_table_manager/match.rb', line 386 def bot_special_port_requirements ::AcpcTableManager.exhibition_config.bots(game_definition_key, *opponent_names).values.map do |bot| bot['requires_special_port'] end end |
#bots(dealer_host) ⇒ Object
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/acpc_table_manager/match.rb', line 256 def bots(dealer_host) bot_info_from_config_that_match_opponents = ::AcpcTableManager.exhibition_config.bots(game_definition_key, *opponent_names) bot_opponent_ports = opponent_ports_with_condition do |name| bot_info_from_config_that_match_opponents.keys.include? name end raise unless ( port_numbers.length == player_names.length || bot_opponent_ports.length == bot_info_from_config_that_match_opponents.length ) bot_opponent_ports.zip( bot_info_from_config_that_match_opponents.keys, bot_info_from_config_that_match_opponents.values ).reduce({}) do |map, args| port_num, name, info = args map[name] = { runner: (if info['runner'] then info['runner'] else info end), host: dealer_host, port: port_num } map end end |
#copy? ⇒ Boolean
332 333 334 |
# File 'lib/acpc_table_manager/match.rb', line 332 def copy? self.name_from_user.match(/^#{UNIQUENESS_GUARANTEE_CHARACTER}+$/) end |
#copy_for_next_human_player(next_user_name, next_seat) ⇒ Object
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/acpc_table_manager/match.rb', line 315 def copy_for_next_human_player(next_user_name, next_seat) match = dup # This match was not given a name from the user, # so set this parameter to an arbitrary character match.name_from_user = UNIQUENESS_GUARANTEE_CHARACTER while !match.save do match.name_from_user << UNIQUENESS_GUARANTEE_CHARACTER end match.user_name = next_user_name # Swap seat match.seat = next_seat match.opponent_names.insert(seat - 1, user_name) match.opponent_names.delete_at(seat - 1) match.save!(validate: false) match end |
#dealer_running? ⇒ Boolean
361 362 363 |
# File 'lib/acpc_table_manager/match.rb', line 361 def dealer_running? self.dealer_pid && self.dealer_pid > 0 && AcpcDealer::process_exists?(self.dealer_pid) end |
#defunkt? ⇒ Boolean
442 443 444 |
# File 'lib/acpc_table_manager/match.rb', line 442 def defunkt?() (started? and !running? and !finished?) || self.unable_to_start_dealer end |
#finish_starting! ⇒ Object
305 306 307 308 309 310 311 312 |
# File 'lib/acpc_table_manager/match.rb', line 305 def finish_starting! set_name!.set_seat!.set_game_definition_file_name!.set_game_definition_hash! self.opponent_names ||= self.class().default_opponent_names(game_info['num_players']) self.number_of_hands ||= 1 self.ready_to_start = true save! self end |
#finished? ⇒ Boolean
359 |
# File 'lib/acpc_table_manager/match.rb', line 359 def finished?() started? && self.slices.last.match_ended? end |
#game_def ⇒ Object
345 346 347 |
# File 'lib/acpc_table_manager/match.rb', line 345 def game_def @game_def ||= AcpcPokerTypes::GameDefinition.new(game_def_hash_from_key) end |
#game_def_file_name_from_key ⇒ Object
341 |
# File 'lib/acpc_table_manager/match.rb', line 341 def game_def_file_name_from_key() game_info['file'] end |
#game_def_hash_from_key ⇒ Object
342 343 344 |
# File 'lib/acpc_table_manager/match.rb', line 342 def game_def_hash_from_key() @game_def_hash_from_key ||= AcpcPokerTypes::GameDefinition.parse_file(game_def_file_name_from_key).to_h end |
#game_info ⇒ Object
Convenience accessors
337 338 339 |
# File 'lib/acpc_table_manager/match.rb', line 337 def game_info @game_info ||= AcpcTableManager.exhibition_config.games[self.game_definition_key.to_s] end |
#hand_number ⇒ Object
348 349 350 351 352 353 354 |
# File 'lib/acpc_table_manager/match.rb', line 348 def hand_number return nil if slices.last.nil? state = AcpcPokerTypes::MatchState.parse( slices.last.state_string ) if state then state.hand_number else nil end end |
#kill_dealer! ⇒ Object
429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/acpc_table_manager/match.rb', line 429 def kill_dealer! self.class().kill_process_if_running(self.dealer_pid) do raise( StandardError.new( "Dealer process #{self.dealer_pid} couldn't be killed!" ) ) end self.dealer_pid = nil save self end |
#kill_orphan_processes! ⇒ Object
463 464 465 466 467 468 469 |
# File 'lib/acpc_table_manager/match.rb', line 463 def kill_orphan_processes! if dealer_running? && !proxy_running? kill_dealer! elsif proxy_running && !dealer_running? kill_proxy! end end |
#kill_orphan_proxy! ⇒ Object
459 460 461 |
# File 'lib/acpc_table_manager/match.rb', line 459 def kill_orphan_proxy! kill_proxy! if proxy_running? && !dealer_running? end |
#kill_proxy! ⇒ Object
446 447 448 449 450 451 452 453 454 455 456 457 |
# File 'lib/acpc_table_manager/match.rb', line 446 def kill_proxy! self.class().kill_process_if_running(self.proxy_pid) do raise( StandardError.new( "Proxy process #{self.proxy_pid} couldn't be killed!" ) ) end self.proxy_pid = nil save self end |
#no_limit? ⇒ Boolean
355 356 357 |
# File 'lib/acpc_table_manager/match.rb', line 355 def no_limit? @is_no_limit ||= game_def.betting_type == AcpcPokerTypes::GameDefinition::BETTING_TYPES[:nolimit] end |
#opponent_ports ⇒ Object
394 395 396 397 398 |
# File 'lib/acpc_table_manager/match.rb', line 394 def opponent_ports port_numbers_ = port_numbers.dup users_port_ = port_numbers_.delete_at(seat - 1) port_numbers_ end |
#opponent_ports_with_condition ⇒ Object
407 408 409 410 411 |
# File 'lib/acpc_table_manager/match.rb', line 407 def opponent_ports_with_condition opponent_seats_with_condition { |player_name| yield player_name }.map do |opp_seat| port_numbers[opp_seat - 1] end end |
#opponent_ports_without_condition ⇒ Object
412 413 414 415 416 417 418 |
# File 'lib/acpc_table_manager/match.rb', line 412 def opponent_ports_without_condition local_opponent_ports = opponent_ports opponent_ports_with_condition { |player_name| yield player_name }.each do |port| local_opponent_ports.delete port end local_opponent_ports end |
#opponent_seats(opponent_name) ⇒ Object
404 405 406 |
# File 'lib/acpc_table_manager/match.rb', line 404 def opponent_seats(opponent_name) opponent_seats_with_condition { |player_name| player_name == opponent_name } end |
#opponent_seats_with_condition ⇒ Object
399 400 401 402 403 |
# File 'lib/acpc_table_manager/match.rb', line 399 def opponent_seats_with_condition player_names.each_index.select do |i| yield player_names[i] end.map { |s| s + 1 } - [self.seat] end |
#player_names ⇒ Object
383 384 385 |
# File 'lib/acpc_table_manager/match.rb', line 383 def player_names opponent_names.dup.insert seat-1, self.user_name end |
#proxy_running? ⇒ Boolean
364 365 366 |
# File 'lib/acpc_table_manager/match.rb', line 364 def proxy_running? self.proxy_pid && self.proxy_pid > 0 && AcpcDealer::process_exists?(self.proxy_pid) end |
#queue ⇒ Object
Returns The matches to be started (have not been started and not currently running) ordered from newest to oldest.
55 |
# File 'lib/acpc_table_manager/match.rb', line 55 scope :queue, not_started.and.ready_to_start.desc(:updated_at) |
#rejoinable_seats(user_name) ⇒ Object
419 420 421 422 423 424 425 |
# File 'lib/acpc_table_manager/match.rb', line 419 def rejoinable_seats(user_name) ( opponent_seats(user_name) - # Remove seats already taken by players who have already joined this match self.class().where(name: self.name).ne(name_from_user: self.name).map { |m| m.seat } ) end |
#running? ⇒ Boolean
360 |
# File 'lib/acpc_table_manager/match.rb', line 360 def running?() dealer_running? && proxy_running? end |
#sanitized_name ⇒ Object
426 427 428 |
# File 'lib/acpc_table_manager/match.rb', line 426 def sanitized_name Zaru.sanitize!(Shellwords.escape(self.name.gsub(/\s+/, '_'))) end |
#set_dealer_options!(options) ⇒ Object
Initializers
281 282 283 284 |
# File 'lib/acpc_table_manager/match.rb', line 281 def () self. = (.split(' ').map { |o| Shellwords.escape o }.join(' ') || '') self end |
#set_game_definition_file_name!(file_name = ) ⇒ Object
298 299 300 301 |
# File 'lib/acpc_table_manager/match.rb', line 298 def set_game_definition_file_name!(file_name = game_info['file']) self.game_definition_file_name = file_name self end |
#set_game_definition_hash!(hash = self.game_def_hash) ⇒ Object
302 303 304 |
# File 'lib/acpc_table_manager/match.rb', line 302 def set_game_definition_hash!(hash = self.game_def_hash) self.game_def_hash = hash || game_def_hash_from_key end |
#set_name!(name_ = self.name_from_user) ⇒ Object
285 286 287 288 289 290 |
# File 'lib/acpc_table_manager/match.rb', line 285 def set_name!(name_ = self.name_from_user) name_from_user_ = name_.strip self.name = name_from_user_ self.name_from_user = name_from_user_ self end |
#set_seat!(seat_ = self.seat) ⇒ Object
291 292 293 294 295 296 297 |
# File 'lib/acpc_table_manager/match.rb', line 291 def set_seat!(seat_ = self.seat) self.seat = seat_ || self.class().new_random_seat(game_info['num_players']) if self.seat > game_info['num_players'] self.seat = game_info['num_players'] end self end |
#started? ⇒ Boolean
358 |
# File 'lib/acpc_table_manager/match.rb', line 358 def started?() !self.slices.empty? end |
#users_port ⇒ Object
391 392 393 |
# File 'lib/acpc_table_manager/match.rb', line 391 def users_port port_numbers[seat - 1] end |