acpc_poker_types 0.0.10 → 1.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.
- checksums.yaml +4 -4
- data/Rakefile +7 -4
- data/acpc_poker_types.gemspec +4 -2
- data/lib/acpc_poker_types.rb +13 -12
- data/lib/acpc_poker_types/acpc_dealer_data.rb +9 -0
- data/lib/acpc_poker_types/acpc_dealer_data/action_messages.rb +133 -0
- data/lib/acpc_poker_types/acpc_dealer_data/hand_data.rb +182 -0
- data/lib/acpc_poker_types/acpc_dealer_data/hand_results.rb +79 -0
- data/lib/acpc_poker_types/acpc_dealer_data/log_file.rb +5 -0
- data/lib/acpc_poker_types/acpc_dealer_data/match_definition.rb +54 -0
- data/lib/acpc_poker_types/acpc_dealer_data/poker_match_data.rb +393 -0
- data/lib/acpc_poker_types/board_cards.rb +4 -4
- data/lib/acpc_poker_types/card.rb +16 -16
- data/lib/acpc_poker_types/chip_stack.rb +1 -1
- data/lib/acpc_poker_types/game_definition.rb +34 -38
- data/lib/acpc_poker_types/hand.rb +5 -5
- data/lib/acpc_poker_types/match_state.rb +31 -32
- data/lib/acpc_poker_types/pile_of_cards.rb +1 -1
- data/lib/acpc_poker_types/player.rb +16 -15
- data/lib/acpc_poker_types/poker_action.rb +6 -6
- data/lib/acpc_poker_types/rank.rb +2 -2
- data/lib/acpc_poker_types/suit.rb +4 -4
- data/lib/acpc_poker_types/version.rb +1 -1
- data/spec/action_messages_spec.rb +450 -0
- data/spec/board_cards_spec.rb +9 -9
- data/spec/card_spec.rb +20 -20
- data/spec/chip_stack_spec.rb +28 -29
- data/spec/game_definition_spec.rb +11 -11
- data/spec/hand_data_spec.rb +295 -0
- data/spec/hand_results_spec.rb +292 -0
- data/spec/hand_spec.rb +11 -11
- data/spec/match_definition_spec.rb +95 -0
- data/spec/match_state_spec.rb +105 -287
- data/spec/pile_of_cards_spec.rb +14 -14
- data/spec/player_spec.rb +61 -61
- data/spec/poker_action_spec.rb +49 -49
- data/spec/poker_match_data_spec.rb +388 -0
- data/spec/rank_spec.rb +19 -19
- data/spec/suit_spec.rb +19 -19
- data/spec/support/spec_helper.rb +28 -6
- metadata +55 -10
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
require 'acpc_poker_types/game_definition'
|
5
|
+
|
6
|
+
require 'dmorrill10-utils/class'
|
7
|
+
|
8
|
+
module AcpcPokerTypes::AcpcDealerData
|
9
|
+
class MatchDefinition
|
10
|
+
|
11
|
+
exceptions :unable_to_parse, :incorrect_number_of_player_names
|
12
|
+
|
13
|
+
attr_reader :name, :game_def, :number_of_hands, :random_seed, :player_names
|
14
|
+
|
15
|
+
def self.parse(acpc_log_string, player_names, game_def_directory)
|
16
|
+
if acpc_log_string.strip.match(
|
17
|
+
'^\s*#\s*name/game/hands/seed\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s*$'
|
18
|
+
)
|
19
|
+
name = $1
|
20
|
+
game_def = AcpcPokerTypes::GameDefinition.parse_file(
|
21
|
+
File.join(game_def_directory, File.basename($2))
|
22
|
+
)
|
23
|
+
number_of_hands = $3
|
24
|
+
random_seed = $4
|
25
|
+
|
26
|
+
new(name, game_def, number_of_hands, random_seed, player_names)
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(name, game_def, number_of_hands, random_seed, player_names)
|
33
|
+
if game_def.number_of_players != player_names.length
|
34
|
+
raise IncorrectNumberOfPlayerNames, "number of players: #{game_def.number_of_players}, number of names: #{player_names.length}"
|
35
|
+
end
|
36
|
+
|
37
|
+
@name = name.to_s
|
38
|
+
@game_def = game_def
|
39
|
+
@number_of_hands = number_of_hands.to_i
|
40
|
+
@random_seed = random_seed.to_i
|
41
|
+
@player_names = player_names
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
(
|
46
|
+
@name == other.name &&
|
47
|
+
Set.new(@game_def.to_a) == Set.new(other.game_def.to_a) &&
|
48
|
+
@number_of_hands == other.number_of_hands &&
|
49
|
+
@random_seed == other.random_seed &&
|
50
|
+
@player_names == other.player_names
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
|
2
|
+
require 'acpc_poker_types/player'
|
3
|
+
|
4
|
+
require 'celluloid/autostart'
|
5
|
+
|
6
|
+
require 'dmorrill10-utils/class'
|
7
|
+
|
8
|
+
require 'acpc_poker_types/acpc_dealer_data/action_messages'
|
9
|
+
require 'acpc_poker_types/acpc_dealer_data/hand_data'
|
10
|
+
require 'acpc_poker_types/acpc_dealer_data/hand_results'
|
11
|
+
require 'acpc_poker_types/acpc_dealer_data/match_definition'
|
12
|
+
|
13
|
+
module AcpcPokerTypes::AcpcDealerData
|
14
|
+
class PokerMatchData
|
15
|
+
|
16
|
+
exceptions :match_definitions_do_not_match, :final_scores_do_not_match, :player_data_inconsistent
|
17
|
+
|
18
|
+
attr_accessor(
|
19
|
+
# @returns [Array<Numeric>] Chip distribution at the end of the match
|
20
|
+
:chip_distribution,
|
21
|
+
# @returns [MatchDefinition] Game definition and match parameters
|
22
|
+
:match_def,
|
23
|
+
# @returns [Integer] Zero-index turn number within the hand
|
24
|
+
:hand_number,
|
25
|
+
# @returns [ AcpcPokerTypes::AcpcDealerData::HandData] Data from each hand
|
26
|
+
:data,
|
27
|
+
# @returns [Array<AcpcPokerTypes::>] AcpcPokerTypes:: information
|
28
|
+
:players,
|
29
|
+
# @returns [Integer] Seat of the active player
|
30
|
+
:seat
|
31
|
+
)
|
32
|
+
|
33
|
+
# @returns [ AcpcPokerTypes::AcpcDealerData::PokerMatchData]
|
34
|
+
def self.parse_files(
|
35
|
+
action_messages_file,
|
36
|
+
result_messages_file,
|
37
|
+
player_names,
|
38
|
+
dealer_directory,
|
39
|
+
num_hands=nil
|
40
|
+
)
|
41
|
+
parsed_action_messages = Celluloid::Future.new do
|
42
|
+
AcpcPokerTypes::AcpcDealerData::ActionMessages.parse_file(
|
43
|
+
action_messages_file,
|
44
|
+
player_names,
|
45
|
+
dealer_directory,
|
46
|
+
num_hands
|
47
|
+
)
|
48
|
+
end
|
49
|
+
parsed_hand_results = Celluloid::Future.new do
|
50
|
+
AcpcPokerTypes::AcpcDealerData::HandResults.parse_file(
|
51
|
+
result_messages_file,
|
52
|
+
player_names,
|
53
|
+
dealer_directory,
|
54
|
+
num_hands
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
new(
|
59
|
+
parsed_action_messages.value,
|
60
|
+
parsed_hand_results.value,
|
61
|
+
player_names,
|
62
|
+
dealer_directory
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @returns [ AcpcPokerTypes::AcpcDealerData::PokerMatchData]
|
67
|
+
def self.parse(
|
68
|
+
action_messages,
|
69
|
+
result_messages,
|
70
|
+
player_names,
|
71
|
+
dealer_directory,
|
72
|
+
num_hands=nil
|
73
|
+
)
|
74
|
+
parsed_action_messages = Celluloid::Future.new do
|
75
|
+
AcpcPokerTypes::AcpcDealerData::ActionMessages.parse(
|
76
|
+
action_messages,
|
77
|
+
player_names,
|
78
|
+
dealer_directory,
|
79
|
+
num_hands
|
80
|
+
)
|
81
|
+
end
|
82
|
+
parsed_hand_results = Celluloid::Future.new do
|
83
|
+
AcpcPokerTypes::AcpcDealerData::HandResults.parse(
|
84
|
+
result_messages,
|
85
|
+
player_names,
|
86
|
+
dealer_directory,
|
87
|
+
num_hands
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
new(
|
92
|
+
parsed_action_messages.value,
|
93
|
+
parsed_hand_results.value,
|
94
|
+
player_names,
|
95
|
+
dealer_directory
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def initialize(
|
100
|
+
parsed_action_messages,
|
101
|
+
parsed_hand_results,
|
102
|
+
player_names,
|
103
|
+
dealer_directory
|
104
|
+
)
|
105
|
+
if (
|
106
|
+
parsed_action_messages.match_def.nil? ||
|
107
|
+
parsed_hand_results.match_def.nil? ||
|
108
|
+
parsed_action_messages.match_def != parsed_hand_results.match_def
|
109
|
+
)
|
110
|
+
raise MatchDefinitionsDoNotMatch
|
111
|
+
end
|
112
|
+
|
113
|
+
if (
|
114
|
+
parsed_action_messages.final_score != parsed_hand_results.final_score
|
115
|
+
)
|
116
|
+
raise FinalScoresDoNotMatch
|
117
|
+
end
|
118
|
+
|
119
|
+
@match_def = parsed_hand_results.match_def
|
120
|
+
|
121
|
+
if parsed_hand_results.final_score
|
122
|
+
set_chip_distribution! parsed_hand_results.final_score
|
123
|
+
end
|
124
|
+
|
125
|
+
set_data! parsed_action_messages, parsed_hand_results
|
126
|
+
|
127
|
+
# Zero-indexed seat
|
128
|
+
@seat = 0
|
129
|
+
|
130
|
+
initialize_players!
|
131
|
+
end
|
132
|
+
|
133
|
+
def for_every_seat!
|
134
|
+
match_def.game_def.number_of_players.times do |seat|
|
135
|
+
@seat = seat
|
136
|
+
|
137
|
+
initialize_players!
|
138
|
+
|
139
|
+
yield seat
|
140
|
+
end
|
141
|
+
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
def player(seat=@seat) @players[seat] end
|
146
|
+
|
147
|
+
def for_every_hand!
|
148
|
+
initialize_players!
|
149
|
+
|
150
|
+
@data.each_index do |i|
|
151
|
+
@hand_number = i
|
152
|
+
|
153
|
+
@players.each_with_index do |player, seat|
|
154
|
+
player.start_new_hand!(
|
155
|
+
@match_def.game_def.blinds[current_hand.data.first.state_messages[seat].position_relative_to_dealer],
|
156
|
+
@match_def.game_def.chip_stacks[current_hand.data.first.state_messages[seat].position_relative_to_dealer],
|
157
|
+
current_hand.data.first.state_messages[seat].users_hole_cards
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
yield @hand_number
|
162
|
+
end
|
163
|
+
|
164
|
+
if @chip_distribution && @chip_distribution != @players.map { |p| p.chip_balance }
|
165
|
+
raise AcpcPokerTypes::DataInconsistent, "chip distribution: #{@chip_distribution}, player balances: #{@players.map { |p| p.chip_balance }}"
|
166
|
+
end
|
167
|
+
|
168
|
+
@hand_number = nil
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
def for_every_turn!
|
173
|
+
current_hand.for_every_turn!(@seat) do |turn_number|
|
174
|
+
@players.each_with_index do |player, seat|
|
175
|
+
last_match_state = current_hand.last_match_state(seat)
|
176
|
+
match_state = current_hand.current_match_state(seat)
|
177
|
+
|
178
|
+
if current_hand.last_action && player.seat == current_hand.last_action.seat
|
179
|
+
|
180
|
+
player.take_action!(current_hand.last_action.action)
|
181
|
+
end
|
182
|
+
|
183
|
+
if (
|
184
|
+
player.active? &&
|
185
|
+
!match_state.first_state_of_first_round? &&
|
186
|
+
match_state.round > last_match_state.round
|
187
|
+
)
|
188
|
+
player.start_new_round!
|
189
|
+
end
|
190
|
+
|
191
|
+
if current_hand.final_turn?
|
192
|
+
player.take_winnings!(
|
193
|
+
current_hand.chip_distribution[seat] + @match_def.game_def.blinds[current_hand.current_match_state(seat).position_relative_to_dealer]
|
194
|
+
)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
yield turn_number
|
199
|
+
end
|
200
|
+
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
def match_has_another_round?(current_round, turn_index, turns_taken)
|
205
|
+
new_round?(current_round, turn_index) ||
|
206
|
+
players_all_in?(current_round, turn_index, turns_taken)
|
207
|
+
end
|
208
|
+
|
209
|
+
def hand_started?
|
210
|
+
@hand_number &&
|
211
|
+
current_hand.turn_number &&
|
212
|
+
current_hand.turn_number > 0
|
213
|
+
end
|
214
|
+
|
215
|
+
def player_acting_sequence
|
216
|
+
sequence = [[]]
|
217
|
+
|
218
|
+
return sequence unless hand_started?
|
219
|
+
|
220
|
+
turns_taken = current_hand.data[0..current_hand.turn_number-1]
|
221
|
+
turns_taken.each_with_index do |turn, turn_index|
|
222
|
+
next unless turn.action_message
|
223
|
+
|
224
|
+
sequence[turn.action_message.state.round] << turn.action_message.seat
|
225
|
+
if match_has_another_round?(sequence.length - 1, turn_index, turns_taken)
|
226
|
+
sequence << []
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
sequence
|
231
|
+
end
|
232
|
+
|
233
|
+
def current_hand
|
234
|
+
if @hand_number then @data[@hand_number] else nil end
|
235
|
+
end
|
236
|
+
|
237
|
+
def final_hand?
|
238
|
+
if @hand_number then @hand_number >= @data.length - 1 else nil end
|
239
|
+
end
|
240
|
+
|
241
|
+
# @return [Array<ChipStack>] AcpcPokerTypes:: stacks.
|
242
|
+
def chip_stacks
|
243
|
+
@players.map { |player| player.chip_stack }
|
244
|
+
end
|
245
|
+
|
246
|
+
# return [Array<Integer>] Each player's current chip balance.
|
247
|
+
def chip_balances
|
248
|
+
@players.map { |player| player.chip_balance }
|
249
|
+
end
|
250
|
+
|
251
|
+
# return [Array<Array<Integer>>] Each player's current chip contribution organized by round.
|
252
|
+
def chip_contributions
|
253
|
+
@players.map { |player| player.chip_contributions }
|
254
|
+
end
|
255
|
+
|
256
|
+
def opponents
|
257
|
+
@players.reject { |other_player| player == other_player }
|
258
|
+
end
|
259
|
+
def active_players
|
260
|
+
@players.select { |player_to_collect| player_to_collect.active? }
|
261
|
+
end
|
262
|
+
def non_folded_players
|
263
|
+
@players.reject { |player_to_reject| player_to_reject.folded? }
|
264
|
+
end
|
265
|
+
def opponents_cards_visible?
|
266
|
+
return false unless current_hand
|
267
|
+
|
268
|
+
current_hand.current_match_state.list_of_hole_card_hands.reject_empty_elements.length > 1
|
269
|
+
end
|
270
|
+
def player_with_dealer_button
|
271
|
+
return nil unless current_hand
|
272
|
+
|
273
|
+
@players.find do |plr|
|
274
|
+
current_hand.current_match_state(plr.seat).position_relative_to_dealer == @players.length - 1
|
275
|
+
end
|
276
|
+
end
|
277
|
+
# @return [Hash<AcpcPokerTypes::, #to_i] Relation from player to the blind that player paid.
|
278
|
+
def player_blind_relation
|
279
|
+
return nil unless current_hand
|
280
|
+
|
281
|
+
@players.inject({}) do |relation, plr|
|
282
|
+
relation[plr] = @match_def.game_def.blinds[current_hand.current_match_state(plr.seat).position_relative_to_dealer]
|
283
|
+
relation
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# @todo Untested
|
288
|
+
# @return [String] player acting sequence as a string.
|
289
|
+
def player_acting_sequence_string
|
290
|
+
(player_acting_sequence.map { |per_round| per_round.join('') }).join('/')
|
291
|
+
end
|
292
|
+
def users_turn_to_act?
|
293
|
+
return false unless current_hand && current_hand.next_action
|
294
|
+
current_hand.next_action.seat == @seat
|
295
|
+
end
|
296
|
+
def betting_sequence
|
297
|
+
sequence = [[]]
|
298
|
+
|
299
|
+
if (
|
300
|
+
@hand_number.nil? ||
|
301
|
+
current_hand.turn_number.nil? ||
|
302
|
+
current_hand.turn_number < 1
|
303
|
+
)
|
304
|
+
return sequence
|
305
|
+
end
|
306
|
+
|
307
|
+
turns_taken = current_hand.data[0..current_hand.turn_number-1]
|
308
|
+
turns_taken.each_with_index do |turn, turn_index|
|
309
|
+
next unless turn.action_message
|
310
|
+
|
311
|
+
sequence[turn.action_message.state.round] << turn.action_message.action
|
312
|
+
|
313
|
+
if (
|
314
|
+
new_round?(sequence.length - 1 , turn_index) ||
|
315
|
+
players_all_in?(sequence.length - 1, turn_index, turns_taken)
|
316
|
+
)
|
317
|
+
sequence << []
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
sequence
|
322
|
+
end
|
323
|
+
def betting_sequence_string
|
324
|
+
(betting_sequence.map do |per_round|
|
325
|
+
(per_round.map{|action| action.to_acpc}).join('')
|
326
|
+
end).join('/')
|
327
|
+
end
|
328
|
+
# @todo Test and implement this
|
329
|
+
# def min_wager
|
330
|
+
# return nil unless current_hand
|
331
|
+
|
332
|
+
# @match_def.game_def.min_wagers[current_hand.next_state.round]
|
333
|
+
# ChipStack.new [@min_wager.to_i, action_with_context.amount_to_put_in_pot.to_i].max
|
334
|
+
# end
|
335
|
+
|
336
|
+
protected
|
337
|
+
|
338
|
+
def initialize_players!
|
339
|
+
@players = @match_def.player_names.length.times.map do |seat|
|
340
|
+
AcpcPokerTypes::Player.join_match(
|
341
|
+
@match_def.player_names[seat],
|
342
|
+
seat,
|
343
|
+
@match_def.game_def.chip_stacks[seat]
|
344
|
+
)
|
345
|
+
end
|
346
|
+
|
347
|
+
self
|
348
|
+
end
|
349
|
+
|
350
|
+
def set_chip_distribution!(final_score)
|
351
|
+
@chip_distribution = []
|
352
|
+
final_score.each do |player_name, amount|
|
353
|
+
begin
|
354
|
+
@chip_distribution[@match_def.player_names.index(player_name.to_s)] = amount
|
355
|
+
rescue TypeError
|
356
|
+
raise AcpcPokerTypes::NamesDoNotMatch
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
self
|
361
|
+
end
|
362
|
+
|
363
|
+
def set_data!(parsed_action_messages, parsed_hand_results)
|
364
|
+
@data = []
|
365
|
+
parsed_action_messages.data.zip(parsed_hand_results.data).each do |action_messages_by_hand, hand_result|
|
366
|
+
@data << AcpcPokerTypes::AcpcDealerData::HandData.new(
|
367
|
+
@match_def,
|
368
|
+
action_messages_by_hand,
|
369
|
+
hand_result
|
370
|
+
)
|
371
|
+
end
|
372
|
+
|
373
|
+
self
|
374
|
+
end
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
def players_all_in?(current_round, turn_index, turns_taken)
|
379
|
+
current_hand.data.length == turn_index + 2 &&
|
380
|
+
current_round < (@match_def.game_def.number_of_rounds - 1) &&
|
381
|
+
(turns_taken[0..turn_index].count do |t|
|
382
|
+
# @todo Use FOLD constant instead once poker action has one
|
383
|
+
t.action_message.action.to_acpc_character == 'f'
|
384
|
+
end) != @players.length - 1
|
385
|
+
end
|
386
|
+
|
387
|
+
def new_round?(current_round, turn_index)
|
388
|
+
current_hand.data.length > turn_index + 1 &&
|
389
|
+
current_hand.data[turn_index + 1].action_message &&
|
390
|
+
current_hand.data[turn_index + 1].action_message.state.round > current_round
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|