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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +7 -4
  3. data/acpc_poker_types.gemspec +4 -2
  4. data/lib/acpc_poker_types.rb +13 -12
  5. data/lib/acpc_poker_types/acpc_dealer_data.rb +9 -0
  6. data/lib/acpc_poker_types/acpc_dealer_data/action_messages.rb +133 -0
  7. data/lib/acpc_poker_types/acpc_dealer_data/hand_data.rb +182 -0
  8. data/lib/acpc_poker_types/acpc_dealer_data/hand_results.rb +79 -0
  9. data/lib/acpc_poker_types/acpc_dealer_data/log_file.rb +5 -0
  10. data/lib/acpc_poker_types/acpc_dealer_data/match_definition.rb +54 -0
  11. data/lib/acpc_poker_types/acpc_dealer_data/poker_match_data.rb +393 -0
  12. data/lib/acpc_poker_types/board_cards.rb +4 -4
  13. data/lib/acpc_poker_types/card.rb +16 -16
  14. data/lib/acpc_poker_types/chip_stack.rb +1 -1
  15. data/lib/acpc_poker_types/game_definition.rb +34 -38
  16. data/lib/acpc_poker_types/hand.rb +5 -5
  17. data/lib/acpc_poker_types/match_state.rb +31 -32
  18. data/lib/acpc_poker_types/pile_of_cards.rb +1 -1
  19. data/lib/acpc_poker_types/player.rb +16 -15
  20. data/lib/acpc_poker_types/poker_action.rb +6 -6
  21. data/lib/acpc_poker_types/rank.rb +2 -2
  22. data/lib/acpc_poker_types/suit.rb +4 -4
  23. data/lib/acpc_poker_types/version.rb +1 -1
  24. data/spec/action_messages_spec.rb +450 -0
  25. data/spec/board_cards_spec.rb +9 -9
  26. data/spec/card_spec.rb +20 -20
  27. data/spec/chip_stack_spec.rb +28 -29
  28. data/spec/game_definition_spec.rb +11 -11
  29. data/spec/hand_data_spec.rb +295 -0
  30. data/spec/hand_results_spec.rb +292 -0
  31. data/spec/hand_spec.rb +11 -11
  32. data/spec/match_definition_spec.rb +95 -0
  33. data/spec/match_state_spec.rb +105 -287
  34. data/spec/pile_of_cards_spec.rb +14 -14
  35. data/spec/player_spec.rb +61 -61
  36. data/spec/poker_action_spec.rb +49 -49
  37. data/spec/poker_match_data_spec.rb +388 -0
  38. data/spec/rank_spec.rb +19 -19
  39. data/spec/suit_spec.rb +19 -19
  40. data/spec/support/spec_helper.rb +28 -6
  41. metadata +55 -10
@@ -0,0 +1,5 @@
1
+ # Wrapper class to enable mocking log files in tests
2
+ module AcpcPokerTypes::AcpcDealerData
3
+ class LogFile < File
4
+ end
5
+ end
@@ -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