acpc_poker_types 0.0.10 → 1.0.0

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