acpc_table_manager 3.0.18 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,203 +0,0 @@
1
- require 'delegate'
2
- require 'timeout'
3
-
4
- require 'acpc_poker_types/match_state'
5
- require 'acpc_poker_types/game_definition'
6
- require 'acpc_poker_types/hand'
7
-
8
- require 'contextual_exceptions'
9
- using ContextualExceptions::ClassRefinement
10
-
11
- require_relative 'match'
12
-
13
- module AcpcTableManager
14
- class MatchView < SimpleDelegator
15
- include AcpcPokerTypes
16
-
17
- exceptions :unable_to_find_next_slice
18
-
19
- attr_reader :match, :slice_index, :messages_to_display
20
- attr_writer :messages_to_display
21
-
22
- def self.chip_contributions_in_previous_rounds(player, round)
23
- if round > 0
24
- player['chip_contributions'][0..round-1].inject(:+)
25
- else
26
- 0
27
- end
28
- end
29
-
30
- DEFAULT_WAIT_FOR_SLICE_TIMEOUT = 0 # seconds
31
-
32
- def initialize(match_id, slice_index = nil, load_previous_messages: false, timeout: DEFAULT_WAIT_FOR_SLICE_TIMEOUT)
33
- @match = Match.find(match_id)
34
- super @match
35
-
36
- @messages_to_display = []
37
-
38
- @slice_index = slice_index || @match.last_slice_viewed
39
-
40
- raise StandardError.new("Illegal slice index: #{@slice_index}") unless @slice_index >= 0
41
-
42
- unless @slice_index < @match.slices.length
43
- if timeout > 0
44
- Timeout.timeout(timeout, UnableToFindNextSlice) do
45
- while @slice_index >= @match.slices.length do
46
- sleep 0.5
47
- @match = Match.find(match_id)
48
- end
49
- end
50
- super @match
51
- else
52
- raise UnableToFindNextSlice
53
- end
54
- end
55
-
56
- @messages_to_display = slice.messages
57
-
58
- @loaded_previous_messages_ = false
59
-
60
- load_previous_messages! if load_previous_messages
61
- end
62
- def user_contributions_in_previous_rounds
63
- self.class.chip_contributions_in_previous_rounds(user, state.round)
64
- end
65
- def state() @state ||= MatchState.parse slice.state_string end
66
- def slice() slices[@slice_index] end
67
-
68
- # zero indexed
69
- def users_seat() @users_seat ||= @match.seat - 1 end
70
- def betting_sequence() slice.betting_sequence end
71
- def pot_at_start_of_round() slice.pot_at_start_of_round end
72
- def hand_ended?() slice.hand_ended? end
73
- def match_ended?() slice.match_ended? end
74
- def users_turn_to_act?() slice.users_turn_to_act? end
75
- def legal_actions
76
- slice.legal_actions.map do |action|
77
- AcpcPokerTypes::PokerAction.new(action)
78
- end
79
- end
80
-
81
- # @return [Array<Hash>] Player information ordered by seat.
82
- # Each player hash should contain
83
- # values for the following keys:
84
- # 'name',
85
- # 'seat'
86
- # 'chip_stack'
87
- # 'chip_contributions'
88
- # 'chip_balance'
89
- # 'hole_cards'
90
- def players
91
- return @players if @players
92
-
93
- @players = slice.players
94
- end
95
- def user
96
- @user ||= players[users_seat]
97
- end
98
- def opponents
99
- @opponents ||= compute_opponents
100
- end
101
- def opponents_sorted_by_position_from_user
102
- @opponents_sorted_by_position_from_user ||= opponents.sort_by do |opp|
103
- Seat.new(
104
- opp['seat'],
105
- players.length
106
- ).seats_from(
107
- users_seat
108
- )
109
- end
110
- end
111
- def amount_for_next_player_to_call
112
- @amount_for_next_player_to_call ||= slice.amount_to_call
113
- end
114
-
115
- # Over round
116
- def chip_contribution_for_next_player_after_calling
117
- @chip_contribution_for_next_player_after_calling ||= slice.chip_contribution_after_calling
118
- end
119
-
120
- # Over round
121
- def minimum_wager_to
122
- @minimum_wager_to ||= slice.minimum_wager_to
123
- end
124
-
125
- # Over round
126
- def pot_after_call
127
- @pot_after_call ||= slice.pot_after_call
128
- end
129
-
130
- # Over round
131
- def pot_fraction_wager_to(fraction=1)
132
- return 0 if hand_ended?
133
-
134
- [
135
- [
136
- (
137
- fraction * pot_after_call +
138
- chip_contribution_for_next_player_after_calling
139
- ),
140
- minimum_wager_to
141
- ].max,
142
- all_in
143
- ].min.floor
144
- end
145
-
146
- # Over round
147
- def all_in
148
- @all_in ||= slice.all_in
149
- end
150
-
151
- def betting_type_label
152
- if @betting_type_label.nil?
153
- @betting_type_label = if no_limit?
154
- 'nolimit'
155
- else
156
- 'limit'
157
- end
158
- end
159
-
160
- @betting_type_label
161
- end
162
-
163
- def load_previous_messages!(index = @slice_index)
164
- @messages_to_display = slices[0...index].inject([]) do |messages, s|
165
- messages += s.messages
166
- end + @messages_to_display
167
- @loaded_previous_messages_ = true
168
- self
169
- end
170
-
171
- def loaded_previous_messages?
172
- @loaded_previous_messages_
173
- end
174
-
175
- private
176
-
177
- def compute_opponents
178
- opp = players.dup
179
- opp.delete_at(users_seat)
180
- opp
181
- end
182
-
183
- def next_slice_without_updating_messages!(max_retries = 0)
184
- @slice_index += 1
185
- retries = 0
186
- if @slice_index >= slices.length && max_retries < 1
187
- @slice_index -= 1
188
- raise UnableToFindNextSlice.new("Unable to find next match slice after #{retries} retries")
189
- end
190
- while @slice_index >= slices.length do
191
- sleep(0.1)
192
- @match = Match.find(@match.id)
193
- __setobj__ @match
194
- if retries >= max_retries
195
- @slice_index -= 1
196
- raise UnableToFindNextSlice.new("Unable to find next match slice after #{retries} retries")
197
- end
198
- retries += 1
199
- end
200
- self
201
- end
202
- end
203
- end
@@ -1,62 +0,0 @@
1
- require 'timeout'
2
- require 'process_runner'
3
- require_relative 'config'
4
- require_relative 'simple_logging'
5
- require 'fileutils'
6
- require 'shellwords'
7
-
8
- module AcpcTableManager
9
- module Opponents
10
- extend SimpleLogging
11
-
12
- @logger = nil
13
-
14
- # @return [Array<Integer>] PIDs of the opponents started
15
- def self.start(match)
16
- @logger ||= ::AcpcTableManager.new_log 'opponents.log'
17
-
18
- opponents = match.bots(AcpcTableManager.config.dealer_host)
19
- log __method__, num_opponents: opponents.length
20
-
21
- if opponents.empty?
22
- raise StandardError.new("No opponents found to start for \"#{match.name}\" (#{match.id.to_s})!")
23
- end
24
-
25
- opponents_log_dir = File.join(AcpcTableManager.config.log_directory, 'opponents')
26
- FileUtils.mkdir(opponents_log_dir) unless File.directory?(opponents_log_dir)
27
-
28
- bot_start_commands = opponents.map do |name, info|
29
- {
30
- args: [info[:runner], info[:host], info[:port], Shellwords.escape(match.random_seed.to_s)],
31
- log: File.join(opponents_log_dir, "#{match.name}.#{match.id}.#{name}.log")
32
- }
33
- end
34
-
35
- bot_start_commands.map do |bot_start_command|
36
- log(
37
- __method__,
38
- {
39
- bot_start_command_parameters: bot_start_command[:args],
40
- command_to_be_run: bot_start_command[:args].join(' ')
41
- }
42
- )
43
- pid = Timeout::timeout(3) do
44
- ProcessRunner.go(
45
- bot_start_command[:args].map { |e| e.to_s },
46
- {
47
- [:err, :out] => [bot_start_command[:log], File::CREAT|File::WRONLY]
48
- }
49
- )
50
- end
51
- log(
52
- __method__,
53
- {
54
- bot_started?: true,
55
- pid: pid
56
- }
57
- )
58
- pid
59
- end
60
- end
61
- end
62
- end
@@ -1,346 +0,0 @@
1
- require_relative 'config'
2
- require_relative 'match'
3
- require_relative 'match_slice'
4
-
5
- require 'hescape'
6
- require 'acpc_poker_player_proxy'
7
- require 'acpc_poker_types'
8
-
9
- require_relative 'simple_logging'
10
- using AcpcTableManager::SimpleLogging::MessageFormatting
11
-
12
- require 'contextual_exceptions'
13
- using ContextualExceptions::ClassRefinement
14
-
15
- module AcpcTableManager
16
-
17
- class Proxy
18
- include SimpleLogging
19
- include AcpcPokerTypes
20
-
21
- exceptions :unable_to_create_match_slice
22
-
23
- def self.start(match, must_send_ready = false)
24
- game_definition = GameDefinition.parse_file(match.game_definition_file_name)
25
- match.game_def_hash = game_definition.to_h
26
- match.save!
27
-
28
- proxy = new(
29
- match.id,
30
- match.sanitized_name,
31
- AcpcDealer::ConnectionInformation.new(
32
- match.port_numbers[match.seat - 1],
33
- ::AcpcTableManager.config.dealer_host
34
- ),
35
- match.seat - 1,
36
- game_definition,
37
- match.player_names.join(' '),
38
- match.number_of_hands,
39
- must_send_ready
40
- ) do |players_at_the_table|
41
- yield players_at_the_table if block_given?
42
- end
43
- end
44
-
45
- # @todo Reduce the # of params
46
- #
47
- # @param [String] match_id The ID of the match in which this player is participating.
48
- # @param [String] match_name The name of the match in which this player is participating.
49
- # @param [DealerInformation] dealer_information Information about the dealer to which this bot should connect.
50
- # @param [GameDefinition, #to_s] game_definition A game definition; either a +GameDefinition+ or the name of the file containing a game definition.
51
- # @param [String] player_names The names of the players in this match.
52
- # @param [Integer] number_of_hands The number of hands in this match.
53
- def initialize(
54
- match_id,
55
- match_name,
56
- dealer_information,
57
- users_seat,
58
- game_definition,
59
- player_names='user p2',
60
- number_of_hands=1,
61
- must_send_ready=false
62
- )
63
- @logger = AcpcTableManager.new_log File.join('proxies', "#{match_name}.#{match_id}.#{users_seat}.log")
64
-
65
- log __method__, {
66
- dealer_information: dealer_information,
67
- users_seat: users_seat,
68
- game_definition: game_definition,
69
- player_names: player_names,
70
- number_of_hands: number_of_hands
71
- }
72
-
73
- @match_id = match_id
74
- @player_proxy = AcpcPokerPlayerProxy::PlayerProxy.new(
75
- dealer_information,
76
- game_definition,
77
- users_seat,
78
- must_send_ready
79
- ) do |players_at_the_table|
80
-
81
- if players_at_the_table.match_state
82
- update_database! players_at_the_table
83
-
84
- yield players_at_the_table if block_given?
85
- else
86
- log __method__, {before_first_match_state: true}
87
- end
88
- end
89
- end
90
-
91
- def next_hand!
92
- log __method__
93
-
94
- if @player_proxy.hand_ended?
95
- @player_proxy.next_hand! do |players_at_the_table|
96
- update_database! players_at_the_table
97
-
98
- yield players_at_the_table if block_given?
99
- end
100
- end
101
-
102
- log(
103
- __method__,
104
- {
105
- users_turn_to_act?: @player_proxy.users_turn_to_act?,
106
- match_ended?: @player_proxy.match_ended?
107
- }
108
- )
109
-
110
- self
111
- end
112
-
113
- # Player action interface
114
- # @see PlayerProxy#play!
115
- def play!(action, fast_forward = false)
116
- log __method__, users_turn_to_act?: @player_proxy.users_turn_to_act?, action: action
117
-
118
- if @player_proxy.users_turn_to_act?
119
- action = PokerAction.new(action) unless action.is_a?(PokerAction)
120
-
121
- @player_proxy.play! action do |players_at_the_table|
122
- update_database! players_at_the_table, fast_forward
123
-
124
- yield players_at_the_table if block_given?
125
- end
126
-
127
- log(
128
- __method__,
129
- {
130
- users_turn_to_act?: @player_proxy.users_turn_to_act?,
131
- match_ended?: @player_proxy.match_ended?
132
- }
133
- )
134
- end
135
-
136
- self
137
- end
138
-
139
- def users_turn_to_act?() @player_proxy.users_turn_to_act? end
140
-
141
- def play_check_fold!
142
- log __method__
143
- if @player_proxy.users_turn_to_act?
144
- play!(
145
- if (
146
- @player_proxy.legal_actions.any? do |a|
147
- a == AcpcPokerTypes::PokerAction::FOLD
148
- end
149
- )
150
- AcpcPokerTypes::PokerAction::FOLD
151
- else
152
- AcpcPokerTypes::PokerAction::CALL
153
- end,
154
- true
155
- )
156
- end
157
- self
158
- end
159
-
160
- # @see PlayerProxy#match_ended?
161
- def match_ended?
162
- return false if @player_proxy.nil?
163
-
164
- @match ||= begin
165
- Match.find(@match_id)
166
- rescue Mongoid::Errors::DocumentNotFound
167
- log(
168
- __method__,
169
- {
170
- msg: "Unable to find match",
171
- match_id: @match_id,
172
- match_ended: @player_proxy.match_ended?
173
- }
174
- )
175
- return true
176
- end
177
- @player_proxy.match_ended? @match.number_of_hands
178
- end
179
-
180
- private
181
-
182
- def update_database!(players_at_the_table, fast_forward = false)
183
- @match = Match.find(@match_id)
184
-
185
- begin
186
- if fast_forward
187
- @match.last_slice_viewed = @match.slices.length - 1
188
- end
189
-
190
- MatchSlice.from_players_at_the_table!(
191
- players_at_the_table,
192
- match_ended?,
193
- @match
194
- )
195
-
196
- new_slice = @match.slices.last
197
- new_slice.messages = []
198
-
199
- ms = players_at_the_table.match_state
200
-
201
- log(
202
- __method__,
203
- {
204
- first_state_of_first_round?: ms.first_state_of_first_round?
205
- }
206
- )
207
-
208
- if ms.first_state_of_first_round?
209
- new_slice.messages << hand_dealt_description(
210
- @match.player_names.map { |n| Hescape.escape_html(n) },
211
- ms.hand_number + 1,
212
- players_at_the_table.game_def,
213
- @match.number_of_hands
214
- )
215
- end
216
-
217
- last_action = ms.betting_sequence(
218
- players_at_the_table.game_def
219
- ).flatten.last
220
-
221
- log(
222
- __method__,
223
- {
224
- last_action: last_action
225
- }
226
- )
227
-
228
- if last_action
229
- last_actor = @match.player_names[
230
- @match.slices[-2].seat_next_to_act
231
- ]
232
-
233
- log(
234
- __method__,
235
- {
236
- last_actor: last_actor
237
- }
238
- )
239
-
240
- case last_action.to_acpc_character
241
- when PokerAction::CHECK
242
- new_slice.messages << check_description(
243
- last_actor
244
- )
245
- when PokerAction::CALL
246
- new_slice.messages << call_description(
247
- last_actor,
248
- last_action
249
- )
250
- when PokerAction::BET
251
- new_slice.messages << bet_description(
252
- last_actor,
253
- last_action
254
- )
255
- when PokerAction::RAISE
256
- new_slice.messages << if @match.no_limit?
257
- no_limit_raise_description(
258
- last_actor,
259
- last_action,
260
- @match.slices[-2].amount_to_call
261
- )
262
- else
263
- limit_raise_description(
264
- last_actor,
265
- last_action,
266
- ms.players(players_at_the_table.game_def).num_wagers(ms.round) - 1,
267
- players_at_the_table.game_def.max_number_of_wagers[ms.round]
268
- )
269
- end
270
- when PokerAction::FOLD
271
- new_slice.messages << fold_description(
272
- last_actor
273
- )
274
- end
275
- end
276
-
277
- log(
278
- __method__,
279
- {
280
- hand_ended?: players_at_the_table.hand_ended?
281
- }
282
- )
283
-
284
- if ms.first_state_of_round? && ms.round > 0
285
- s = if ms.community_cards[ms.round - 1].length > 1 then 'are' else 'is' end
286
- new_slice.messages << (
287
- "#{(ms.community_cards[ms.round - 1].map { |c| c.rank.to_s + c.suit.to_html }).join('')} #{s} revealed."
288
- )
289
- end
290
-
291
- if players_at_the_table.hand_ended?
292
- log(
293
- __method__,
294
- {
295
- reached_showdown?: ms.reached_showdown?
296
- }
297
- )
298
-
299
- if ms.reached_showdown?
300
- players_at_the_table.players.each_with_index do |player, i|
301
- hd = PileOfCards.new(
302
- player.hand +
303
- ms.community_cards.flatten
304
- ).to_poker_hand_description
305
- new_slice.messages << "#{Hescape.escape_html(@match.player_names[i])} shows #{hd}"
306
- end
307
- end
308
- winning_players = new_slice.players.select do |player|
309
- player['winnings'] > 0
310
- end
311
- if winning_players.length > 1
312
- new_slice.messages << split_pot_description(
313
- winning_players.map { |player| Hescape.escape_html(player['name']) },
314
- ms.pot(players_at_the_table.game_def)
315
- )
316
- else
317
- winnings = winning_players.first['winnings']
318
- if winnings.to_i == winnings
319
- winnings = winnings.to_i
320
- end
321
- chip_balance = winning_players.first['chip_balance']
322
- if chip_balance.to_i == chip_balance
323
- chip_balance = chip_balance.to_i
324
- end
325
-
326
- new_slice.messages << hand_win_description(
327
- Hescape.escape_html(winning_players.first['name']),
328
- winnings,
329
- chip_balance - winnings
330
- )
331
- end
332
- end
333
-
334
- new_slice.save!
335
-
336
- # Since creating a new slice doesn't "update" the match for some reason
337
- @match.update_attribute(:updated_at, Time.now)
338
- @match.save!
339
- rescue => e
340
- raise UnableToCreateMatchSlice.with_context('Unable to create match slice', e)
341
- end
342
-
343
- self
344
- end
345
- end
346
- end