acpc_dealer_data 0.0.1

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