acpc_dealer_data 0.0.1

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.
@@ -0,0 +1,56 @@
1
+
2
+ require 'set'
3
+
4
+ require 'acpc_poker_types/game_definition'
5
+
6
+ require 'dmorrill10-utils/class'
7
+
8
+ class MatchDefinition
9
+
10
+ exceptions :unable_to_parse, :incorrect_number_of_player_names
11
+
12
+ attr_reader :name, :game_def, :number_of_hands, :random_seed, :player_names
13
+
14
+ def self.parse(acpc_log_string, player_names, game_def_directory)
15
+ if acpc_log_string.strip.match(
16
+ '^\s*#\s*name/game/hands/seed\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s*$'
17
+ )
18
+ name = $1
19
+ game_def = GameDefinition.parse_file(File.join(game_def_directory, File.basename($2)))
20
+ number_of_hands = $3
21
+ random_seed = $4
22
+
23
+ MatchDefinition.new(
24
+ name,
25
+ game_def,
26
+ number_of_hands,
27
+ random_seed,
28
+ player_names
29
+ )
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ def initialize(name, game_def, number_of_hands, random_seed, player_names)
36
+ if game_def.number_of_players != player_names.length
37
+ raise IncorrectNumberOfPlayerNames, "number of players: #{game_def.number_of_players}, number of names: #{player_names.length}"
38
+ end
39
+
40
+ @name = name.to_s
41
+ @game_def = game_def
42
+ @number_of_hands = number_of_hands.to_i
43
+ @random_seed = random_seed.to_i
44
+ @player_names = player_names
45
+ end
46
+
47
+ def ==(other)
48
+ (
49
+ @name == other.name &&
50
+ Set.new(@game_def.to_a) == Set.new(other.game_def.to_a) &&
51
+ @number_of_hands == other.number_of_hands &&
52
+ @random_seed == other.random_seed &&
53
+ @player_names == other.player_names
54
+ )
55
+ end
56
+ end
@@ -0,0 +1,178 @@
1
+
2
+ require 'acpc_poker_types/player'
3
+
4
+ require 'celluloid'
5
+
6
+ require 'dmorrill10-utils/class'
7
+
8
+ require_relative 'action_messages'
9
+ require_relative 'hand_data'
10
+ require_relative 'hand_results'
11
+ require_relative 'match_definition'
12
+
13
+ class PokerMatchData
14
+
15
+ exceptions :match_definitions_do_not_match, :final_scores_do_not_match, :player_data_inconsistent
16
+
17
+ attr_reader :chip_distribution, :match_def, :hand_number, :data, :players
18
+ attr_accessor :seat
19
+
20
+ def self.parse_files(action_messages_file, result_messages_file, player_names, dealer_directory)
21
+ parsed_action_messages = Celluloid::Future.new { ActionMessages.parse_file action_messages_file, player_names, dealer_directory }
22
+ parsed_hand_results = Celluloid::Future.new { HandResults.parse_file result_messages_file, player_names, dealer_directory }
23
+
24
+ PokerMatchData.new parsed_action_messages.value, parsed_hand_results.value, player_names, dealer_directory
25
+ end
26
+
27
+ def self.parse(action_messages, result_messages, player_names, dealer_directory)
28
+ parsed_action_messages = ActionMessages.parse action_messages, player_names, dealer_directory
29
+ parsed_hand_results = HandResults.parse result_messages, player_names, dealer_directory
30
+
31
+ PokerMatchData.new parsed_action_messages, parsed_hand_results, player_names, dealer_directory
32
+ end
33
+
34
+ def initialize(parsed_action_messages, parsed_hand_results, player_names, dealer_directory)
35
+ if (
36
+ parsed_action_messages.match_def.nil? ||
37
+ parsed_hand_results.match_def.nil? ||
38
+ parsed_action_messages.match_def != parsed_hand_results.match_def
39
+ )
40
+ raise MatchDefinitionsDoNotMatch
41
+ end
42
+
43
+ if (
44
+ parsed_action_messages.final_score.nil? ||
45
+ parsed_hand_results.final_score.nil? ||
46
+ parsed_action_messages.final_score != parsed_hand_results.final_score
47
+ )
48
+ raise FinalScoresDoNotMatch
49
+ end
50
+
51
+ @match_def = parsed_hand_results.match_def
52
+
53
+ set_chip_distribution! parsed_hand_results.final_score
54
+
55
+ set_data! parsed_action_messages, parsed_hand_results
56
+
57
+ @seat = 0
58
+ @players = @match_def.player_names.length.times.map do |seat|
59
+ Player.join_match(
60
+ @match_def.player_names[seat],
61
+ seat,
62
+ @match_def.game_def.chip_stacks[seat]
63
+ )
64
+ end
65
+ end
66
+
67
+ def for_every_seat!
68
+ match_def.game_def.number_of_players.times do |seat|
69
+ @seat = seat
70
+
71
+ @players = @match_def.player_names.length.times.map do |seat_j|
72
+ Player.join_match(
73
+ @match_def.player_names[seat_j],
74
+ seat_j,
75
+ @match_def.game_def.chip_stacks[seat_j]
76
+ )
77
+ end
78
+
79
+ yield seat
80
+ end
81
+
82
+ self
83
+ end
84
+
85
+ def player_name(seat=@seat) @players[seat].name end
86
+ def chip_balance(seat=@seat) @players[seat].chip_balance end
87
+ def hole_cards(seat=@seat) @players[seat].hole_cards end
88
+ def actions_taken_this_hand(seat=@seat) @players[seat].actions_taken_this_hand end
89
+ def folded?(seat=@seat) @players[seat].folded? end
90
+ def all_in?(seat=@seat) @players[seat].all_in? end
91
+ def active?(seat=@seat) @players[seat].active? end
92
+
93
+ def for_every_hand!
94
+ @data.each_index do |i|
95
+ @hand_number = i
96
+
97
+ @players.each_with_index do |player, seat|
98
+ player.start_new_hand!(
99
+ @match_def.game_def.blinds[@seat],
100
+ @match_def.game_def.chip_stacks[@seat],
101
+ current_hand.data.first.state_messages[seat].users_hole_cards
102
+ )
103
+ end
104
+
105
+ yield @hand_number
106
+ end
107
+
108
+ if @chip_distribution != @players.map { |p| p.chip_balance }
109
+ raise PlayerDataInconsistent, "chip distribution: #{@chip_distribution}, player balances: #{@players.map { |p| p.chip_balance }}"
110
+ end
111
+
112
+ @hand_number = nil
113
+ self
114
+ end
115
+
116
+ def for_every_turn!
117
+ current_hand.for_every_turn!(@seat) do |turn_number|
118
+ @players.each_with_index do |player, seat|
119
+ last_match_state = current_hand.last_match_state(seat)
120
+ match_state = current_hand.current_match_state(seat)
121
+
122
+ if current_hand.next_action && player.seat == current_hand.next_action[:seat]
123
+ player.take_action!(current_hand.next_action[:action])
124
+ end
125
+
126
+ if !match_state.first_state_of_first_round? && match_state.round > last_match_state.round
127
+ player.start_new_round!
128
+ end
129
+
130
+ if current_hand.final_turn?
131
+ player.take_winnings!(
132
+ current_hand.chip_distribution[seat] + @match_def.game_def.blinds[@seat]
133
+ )
134
+ end
135
+ end
136
+
137
+ yield turn_number
138
+ end
139
+
140
+ self
141
+ end
142
+
143
+ def current_hand
144
+ if @hand_number then @data[@hand_number] else nil end
145
+ end
146
+
147
+ def final_hand?
148
+ if @hand_number then @hand_number >= @data.length - 1 else nil end
149
+ end
150
+
151
+ protected
152
+
153
+ def set_chip_distribution!(final_score)
154
+ @chip_distribution = []
155
+ final_score.each do |player_name, amount|
156
+ begin
157
+ @chip_distribution[@match_def.player_names.index(player_name.to_s)] = amount
158
+ rescue TypeError
159
+ raise PlayerNamesDoNotMatch
160
+ end
161
+ end
162
+
163
+ self
164
+ end
165
+
166
+ def set_data!(parsed_action_messages, parsed_hand_results)
167
+ @data = []
168
+ parsed_action_messages.data.zip(parsed_hand_results.data).each do |action_messages_by_hand, hand_result|
169
+ @data << HandData.new(
170
+ @match_def,
171
+ action_messages_by_hand,
172
+ hand_result
173
+ )
174
+ end
175
+
176
+ self
177
+ end
178
+ end
@@ -0,0 +1,3 @@
1
+ module AcpcDealerData
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,308 @@
1
+
2
+ # Spec helper (must include first to track code coverage with SimpleCov)
3
+ require_relative 'support/spec_helper'
4
+
5
+ require 'mocha'
6
+
7
+ require 'acpc_dealer'
8
+ require 'acpc_poker_types/match_state'
9
+ require 'acpc_poker_types/poker_action'
10
+
11
+ require_relative '../lib/acpc_dealer_data/action_messages'
12
+ require_relative '../lib/acpc_dealer_data/match_definition'
13
+
14
+ describe ActionMessages do
15
+ before do
16
+ @data = nil
17
+ @final_score = nil
18
+ @patient = nil
19
+ @match_def = nil
20
+ @player_names = nil
21
+ end
22
+
23
+ describe '::parse_to_message' do
24
+ it 'properly parses a ACPC log "TO . . ." line' do
25
+ [
26
+ "TO 1 at 1341695999.222281 MATCHSTATE:0:0::5d5c|\n" =>
27
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:0::5d5c|')},
28
+ "TO 2 at 1341695920.914907 MATCHSTATE:1:0:r19686:|9hQd\n" =>
29
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:0:r19686:|9hQd')},
30
+ "TO 3 at 1341696044.566738 MATCHSTATE:2:0:rf:||8dAs\n" =>
31
+ {seat: 2, state: MatchState.parse('MATCHSTATE:2:0:rf:||8dAs')},
32
+ "TO 1 at 1341715418.808925 MATCHSTATE:0:0:fcr17162:5d5c||\n" =>
33
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:0:fcr17162:5d5c||')}
34
+ ].each do |to_message_to_data|
35
+ to_message_to_data.each do |to_message, expected_values|
36
+ ActionMessages.parse_to_message(to_message).must_equal expected_values
37
+ end
38
+ end
39
+ end
40
+ it 'returns nil if asked to parse an improperly formatted string' do
41
+ ActionMessages.parse_to_message("improperly formatted string").must_be_nil
42
+ end
43
+ end
44
+
45
+ describe '::parse_from_message' do
46
+ it 'properly parses a ACPC log "FROM . . ." line' do
47
+ [
48
+ "FROM 2 at 1341695999.222410 MATCHSTATE:1:0::|9hQd:c\n" =>
49
+ {
50
+ seat: 1,
51
+ state: MatchState.parse('MATCHSTATE:1:0::|9hQd'),
52
+ action: PokerAction.new('c')
53
+ },
54
+ "FROM 1 at 1341695920.914935 MATCHSTATE:0:0:r19686:5d5c|:r20000\n" =>
55
+ {
56
+ seat: 0,
57
+ state: MatchState.parse('MATCHSTATE:0:0:r19686:5d5c|'),
58
+ action: PokerAction.new('r20000')
59
+ },
60
+ "FROM 3 at 1341696044.566938 MATCHSTATE:2:0:rfr:||8dAs:r\n" =>
61
+ {
62
+ seat: 2,
63
+ state: MatchState.parse('MATCHSTATE:2:0:rfr:||8dAs'),
64
+ action: PokerAction.new('r')
65
+ },
66
+ "FROM 2 at 1341715418.808896 MATCHSTATE:1:0:fc:|9hQd|:r17162\n" =>
67
+ {
68
+ seat: 1,
69
+ state: MatchState.parse('MATCHSTATE:1:0:fc:|9hQd|'),
70
+ action: PokerAction.new('r17162')
71
+ }
72
+ ].each do |from_message_to_data|
73
+ from_message_to_data.each do |from_message, expected_values|
74
+ ActionMessages.parse_from_message(from_message).must_equal expected_values
75
+ end
76
+ end
77
+ end
78
+ it 'returns nil if asked to parse an improperly formatted string' do
79
+ ActionMessages.parse_from_message("improperly formatted string").must_be_nil
80
+ end
81
+ end
82
+
83
+ describe '::parse_score' do
84
+ it 'properly parses a ACPC log "SCORE. . ." line' do
85
+ [
86
+ "SCORE:100|-100:p1|p2\n" => {p1: 100, p2: -100},
87
+ 'SCORE:19835|621.5|-20455.5:p1|p2|p3' => {p1: 19835, p2: 621.5, p3: -20455.5}
88
+ ].each do |score_to_player_results|
89
+ score_to_player_results.each do |score_string, expected_values|
90
+ ActionMessages.parse_score(score_string).must_equal expected_values
91
+ end
92
+ end
93
+ end
94
+ it 'returns nil if asked to parse an improperly formatted string' do
95
+ ActionMessages.parse_score("improperly formatted string").must_be_nil
96
+ end
97
+ end
98
+
99
+ describe 'properly parses ACPC log statements' do
100
+ it 'from file' do
101
+ skip "Not sure how to test this easily when GameDefinition needs to open a file as well"
102
+
103
+ init_data do |action_messages|
104
+ file_name = 'file_name'
105
+ File.expects(:open).with(file_name, 'r').yields(
106
+ action_messages
107
+ ).returns(
108
+ ActionMessages.parse(
109
+ action_messages,
110
+ @player_names,
111
+ AcpcDealer::DEALER_DIRECTORY
112
+ )
113
+ )
114
+
115
+ @patient = ActionMessages.parse_file(
116
+ file_name,
117
+ @player_names,
118
+ AcpcDealer::DEALER_DIRECTORY
119
+ )
120
+
121
+ check_patient
122
+ end
123
+ end
124
+ it 'from array' do
125
+ init_data do |action_messages|
126
+ @patient = ActionMessages.parse(
127
+ action_messages,
128
+ @player_names,
129
+ AcpcDealer::DEALER_DIRECTORY
130
+ )
131
+
132
+ check_patient
133
+ end
134
+ end
135
+ end
136
+
137
+ def check_patient
138
+ @patient.data.must_equal @data
139
+ @patient.final_score.must_equal @final_score
140
+ @patient.match_def.must_equal @match_def
141
+ end
142
+
143
+ def init_data
144
+ all_data.each do |game, data_hash|
145
+ @final_score = data_hash[:final_score]
146
+ @data = data_hash[:data]
147
+ @player_names = data_hash[:player_names]
148
+ @match_def = MatchDefinition.parse(
149
+ data_hash[:action_messages].first,
150
+ @player_names,
151
+ AcpcDealer::DEALER_DIRECTORY
152
+ )
153
+
154
+ yield data_hash[:action_messages]
155
+ end
156
+ end
157
+
158
+ def all_data
159
+ {
160
+ two_player_limit: {
161
+ action_messages: [
162
+ "# name/game/hands/seed 2p.limit.h1000.r0 holdem.limit.2p.reverse_blinds.game 1000 0\n",
163
+ "TO 1 at 1341696000.058613 MATCHSTATE:1:998:crc/cc/cc/:|TdQd/As6d6h/7h/4s\n",
164
+ "TO 2 at 1341696000.058634 MATCHSTATE:0:998:crc/cc/cc/:Jc8d|/As6d6h/7h/4s\n",
165
+ "FROM 2 at 1341696000.058641 MATCHSTATE:0:998:crc/cc/cc/:Jc8d|/As6d6h/7h/4s:r\n",
166
+ "TO 1 at 1341696000.058664 MATCHSTATE:1:999:crc/cc/cc/r:|TdQd/As6d6h/7h/4s\n",
167
+ "TO 2 at 1341696000.058681 MATCHSTATE:0:999:crc/cc/cc/r:Jc8d|/As6d6h/7h/4s\n",
168
+ "FROM 1 at 1341696000.058688 MATCHSTATE:1:999:crc/cc/cc/r:|TdQd/As6d6h/7h/4s:c\n",
169
+ "TO 1 at 1341696000.058712 MATCHSTATE:1:999:crc/cc/cc/rc:Jc8d|TdQd/As6d6h/7h/4s\n",
170
+ "TO 2 at 1341696000.058732 MATCHSTATE:0:999:crc/cc/cc/rc:Jc8d|TdQd/As6d6h/7h/4s\n",
171
+ "FINISHED at 1341696000.058664\n",
172
+ 'SCORE:455|-455:p1|p2'
173
+ ],
174
+ data: [
175
+ [
176
+ {seat: 0, state: MatchState.parse('MATCHSTATE:1:998:crc/cc/cc/:|TdQd/As6d6h/7h/4s')},
177
+ {seat: 1, state: MatchState.parse('MATCHSTATE:0:998:crc/cc/cc/:Jc8d|/As6d6h/7h/4s')},
178
+ {
179
+ seat: 1,
180
+ state: MatchState.parse('MATCHSTATE:0:998:crc/cc/cc/:Jc8d|/As6d6h/7h/4s'),
181
+ action: PokerAction.new('r')
182
+ }
183
+ ],
184
+ [
185
+ {seat: 0, state: MatchState.parse('MATCHSTATE:1:999:crc/cc/cc/r:|TdQd/As6d6h/7h/4s')},
186
+ {seat: 1, state: MatchState.parse('MATCHSTATE:0:999:crc/cc/cc/r:Jc8d|/As6d6h/7h/4s')},
187
+ {
188
+ seat: 0,
189
+ state: MatchState.parse('MATCHSTATE:1:999:crc/cc/cc/r:|TdQd/As6d6h/7h/4s'),
190
+ action: PokerAction.new('c')
191
+ },
192
+ {seat: 0, state: MatchState.parse('MATCHSTATE:1:999:crc/cc/cc/rc:Jc8d|TdQd/As6d6h/7h/4s')},
193
+ {seat: 1, state: MatchState.parse('MATCHSTATE:0:999:crc/cc/cc/rc:Jc8d|TdQd/As6d6h/7h/4s')}
194
+ ]
195
+ ],
196
+ final_score: {p1: 455, p2: -455},
197
+ player_names: ['p1', 'p2']
198
+ },
199
+ two_player_nolimit: {
200
+ action_messages: [
201
+ "# name/game/hands/seed 2p.nolimit.h1000.r0 holdem.nolimit.2p.reverse_blinds.game 1000 0\n",
202
+ "TO 1 at 1341695921.617099 MATCHSTATE:0:998:cc/r5841r19996r20000:Kc6h|/QhAh8d\n",
203
+ "TO 2 at 1341695921.617126 MATCHSTATE:1:998:cc/r5841r19996r20000:|Qc3s/QhAh8d\n",
204
+ "FROM 2 at 1341695921.617133 MATCHSTATE:1:998:cc/r5841r19996r20000:|Qc3s/QhAh8d:c\n",
205
+ "TO 1 at 1341695921.617182 MATCHSTATE:0:998:cc/r5841r19996r20000c//:Kc6h|Qc3s/QhAh8d/Th/9d\n",
206
+ "TO 2 at 1341695921.617224 MATCHSTATE:1:998:cc/r5841r19996r20000c//:Kc6h|Qc3s/QhAh8d/Th/9d\n",
207
+ "TO 1 at 1341695921.617268 MATCHSTATE:1:999::|TdQd\n",
208
+ "TO 2 at 1341695921.617309 MATCHSTATE:0:999::Jc8d|\n",
209
+ "FROM 1 at 1341695921.617324 MATCHSTATE:1:999::|TdQd:f\n",
210
+ "TO 1 at 1341695921.617377 MATCHSTATE:1:999:f:|TdQd\n",
211
+ "TO 2 at 1341695921.617415 MATCHSTATE:0:999:f:Jc8d|\n",
212
+ "FINISHED at 1341695921.617268\n",
213
+ "SCORE:-64658|64658:p1|p2"
214
+ ],
215
+ data: [
216
+ [
217
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:998:cc/r5841r19996r20000:Kc6h|/QhAh8d')},
218
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:998:cc/r5841r19996r20000:|Qc3s/QhAh8d')},
219
+ {
220
+ seat: 1,
221
+ state: MatchState.parse('MATCHSTATE:1:998:cc/r5841r19996r20000:|Qc3s/QhAh8d:c'),
222
+ action: PokerAction.new('c')
223
+ },
224
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:998:cc/r5841r19996r20000c//:Kc6h|Qc3s/QhAh8d/Th/9d')},
225
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:998:cc/r5841r19996r20000c//:Kc6h|Qc3s/QhAh8d/Th/9d')}
226
+ ],
227
+ [
228
+ {seat: 0, state: MatchState.parse('MATCHSTATE:1:999::|TdQd')},
229
+ {seat: 1, state: MatchState.parse('MATCHSTATE:0:999::Jc8d|')},
230
+ {
231
+ seat: 0,
232
+ state: MatchState.parse('MATCHSTATE:1:999::|TdQd'),
233
+ action: PokerAction.new('f')
234
+ },
235
+ {seat: 0, state: MatchState.parse('MATCHSTATE:1:999:f:|TdQd')},
236
+ {seat: 1, state: MatchState.parse('MATCHSTATE:0:999:f:Jc8d|')}
237
+ ]
238
+ ],
239
+ final_score: {p1: -64658, p2: 64658},
240
+ player_names: ['p1', 'p2']
241
+ },
242
+ three_player_limit: {
243
+ action_messages: [
244
+ "# name/game/hands/seed 3p.limit.h1000.r0 holdem.limit.3p.game 1000 0\n",
245
+ "TO 1 at 1341696046.871086 MATCHSTATE:0:999:ccc/ccc/rrcc/rrrfr:QsAs||/4d6d2d/5d/2c\n",
246
+ "TO 2 at 1341696046.871128 MATCHSTATE:1:999:ccc/ccc/rrcc/rrrfr:|3s8h|/4d6d2d/5d/2c\n",
247
+ "TO 3 at 1341696046.871175 MATCHSTATE:2:999:ccc/ccc/rrcc/rrrfr:||Qd3c/4d6d2d/5d/2c\n",
248
+ "FROM 3 at 1341696046.871201 MATCHSTATE:2:999:ccc/ccc/rrcc/rrrfr:||Qd3c/4d6d2d/5d/2c:c\n",
249
+ "TO 1 at 1341696046.871245 MATCHSTATE:0:999:ccc/ccc/rrcc/rrrfrc:QsAs|3s8h|Qd3c/4d6d2d/5d/2c\n",
250
+ "TO 2 at 1341696046.871267 MATCHSTATE:1:999:ccc/ccc/rrcc/rrrfrc:|3s8h|Qd3c/4d6d2d/5d/2c\n",
251
+ "TO 3 at 1341696046.871313 MATCHSTATE:2:999:ccc/ccc/rrcc/rrrfrc:|3s8h|Qd3c/4d6d2d/5d/2c\n",
252
+ "FINISHED at 1341696046.871175\n",
253
+ "SCORE:-4330|625|3705:p1|p2|p3"
254
+ ],
255
+ data: [
256
+ [
257
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:999:ccc/ccc/rrcc/rrrfr:QsAs||/4d6d2d/5d/2c')},
258
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:999:ccc/ccc/rrcc/rrrfr:|3s8h|/4d6d2d/5d/2c')},
259
+ {seat: 2, state: MatchState.parse('MATCHSTATE:2:999:ccc/ccc/rrcc/rrrfr:||Qd3c/4d6d2d/5d/2c')},
260
+ {
261
+ seat: 2,
262
+ state: MatchState.parse('MATCHSTATE:2:999:ccc/ccc/rrcc/rrrfr:||Qd3c/4d6d2d/5d/2c'),
263
+ action: PokerAction.new('c')
264
+ },
265
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:999:ccc/ccc/rrcc/rrrfrc:QsAs|3s8h|Qd3c/4d6d2d/5d/2c')},
266
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:999:ccc/ccc/rrcc/rrrfrc:|3s8h|Qd3c/4d6d2d/5d/2c')},
267
+ {seat: 2, state: MatchState.parse('MATCHSTATE:2:999:ccc/ccc/rrcc/rrrfrc:|3s8h|Qd3c/4d6d2d/5d/2c')}
268
+ ]
269
+ ],
270
+ final_score: {p1: -4330, p2: 625, p3: 3705},
271
+ player_names: ['p1', 'p2', 'p3']
272
+ },
273
+ three_player_nolimit: {
274
+ action_messages: [
275
+ "# name/game/hands/seed 3p.nolimit.h1000.r0 holdem.nolimit.3p.game 1000 0\n",
276
+ "TO 1 at 1341715420.129997 MATCHSTATE:0:998:ccr12926r20000c:QsAs||\n",
277
+ "TO 2 at 1341715420.130034 MATCHSTATE:1:998:ccr12926r20000c:|3s8h|\n",
278
+ "TO 3 at 1341715420.130070 MATCHSTATE:2:998:ccr12926r20000c:||Qd3c\n",
279
+ "FROM 2 at 1341715420.130093 MATCHSTATE:1:998:ccr12926r20000c:|3s8h|:c\n",
280
+ "TO 1 at 1341715420.130156 MATCHSTATE:0:999:ccr12926r20000cc///:QsAs|3s8h|Qd3c/4d6d2d/5d/2c\n",
281
+ "TO 2 at 1341715420.130191 MATCHSTATE:1:999:ccr12926r20000cc///:QsAs|3s8h|Qd3c/4d6d2d/5d/2c\n",
282
+ "TO 3 at 1341715420.130225 MATCHSTATE:2:999:ccr12926r20000cc///:QsAs|3s8h|Qd3c/4d6d2d/5d/2c\n",
283
+ "FINISHED at 1341715420.130034\n",
284
+ "SCORE:684452|552584.5|-1237036.5:p1|p2|p3"
285
+ ],
286
+ data: [
287
+ [
288
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:998:ccr12926r20000c:QsAs||')},
289
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:998:ccr12926r20000c:|3s8h|')},
290
+ {seat: 2, state: MatchState.parse('MATCHSTATE:2:998:ccr12926r20000c:||Qd3c')},
291
+ {
292
+ seat: 1,
293
+ state: MatchState.parse('MATCHSTATE:1:998:ccr12926r20000c:|3s8h|'),
294
+ action: PokerAction.new('c')
295
+ }
296
+ ],
297
+ [
298
+ {seat: 0, state: MatchState.parse('MATCHSTATE:0:999:ccr12926r20000cc///:QsAs|3s8h|Qd3c/4d6d2d/5d/2c')},
299
+ {seat: 1, state: MatchState.parse('MATCHSTATE:1:999:ccr12926r20000cc///:QsAs|3s8h|Qd3c/4d6d2d/5d/2c')},
300
+ {seat: 2, state: MatchState.parse('MATCHSTATE:2:999:ccr12926r20000cc///:QsAs|3s8h|Qd3c/4d6d2d/5d/2c')}
301
+ ]
302
+ ],
303
+ final_score: {p1: 684452, p2: 552584.5, p3: -1237036.5},
304
+ player_names: ['p1', 'p2', 'p3']
305
+ }
306
+ }
307
+ end
308
+ end