acpc_table_manager 3.0.18 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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