pokerstats 2.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.
Files changed (37) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +50 -0
  6. data/VERSION +1 -0
  7. data/bin/checkps +190 -0
  8. data/generators/pokerstats/USAGE +1 -0
  9. data/generators/pokerstats/pokerstats_generator.rb +7 -0
  10. data/generators/pokerstats/templates/create_pokerstats.rhtml +28 -0
  11. data/lib/pokerstats.rb +2 -0
  12. data/lib/pokerstats/.gitignore +0 -0
  13. data/lib/pokerstats/hand_constants.rb +27 -0
  14. data/lib/pokerstats/hand_history.rb +38 -0
  15. data/lib/pokerstats/hand_statistics.rb +225 -0
  16. data/lib/pokerstats/hand_statistics_api.rb +64 -0
  17. data/lib/pokerstats/player_statistics.rb +58 -0
  18. data/lib/pokerstats/plugins/aggression_statistics.rb +57 -0
  19. data/lib/pokerstats/plugins/blind_attack_statistics.rb +78 -0
  20. data/lib/pokerstats/plugins/cash_statistics.rb +95 -0
  21. data/lib/pokerstats/plugins/continuation_bet_statistics.rb +68 -0
  22. data/lib/pokerstats/plugins/preflop_raise_statistics.rb +50 -0
  23. data/lib/pokerstats/poker-edge.rb +57 -0
  24. data/lib/pokerstats/pokerstars_file.rb +100 -0
  25. data/lib/pokerstats/pokerstars_hand_history_parser.rb +141 -0
  26. data/pokerstats.gemspec +91 -0
  27. data/spec/file_empty.txt +0 -0
  28. data/spec/file_many_hands.txt +564 -0
  29. data/spec/file_one_hand.txt +52 -0
  30. data/spec/hand_statistics_spec.rb +846 -0
  31. data/spec/hand_statistics_spec_helper.rb +89 -0
  32. data/spec/player_statistics_spec.rb +230 -0
  33. data/spec/pokerstars_file_spec.rb +160 -0
  34. data/spec/pokerstars_hand_history_parser_spec.rb +197 -0
  35. data/spec/spec_helper.rb +9 -0
  36. data/spec/zpokerstars_hand_history_parser_integration.rb.txt +67 -0
  37. metadata +125 -0
@@ -0,0 +1,52 @@
1
+ PokerStars Game #24759011305: Tournament #139359808, $10+$1 Hold'em No Limit - Level I (10/20) - 2009/02/09 14:02:39 ET
2
+ Table '139359808 122' 9-max Seat #5 is the button
3
+ Seat 2: wizardwerdna (3000 in chips)
4
+ Seat 3: EEYORE_Q6 (3000 in chips)
5
+ Seat 4: bimbi76 (3020 in chips)
6
+ Seat 5: izibi (2970 in chips)
7
+ Seat 6: Spidar (2980 in chips) is sitting out
8
+ Seat 7: Little Dee (2900 in chips)
9
+ Seat 8: Gw�nni (3000 in chips)
10
+ Seat 9: MartinBOF84 (3130 in chips)
11
+ Spidar: posts small blind 10
12
+ Little Dee: posts big blind 20
13
+ *** HOLE CARDS ***
14
+ Dealt to wizardwerdna [Qc 4d]
15
+ Gw�nni: folds
16
+ MartinBOF84: folds
17
+ wizardwerdna: folds
18
+ EEYORE_Q6: calls 20
19
+ bimbi76: folds
20
+ izibi: calls 20
21
+ Spidar: folds
22
+ Little Dee: checks ""
23
+ *** FLOP *** [Ac Qs Ks]
24
+ Little Dee: checks
25
+ EEYORE_Q6: bets 20
26
+ izibi: raises 60 to 80
27
+ Little Dee: folds
28
+ EEYORE_Q6: calls 60
29
+ *** TURN *** [Ac Qs Ks] [Js]
30
+ EEYORE_Q6: checks
31
+ izibi: checks
32
+ *** RIVER *** [Ac Qs Ks Js] [8d]
33
+ EEYORE_Q6: bets 60
34
+ izibi: calls 60
35
+ *** SHOW DOWN ***
36
+ EEYORE_Q6: shows [2s Ah] (a pair of Aces)
37
+ izibi: shows [Ad 6d] (a pair of Aces)
38
+ EEYORE_Q6 collected 175 from pot
39
+ izibi collected 175 from pot
40
+ *** SUMMARY ***
41
+ Total pot 350 | Rake 0
42
+ Board [Ac Qs Ks Js 8d]
43
+ Seat 2: wizardwerdna folded before Flop (didn't bet)
44
+ Seat 3: EEYORE_Q6 showed [2s Ah] and won (175) with a pair of Aces
45
+ Seat 4: bimbi76 folded before Flop (didn't bet)
46
+ Seat 5: izibi (button) showed [Ad 6d] and won (175) with a pair of Aces
47
+ Seat 6: Spidar (small blind) folded before Flop
48
+ Seat 7: Little Dee (big blind) folded on the Flop
49
+ Seat 8: Gw�nni folded before Flop (didn't bet)
50
+ Seat 9: MartinBOF84 folded before Flop (didn't bet)
51
+
52
+
@@ -0,0 +1,846 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/pokerstats/hand_statistics')
5
+
6
+ include Pokerstats
7
+ require File.expand_path(File.dirname(__FILE__) + '/hand_statistics_spec_helper')
8
+
9
+
10
+ Spec::Matchers.define :have_all_and_only_keys do |keylist|
11
+ match do |hash|
12
+ hashkeys = hash.keys
13
+ hashkeys.size == keylist.size && hash.keys.all?{|each_key| keylist.include? each_key}
14
+ end
15
+ failure_message_for_should do |hash|
16
+ "missing keys: #{(keylist - hash.keys).inspect}, extra keys: #{(hash.keys - keylist).inspect}"
17
+ end
18
+ failure_message_for_should_not do |hash|
19
+ "the keys (#{hash.keys.inspect}) are all the same"
20
+ end
21
+ description do
22
+ "have all and only the specified keys"
23
+ end
24
+ end
25
+
26
+ describe HandStatistics, "when created" do
27
+ before(:each) do
28
+ @stats = HandStatistics.new
29
+ end
30
+ it "should return an empty player list" do
31
+ @stats.should have(0).players
32
+ end
33
+
34
+ it "should properly chain updates of hand record information" do
35
+ sample_hand.each{|key, value| @stats.update_hand(key => value)}
36
+ @stats.hand_record.should == sample_hand
37
+ end
38
+
39
+ it "should not complain when asked to generate hand record with all HAND_INFORMATION_KEYS filled out" do
40
+ @stats.update_hand(sample_hand)
41
+ lambda{@stats.hand_record}.should_not raise_error(/#{HAND_RECORD_INCOMPLETE_MESSAGE}/)
42
+ end
43
+
44
+ it "should complain when asked to generate hand record if no information is given" do
45
+ lambda{@stats.hand_record}.should raise_error(/#{HAND_RECORD_INCOMPLETE_MESSAGE}/)
46
+ end
47
+
48
+ HandStatistics::HAND_INFORMATION_KEYS.each do |thing|
49
+ it "should complain when asked to generate hand record without a #{thing.to_s}" do
50
+ @stats.update_hand(sample_hand.except(thing))
51
+ lambda{@stats.hand_record}.should raise_error(/#{HAND_RECORD_INCOMPLETE_MESSAGE}/)
52
+ end
53
+ end
54
+
55
+ it "should not produce hand records having extra keys" do
56
+ @stats.update_hand(sample_hand)
57
+ @stats.update_hand(:street => :river)
58
+ @stats.hand_record.should have_all_and_only_keys HAND_INFORMATION_KEYS
59
+ end
60
+ end
61
+
62
+ describe HandStatistics, "when registering game activity" do
63
+
64
+ before(:each) do
65
+ @stats = HandStatistics.new
66
+ end
67
+
68
+ it "should allow you to register a player" do
69
+ @stats.register_player sample_player
70
+ @stats.should have(1).player_records_without_validation
71
+ @stats.player_records_without_validation.first[:screen_name].should == sample_player[:screen_name]
72
+ @stats.player_records_without_validation.first[:seat].should == sample_player[:seat]
73
+ end
74
+
75
+ it "should complain when registering a player twice" do
76
+ @stats.register_player sample_player
77
+ lambda{@stats.register_player sample_player}.should raise_error(/#{PLAYER_RECORDS_DUPLICATE_PLAYER_NAME}/)
78
+ end
79
+
80
+ it "should allow you to register a button" do
81
+ @stats.button.should be_nil
82
+ @stats.register_button(3)
83
+ @stats.button.should == 3
84
+ end
85
+
86
+ it "should complain when registering action for an unregistered player" do
87
+ lambda {
88
+ @stats.register_action sample_action[:screen_name], sample_action[:action], sample_action
89
+ }.should raise_error(/#{PLAYER_RECORDS_UNREGISTERED_PLAYER}/)
90
+ end
91
+
92
+ it "should allow you to register an action" do
93
+ @stats.register_player sample_player
94
+ lambda{@stats.register_action sample_action[:screen_name], sample_action[:action], sample_action}.should_not raise_error
95
+ end
96
+
97
+ it "should complain when asked to generate player records without a registered player" do
98
+ @stats.register_button(3)
99
+ lambda{@stats.player_records}.should raise_error(PLAYER_RECORDS_NO_PLAYER_REGISTERED)
100
+ end
101
+
102
+ it "should complain when asked to generate player records without a registered button" do
103
+ @stats.register_player sample_player
104
+ lambda{@stats.player_records}.should raise_error(PLAYER_RECORDS_NO_BUTTON_REGISTERED)
105
+ end
106
+ end
107
+
108
+ describe HandStatistics, "when managing street state" do
109
+ before(:each) do
110
+ @stats = HandStatistics.new
111
+ end
112
+
113
+ it "should initially have street state set to :prelude" do
114
+ @stats.street.should == :prelude
115
+ end
116
+
117
+ it "should change street state on an explicit call to #street_transition" do
118
+ @stats.street_transition(:foo)
119
+ @stats.street.should == :foo
120
+ end
121
+
122
+ it "should not transition when hand_update does not change street state" do
123
+ @stats.should_not_receive(:street_transition)
124
+ @stats.update_hand(:street => :prelude)
125
+ end
126
+
127
+ it "should transition when hand_update chances to a new state" do
128
+ @stats.should_receive(:street_transition).with(:preflop)
129
+ @stats.update_hand(:street => :preflop)
130
+ end
131
+ end
132
+
133
+ describe HandStatistics, "when evaluating position with three players" do
134
+ before(:each) do
135
+ @stats = HandStatistics.new
136
+ @stats.register_player @seat2 = next_sample_player(:seat => 2, :screen_name => "seat2")
137
+ @stats.register_player @seat4 = next_sample_player(:seat => 4, :screen_name => "seat4")
138
+ @stats.register_player @seat6 = next_sample_player(:seat => 6, :screen_name => "seat6")
139
+ end
140
+ it "should correctly identify position with the button on a chair" do
141
+ @stats.register_button(6)
142
+ @stats.should_not be_cutoff("seat4")
143
+ @stats.should be_button("seat6")
144
+ @stats.should be_sbpos("seat2")
145
+ @stats.should be_bbpos("seat4")
146
+ end
147
+ it "should correctly identify position with the button to the left of the first chair" do
148
+ @stats.register_button(1)
149
+ @stats.should_not be_cutoff("seat4")
150
+ @stats.should be_button("seat6")
151
+ @stats.should be_sbpos("seat2")
152
+ @stats.should be_bbpos("seat4")
153
+ end
154
+ it "should correctly identify position with the button to the right of the last chair" do
155
+ @stats.register_button(9)
156
+ @stats.should_not be_cutoff("seat6")
157
+ @stats.should be_button("seat6")
158
+ @stats.should be_sbpos("seat2")
159
+ @stats.should be_bbpos("seat4")
160
+ end
161
+ it "should correctly identify position with the button between two middle chairs" do
162
+ @stats.register_button(5)
163
+ @stats.should_not be_cutoff("seat2")
164
+ @stats.should be_button("seat4")
165
+ @stats.should be_sbpos("seat6")
166
+ @stats.should be_bbpos("seat2")
167
+ end
168
+ end
169
+
170
+ describe HandStatistics, "when evaluating position with four players" do
171
+ before(:each) do
172
+ @stats = HandStatistics.new
173
+ @stats.register_player @seat2 = next_sample_player(:seat => 2, :screen_name => "seat2")
174
+ @stats.register_player @seat4 = next_sample_player(:seat => 4, :screen_name => "seat4")
175
+ @stats.register_player @seat6 = next_sample_player(:seat => 6, :screen_name => "seat6")
176
+ @stats.register_player @seat8 = next_sample_player(:seat => 8, :screen_name => "seat8")
177
+ end
178
+ it "should correctly identify position with the button on a chair" do
179
+ @stats.register_button(6)
180
+ @stats.should be_cutoff("seat4")
181
+ @stats.should be_button("seat6")
182
+ @stats.should be_sbpos("seat8")
183
+ @stats.should be_bbpos("seat2")
184
+ end
185
+ it "should correctly identify position with the button to the left of the first chair" do
186
+ @stats.register_button(1)
187
+ @stats.should be_cutoff("seat6")
188
+ @stats.should be_button("seat8")
189
+ @stats.should be_sbpos("seat2")
190
+ @stats.should be_bbpos("seat4")
191
+ end
192
+ it "should correctly identify position with the button to the right of the last chair" do
193
+ @stats.register_button(9)
194
+ @stats.should be_cutoff("seat6")
195
+ @stats.should be_button("seat8")
196
+ @stats.should be_sbpos("seat2")
197
+ @stats.should be_bbpos("seat4")
198
+ end
199
+ it "should correctly identify position with the button between two middle chairs" do
200
+ @stats.register_button(5)
201
+ @stats.should be_cutoff("seat2")
202
+ @stats.should be_button("seat4")
203
+ @stats.should be_sbpos("seat6")
204
+ @stats.should be_bbpos("seat8")
205
+ end
206
+ end
207
+
208
+ describe HandStatistics, "when managing plugins" do
209
+ before(:each) do
210
+ @hand_statistics = HandStatistics.new
211
+ @plugins = @hand_statistics.plugins
212
+ end
213
+ it "should install specific plugins upon initialization" do
214
+ HandStatistics.new.plugins.map{|each| each.class}.should include(CashStatistics)
215
+ end
216
+ it "should install all the plugins identified upon initialization" do
217
+ HandStatistics.new.plugins.map{|each| each.class}.should include(*HandStatistics.plugin_factory)
218
+ end
219
+ it "should notify plugins whenever there is a street transition" do
220
+ @plugins.each{|each_plugin| each_plugin.should_receive(:street_transition).with(:foo)}
221
+ @hand_statistics.street_transition(:foo)
222
+ end
223
+ it "should notify plugins whenever there is a street transition for a player" do
224
+ @plugins.each{|each_plugin| each_plugin.should_receive(:street_transition_for_player).with(:foo, :bar)}
225
+ @hand_statistics.street_transition_for_player(:foo, :bar)
226
+ end
227
+ it "should notify plugins whenever a player is registered" do
228
+ @plugins.each{|each_plugin| each_plugin.should_receive(:register_player).with("andy", :prelude)}
229
+ @hand_statistics.register_player({:screen_name => "andy"})
230
+ end
231
+ end
232
+
233
+ describe HandStatistics, "when registering standard actions" do
234
+ before(:each) do
235
+ @stats = HandStatistics.new
236
+ @stats.update_hand sample_hand
237
+ @stats.register_player sample_player
238
+ @stats.street_transition :preflop
239
+ end
240
+
241
+ it "should post correctly" do
242
+ lambda{
243
+ register_post(sample_player, "5".to_d)
244
+ }.should change{@stats.posted(sample_player[:screen_name])}.by("5".to_d)
245
+ end
246
+
247
+ it "should ante correctly" do
248
+ lambda{
249
+ register_ante(sample_player, "5".to_d)
250
+ }.should change{@stats.posted(sample_player[:screen_name])}.by("5".to_d)
251
+ end
252
+
253
+ it "should pay correctly" do
254
+ lambda{
255
+ register_bet(sample_player, "5".to_d)
256
+ }.should change{@stats.paid(sample_player[:screen_name])}.by("5".to_d)
257
+ end
258
+
259
+ it "should win correctly" do
260
+ lambda{
261
+ register_win(sample_player, "5".to_d)
262
+ }.should change{@stats.won(sample_player[:screen_name])}.by("5".to_d)
263
+ end
264
+
265
+ it "should check correctly" do
266
+ lambda {
267
+ register_check(sample_player)
268
+ }.should_not change{@stats}
269
+ end
270
+
271
+ it "should fold correctly" do
272
+ lambda {
273
+ register_fold(sample_player)
274
+ }.should_not change{@stats}
275
+ end
276
+
277
+ it "should show cards correctly" do
278
+ register_cards(sample_player, "AH KH")
279
+ @stats.cards(sample_player[:screen_name]).should == "AH KH"
280
+ end
281
+ end
282
+
283
+ describe HandStatistics, "when registering pay_to actions" do
284
+ before(:each) do
285
+ @stats = HandStatistics.new
286
+ @stats.update_hand sample_hand
287
+ @stats.register_player @first_player = next_sample_player
288
+ @stats.register_player @second_player = next_sample_player
289
+ @stats.register_button @first_player[:seat]
290
+ register_post @first_player, 1
291
+ end
292
+
293
+ it "should pay_to correctly for regular raise" do
294
+ register_street :preflop
295
+ lambda{
296
+ register_raise_to @second_player, 7
297
+ }.should change{@stats.paid(@second_player[:screen_name])}.by(7)
298
+ end
299
+
300
+ it "should pay_to correctly for re-raise" do
301
+ @stats.register_action @second_player[:screen_name], "**************", :result => :neutral
302
+ register_post(@second_player, 2)
303
+ register_street :preflop
304
+ lambda{
305
+ register_street :preflop
306
+ register_raise_to @first_player, "7".to_d
307
+ }.should change{@stats.paid(@first_player[:screen_name])}.by("6".to_d)
308
+ end
309
+
310
+ it "should pay_to correctly for re-raise after new phase" do
311
+ register_post @second_player, "2".to_d
312
+ register_street :flop
313
+ register_bet @first_player, "3".to_d
314
+ lambda{
315
+ register_raise_to @second_player, "7".to_d
316
+ }.should change{@stats.paid(@second_player[:screen_name])}.by("7".to_d)
317
+ lambda{
318
+ #TODO FIX THIS TEST
319
+ @stats.register_action @first_player[:screen_name], "sample_reraise", :result => :pay_to, :amount => "14".to_d
320
+ }.should change{@stats.paid(@first_player[:screen_name])}.by("11".to_d)
321
+ end
322
+ end
323
+
324
+ describe HandStatistics, "when registering pay_to actions after antes" do
325
+ before(:each) do
326
+ @stats = HandStatistics.new
327
+ @stats.update_hand sample_hand
328
+ @stats.register_player @first_player = next_sample_player
329
+ @stats.register_player @second_player = next_sample_player
330
+ register_post @first_player, 1
331
+ register_ante @first_player, 1
332
+ register_ante @second_player, 1
333
+ end
334
+
335
+ it "should pay_to correctly for regular raise" do
336
+ register_street :preflop
337
+ lambda{
338
+ register_raise_to @second_player, 7
339
+ }.should change{@stats.paid(@second_player[:screen_name])}.by(7)
340
+ end
341
+
342
+ it "should pay_to correctly for re-raise" do
343
+ @stats.register_action @second_player[:screen_name], "**************", :result => :neutral
344
+ register_post(@second_player, 2)
345
+ register_street :preflop
346
+ lambda{
347
+ register_street :preflop
348
+ register_raise_to @first_player, "7".to_d
349
+ }.should change{@stats.paid(@first_player[:screen_name])}.by("6".to_d)
350
+ end
351
+
352
+ it "should pay_to correctly for re-raise after new phase" do
353
+ register_post @second_player, "2".to_d
354
+ register_street :flop
355
+ register_bet @first_player, "3".to_d
356
+ lambda{
357
+ register_raise_to @second_player, "7".to_d
358
+ }.should change{@stats.paid(@second_player[:screen_name])}.by("7".to_d)
359
+ lambda{
360
+ #TODO FIX THIS TEST
361
+ @stats.register_action @first_player[:screen_name], "sample_reraise", :result => :pay_to, :amount => "14".to_d
362
+ }.should change{@stats.paid(@first_player[:screen_name])}.by("11".to_d)
363
+ end
364
+ end
365
+
366
+ describe HandStatistics, "when measuring pfr" do
367
+ before(:each) do
368
+ @stats = HandStatistics.new
369
+ @stats.register_player @first_player = next_sample_player
370
+ @stats.register_player @second_player = next_sample_player
371
+ register_post(@first_player, 1)
372
+ register_post(@second_player, 2)
373
+ register_street :preflop
374
+ end
375
+
376
+ it "should find a pfr opportunity if first actors limped" do
377
+ register_call(@first_player, 1)
378
+ register_check(@second_player)
379
+ @stats.should be_pfr_opportunity(@first_player[:screen_name])
380
+ @stats.should_not be_pfr_opportunity_taken(@first_player[:screen_name])
381
+ @stats.should be_pfr_opportunity(@second_player[:screen_name])
382
+ @stats.should_not be_pfr_opportunity_taken(@second_player[:screen_name])
383
+ end
384
+
385
+ it "should not find a pfr opportunity if another player raises first" do
386
+ register_raise_to(@first_player, 5)
387
+ register_call(@second_player, 3)
388
+ @stats.should be_pfr_opportunity(@first_player[:screen_name])
389
+ @stats.should be_pfr_opportunity_taken(@first_player[:screen_name])
390
+ @stats.should_not be_pfr_opportunity(@second_player[:screen_name])
391
+ end
392
+
393
+ it "should not find a pfr opportunity if player made a raise other than the first raise preflpp" do
394
+ register_raise_to(@first_player, 5)
395
+ register_raise_to(@second_player, 10)
396
+ register_call(@first_player, 5)
397
+ @stats.should be_pfr_opportunity(@first_player[:screen_name])
398
+ @stats.should be_pfr_opportunity_taken(@first_player[:screen_name])
399
+ @stats.should_not be_pfr_opportunity(@second_player[:screen_name])
400
+ end
401
+
402
+ it "should be unaffected by postflop bets and raises" do
403
+ register_call(@first_player, 1)
404
+ register_check(@second_player)
405
+ register_street :flop
406
+ register_bet(@first_player, 4)
407
+ register_bet(@second_player, 10)
408
+ register_call(@first_player, 6)
409
+ @stats.should be_pfr_opportunity(@first_player[:screen_name])
410
+ @stats.should be_pfr_opportunity(@second_player[:screen_name])
411
+ end
412
+ end
413
+
414
+ describe HandStatistics, "when measuring aggression" do
415
+ before(:each) do
416
+ @stats = HandStatistics.new
417
+ @stats.register_player @first_player = next_sample_player
418
+ @stats.register_player @second_player = next_sample_player
419
+ end
420
+
421
+ it "should all have zero values when no actions have been taken" do
422
+ @stats.preflop_passive(@first_player[:screen_name]).should be_zero
423
+ @stats.postflop_passive(@first_player[:screen_name]).should be_zero
424
+ @stats.preflop_aggressive(@first_player[:screen_name]).should be_zero
425
+ @stats.postflop_aggressive(@first_player[:screen_name]).should be_zero
426
+ end
427
+
428
+ it "should treat a call preflop as a passive move" do
429
+ register_street :preflop
430
+ lambda{register_call(@first_player, 1)}.should change{@stats.preflop_passive(@first_player[:screen_name])}.by(1)
431
+ end
432
+
433
+ it "should treat a call postflop as a passive move" do
434
+ register_street :flop
435
+ lambda{register_call(@first_player, 1)}.should change{@stats.postflop_passive(@first_player[:screen_name])}.by(1)
436
+ end
437
+
438
+ it "should treat a raise preflop as an aggressive move" do
439
+ register_street :preflop
440
+ lambda{register_raise_to(@first_player, 7)}.should change{@stats.preflop_aggressive(@first_player[:screen_name])}.by(1)
441
+ end
442
+
443
+ it "should treat a raise postflop as an aggressive move" do
444
+ register_street :flop
445
+ lambda{register_raise_to(@first_player, 7)}.should change{@stats.postflop_aggressive(@first_player[:screen_name])}.by(1)
446
+ end
447
+
448
+ it "should treat a bet postflop as an aggressive move" do
449
+ register_street :preflop
450
+ lambda{register_bet(@first_player, 5)}.should change{@stats.preflop_aggressive(@first_player[:screen_name])}.by(1)
451
+ end
452
+
453
+ it "should not treat a check as an aggressive or a passive move" do
454
+ register_street :preflop
455
+ lambda{register_check(@first_player)}.should_not change{@stats.preflop_aggressive(@first_player[:screen_name])}
456
+ lambda{register_check(@first_player)}.should_not change{@stats.preflop_passive(@first_player[:screen_name])}
457
+ lambda{register_check(@first_player)}.should_not change{@stats.postflop_aggressive(@first_player[:screen_name])}
458
+ lambda{register_check(@first_player)}.should_not change{@stats.postflop_aggressive(@first_player[:screen_name])}
459
+ end
460
+
461
+ it "should not treat a fold as an aggressive or a passive move" do
462
+ register_street :preflop
463
+ lambda{register_fold(@first_player)}.should_not change{@stats.preflop_aggressive(@first_player[:screen_name])}
464
+ lambda{register_fold(@first_player)}.should_not change{@stats.preflop_passive(@first_player[:screen_name])}
465
+ lambda{register_fold(@first_player)}.should_not change{@stats.postflop_aggressive(@first_player[:screen_name])}
466
+ lambda{register_fold(@first_player)}.should_not change{@stats.postflop_aggressive(@first_player[:screen_name])}
467
+ end
468
+ end
469
+
470
+ describe HandStatistics, "when measuring c-bets" do
471
+ before(:each) do
472
+ @stats = HandStatistics.new
473
+ @stats.register_player @button = next_sample_player(:screen_name => "button")
474
+ @stats.register_player @sb = next_sample_player(:screen_name => "small blind")
475
+ @stats.register_player @bb = next_sample_player(:screen_name => "big blind")
476
+ @stats.register_button @button[:seat]
477
+ register_street :prelude
478
+ register_post @sb, 1
479
+ register_post @bb, 2
480
+ register_street :preflop
481
+ end
482
+ it "should not find preflop opportunity without a preflop raise" do
483
+ register_call @button, 2
484
+ register_call @sb, 1
485
+ register_check @bb
486
+ register_street :flop
487
+ register_bet @button, 2
488
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
489
+ @stats.should_not be_cbet_opportunity(@sb[:screen_name])
490
+ @stats.should_not be_cbet_opportunity(@bb[:screen_name])
491
+ end
492
+ it "should not find c-bet opportunity with two preflop raises" do
493
+ register_raise_to @button, 7
494
+ register_raise_to @sb, 14
495
+ register_fold @bb
496
+ register_street :flop
497
+ register_bet @button, 2
498
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
499
+ @stats.should_not be_cbet_opportunity(@sb[:screen_name])
500
+ @stats.should_not be_cbet_opportunity(@bb[:screen_name])
501
+ end
502
+ it "should not find c-bet opportunity if player raise is not the preflop raiser" do
503
+ register_call @button, 2
504
+ register_raise_to @sb, 7
505
+ register_fold @bb
506
+ register_call @button, 5
507
+ register_street :flop
508
+ register_bet @button, 2
509
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
510
+ end
511
+ it "should not find c-bet opportunity if non-raiser acts first" do
512
+ register_call @button, 2
513
+ register_raise_to @sb, 7
514
+ register_fold @bb
515
+ register_call @button, 5
516
+ register_street :flop
517
+ register_bet @button, 2
518
+ register_call @sb, 2
519
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
520
+ end
521
+
522
+ it "should find c-bet opportunity taken when lone pre-flop raiser makes first-in bet post-flop in position" do
523
+ register_fold @button
524
+ register_call @sb, 1
525
+ register_raise_to @bb, 7
526
+ register_call @sb, 5
527
+ register_street :flop
528
+ register_check @sb
529
+ register_bet @bb, 7
530
+ @stats.should be_cbet_opportunity(@bb[:screen_name])
531
+ @stats.should be_cbet_opportunity_taken(@bb[:screen_name])
532
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
533
+ @stats.should_not be_cbet_opportunity(@sb[:screen_name])
534
+ end
535
+ it "should find c-bet opportunity taken when lone pre-flop raiser makes first-in bet post-flop out of position" do
536
+ register_fold @button
537
+ register_raise_to @sb, 7
538
+ register_call @bb, 5
539
+ register_street :flop
540
+ register_bet @sb, 7
541
+ @stats.should be_cbet_opportunity(@sb[:screen_name])
542
+ @stats.should be_cbet_opportunity_taken(@sb[:screen_name])
543
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
544
+ @stats.should_not be_cbet_opportunity(@bb[:screen_name])
545
+ end
546
+ it "should find c-bet opportunity declined when lone pre-flop raiser does not make first-in bet post-flop in position" do
547
+ register_fold @button
548
+ register_call @sb, 1
549
+ register_raise_to @bb, 7
550
+ register_call @sb, 5
551
+ register_street :flop
552
+ register_check @sb
553
+ register_check @bb
554
+ @stats.should be_cbet_opportunity(@bb[:screen_name])
555
+ @stats.should_not be_cbet_opportunity_taken(@bb[:screen_name])
556
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
557
+ @stats.should_not be_cbet_opportunity(@sb[:screen_name])
558
+ end
559
+ it "should find c-bet opportunity declined when lone pre-flop raiser does not make first-in bet post-flop out of position" do
560
+ register_fold @button
561
+ register_raise_to @sb, 7
562
+ register_call @bb, 5
563
+ register_street :flop
564
+ register_check @sb
565
+ register_check @bb
566
+ @stats.should be_cbet_opportunity(@sb[:screen_name])
567
+ @stats.should_not be_cbet_opportunity_taken(@sb[:screen_name])
568
+ @stats.should_not be_cbet_opportunity(@button[:screen_name])
569
+ @stats.should_not be_cbet_opportunity(@bb[:screen_name])
570
+ end
571
+ end
572
+
573
+ describe HandStatistics, "when measuring blind attacks" do
574
+ before(:each) do
575
+ @stats = HandStatistics.new
576
+ register_street :prelude
577
+ @stats.register_player @utg = next_sample_player(:screen_name => "utg", :seat => 2)
578
+ @stats.register_player @cutoff = next_sample_player(:screen_name => "cutoff", :seat => 4)
579
+ @stats.register_player @button = next_sample_player(:screen_name => "button", :seat => 6)
580
+ @stats.register_player @sb = next_sample_player(:screen_name => "sb", :seat => 8)
581
+ @stats.register_player @bb = next_sample_player(:screen_name => "bb", :seat => 10)
582
+ @stats.register_button(@button[:seat])
583
+ register_post(@sb, 1)
584
+ register_post(@bb, 2)
585
+ register_street :preflop
586
+ end
587
+
588
+ it "should identify cutoff opportunities if everybody folds to him" do
589
+ register_fold(@utg)
590
+ register_call(@cutoff, 2)
591
+ register_call(@button, 2)
592
+ register_call(@sb, 1)
593
+ register_check(@bb)
594
+ @stats.should be_blind_attack_opportunity("cutoff")
595
+ @stats.should_not be_blind_attack_opportunity_taken("cutoff")
596
+ @stats.should_not be_blind_attack_opportunity("button")
597
+ @stats.should_not be_blind_attack_opportunity("utg")
598
+ @stats.should_not be_blind_attack_opportunity("sb")
599
+ @stats.should_not be_blind_attack_opportunity("bb")
600
+ end
601
+
602
+ it "should properly identify cutoff and button opportunities if everybody folds to button" do
603
+ register_fold(@utg)
604
+ register_fold(@cutoff)
605
+ register_call(@button, 2)
606
+ register_call(@sb, 1)
607
+ register_check(@bb)
608
+ @stats.should be_blind_attack_opportunity("cutoff")
609
+ @stats.should_not be_blind_attack_opportunity_taken("cutoff")
610
+ @stats.should be_blind_attack_opportunity("button")
611
+ @stats.should_not be_blind_attack_opportunity_taken("button")
612
+ @stats.should_not be_blind_attack_opportunity("utg")
613
+ @stats.should_not be_blind_attack_opportunity("sb")
614
+ @stats.should_not be_blind_attack_opportunity("bb")
615
+ end
616
+
617
+ it "should identify cutoff and button attack opportunities if button raises first-in" do
618
+ register_fold(@utg)
619
+ register_fold(@cutoff)
620
+ register_raise_to(@button, 7)
621
+ register_call(@sb, 6)
622
+ register_fold(@bb)
623
+ register_fold(@utg)
624
+ register_fold(@cutoff)
625
+ @stats.should be_blind_attack_opportunity("cutoff")
626
+ @stats.should_not be_blind_attack_opportunity_taken("cutoff")
627
+ @stats.should be_blind_attack_opportunity("button")
628
+ @stats.should be_blind_attack_opportunity_taken("button")
629
+ @stats.should_not be_blind_attack_opportunity("utg")
630
+ @stats.should_not be_blind_attack_opportunity("sb")
631
+ @stats.should_not be_blind_attack_opportunity("bb")
632
+ end
633
+
634
+ it "should identify no attack opportunities if utg raises first in" do
635
+ register_raise_to(@utg, 7)
636
+ register_call(@cutoff, 7)
637
+ register_call(@button, 7)
638
+ register_call(@sb, 6)
639
+ register_fold(@bb)
640
+ @stats.should_not be_blind_attack_opportunity("cutoff")
641
+ @stats.should_not be_blind_attack_opportunity("button")
642
+ @stats.should_not be_blind_attack_opportunity("utg")
643
+ @stats.should_not be_blind_attack_opportunity("sb")
644
+ @stats.should_not be_blind_attack_opportunity("bb")
645
+ end
646
+
647
+ it "should identify attack opportunities only for button if cutoff raises first in" do
648
+ register_raise_to(@utg, 7)
649
+ register_call(@cutoff, 7)
650
+ register_call(@button, 7)
651
+ register_call(@sb, 6)
652
+ register_fold(@bb)
653
+ @stats.should_not be_blind_attack_opportunity("cutoff")
654
+ @stats.should_not be_blind_attack_opportunity("button")
655
+ @stats.should_not be_blind_attack_opportunity("utg")
656
+ @stats.should_not be_blind_attack_opportunity("sb")
657
+ @stats.should_not be_blind_attack_opportunity("bb")
658
+ end
659
+ end
660
+
661
+ describe HandStatistics, "measuring blind defense" do
662
+ before(:each) do
663
+ @stats = HandStatistics.new
664
+ register_street :prelude
665
+ @stats.register_player @utg = next_sample_player(:screen_name => "utg", :seat => 2)
666
+ @stats.register_player @cutoff = next_sample_player(:screen_name => "cutoff", :seat => 4)
667
+ @stats.register_player @button = next_sample_player(:screen_name => "button", :seat => 6)
668
+ @stats.register_player @sb = next_sample_player(:screen_name => "sb", :seat => 8)
669
+ @stats.register_player @bb = next_sample_player(:screen_name => "bb", :seat => 10)
670
+ @stats.register_button(@button[:seat])
671
+ register_post(@sb, 1)
672
+ register_post(@bb, 2)
673
+ register_street :preflop
674
+ end
675
+
676
+ it "should not identify a blind attack when nobody raises" do
677
+ register_fold(@utg)
678
+ register_call(@cutoff, 2)
679
+ register_call(@button, 2)
680
+ register_call(@sb, 1)
681
+ register_check(@bb)
682
+ @stats.should_not be_blind_defense_opportunity("sb")
683
+ @stats.should_not be_blind_defense_opportunity("bb")
684
+ @stats.should_not be_blind_defense_opportunity("utg")
685
+ @stats.should_not be_blind_defense_opportunity("cutoff")
686
+ @stats.should_not be_blind_defense_opportunity("button")
687
+ end
688
+
689
+ it "should not identify a blind attack when a non-attacker raises first" do
690
+ register_raise_to(@utg, 7)
691
+ register_fold(@cutoff)
692
+ register_call(@button, 7)
693
+ register_call(@sb, 6)
694
+ register_check(@bb)
695
+ @stats.should_not be_blind_defense_opportunity("sb")
696
+ @stats.should_not be_blind_defense_opportunity("bb")
697
+ @stats.should_not be_blind_defense_opportunity("utg")
698
+ @stats.should_not be_blind_defense_opportunity("cutoff")
699
+ @stats.should_not be_blind_defense_opportunity("button")
700
+ end
701
+
702
+ it "should not identify a blind attack when a non-attacker raises, even if attacker re-raises" do
703
+ register_raise_to(@utg, 7)
704
+ register_fold(@cutoff)
705
+ register_raise_to(@button, 15)
706
+ register_call(@sb, 14)
707
+ register_call(@bb, 13)
708
+ register_call(@utg, 8)
709
+ @stats.should_not be_blind_defense_opportunity("sb")
710
+ @stats.should_not be_blind_defense_opportunity("bb")
711
+ @stats.should_not be_blind_defense_opportunity("utg")
712
+ @stats.should_not be_blind_defense_opportunity("cutoff")
713
+ @stats.should_not be_blind_defense_opportunity("button")
714
+ end
715
+
716
+ it "should identify a blind attack when button raises first-in, taken when blind calls" do
717
+ register_fold(@utg)
718
+ register_fold(@cutoff)
719
+ register_raise_to(@button, 7)
720
+ register_call(@sb, 6)
721
+ register_fold(@bb)
722
+ register_fold(@utg)
723
+ register_fold(@cutoff)
724
+ @stats.should be_blind_defense_opportunity("sb")
725
+ @stats.should be_blind_defense_opportunity_taken("sb")
726
+ @stats.should_not be_blind_defense_opportunity("bb")
727
+ @stats.should_not be_blind_defense_opportunity("utg")
728
+ @stats.should_not be_blind_defense_opportunity("cutoff")
729
+ @stats.should_not be_blind_defense_opportunity("button")
730
+ end
731
+
732
+ it "should identify a blind attack when cutoff raises first-in and button folds, not taken on fold, and taken on raise" do
733
+ register_fold(@utg)
734
+ register_raise_to(@cutoff, 7)
735
+ register_fold(@button)
736
+ register_fold(@sb)
737
+ register_raise_to(@bb,200)
738
+ register_fold(@utg)
739
+ register_fold(@cutoff)
740
+ @stats.should be_blind_defense_opportunity("sb")
741
+ @stats.should_not be_blind_defense_opportunity_taken("sb")
742
+ @stats.should be_blind_defense_opportunity("bb")
743
+ @stats.should be_blind_defense_opportunity_taken("bb")
744
+ @stats.should_not be_blind_defense_opportunity("utg")
745
+ @stats.should_not be_blind_defense_opportunity("cutoff")
746
+ @stats.should_not be_blind_defense_opportunity("button")
747
+ end
748
+
749
+ it "should not identify a blind attack when cutoff raises first-in and button calls" do
750
+ register_fold(@utg)
751
+ register_raise_to(@cutoff, 7)
752
+ register_call(@button, 7)
753
+ register_call(@sb, 6)
754
+ register_fold(@bb)
755
+ register_fold(@utg)
756
+ @stats.should_not be_blind_defense_opportunity("sb")
757
+ @stats.should_not be_blind_defense_opportunity("bb")
758
+ @stats.should_not be_blind_defense_opportunity("utg")
759
+ @stats.should_not be_blind_defense_opportunity("cutoff")
760
+ @stats.should_not be_blind_defense_opportunity("button")
761
+ end
762
+
763
+ it "should not identify a blind attack when cutoff raises first-in and button re-raises" do
764
+ register_fold(@utg)
765
+ register_raise_to(@cutoff, 7)
766
+ register_raise_to(@button, 15)
767
+ register_call(@sb, 6)
768
+ register_fold(@bb)
769
+ register_fold(@utg)
770
+ @stats.should_not be_blind_defense_opportunity("sb")
771
+ @stats.should_not be_blind_defense_opportunity("bb")
772
+ @stats.should_not be_blind_defense_opportunity("utg")
773
+ @stats.should_not be_blind_defense_opportunity("cutoff")
774
+ @stats.should_not be_blind_defense_opportunity("button")
775
+ end
776
+ end
777
+
778
+ describe HandStatistics, "when reporting statistics" do
779
+ before(:each) do
780
+ @stats = HandStatistics.new
781
+ @stats.register_player @seat2 = next_sample_player(:seat => 2, :screen_name => "seat2")
782
+ @stats.register_player @seat4 = next_sample_player(:seat => 4, :screen_name => "seat4")
783
+ @stats.register_player @seat6 = next_sample_player(:seat => 6, :screen_name => "seat6")
784
+ @stats.register_player @seat8 = next_sample_player(:seat => 8, :screen_name => "seat8")
785
+ @blind_attack_plugin = @stats.plugins.find{|each| each.is_a? BlindAttackStatistics}
786
+ @cash_plugin = @stats.plugins.find{|each| each.is_a? CashStatistics}
787
+ @continuation_bet_plugin = @stats.plugins.find{|each| each.is_a? ContinuationBetStatistics}
788
+ @aggression_plugin = @stats.plugins.find{|each| each.is_a? AggressionStatistics}
789
+ @preflop_raise_plugin = @stats.plugins.find{|each| each.is_a? PreflopRaiseStatistics}
790
+ @reports = {}
791
+ end
792
+
793
+ it "should report blind attack statistics for each player" do
794
+ @blind_attack_plugin.should_receive(:blind_attack_opportunity?).exactly(@stats.players.size)
795
+ @blind_attack_plugin.should_receive(:blind_attack_opportunity_taken?).exactly(@stats.players.size)
796
+ @blind_attack_plugin.should_receive(:blind_defense_opportunity?).exactly(@stats.players.size)
797
+ @blind_attack_plugin.should_receive(:blind_defense_opportunity_taken?).exactly(@stats.players.size)
798
+ @reports = @stats.reports
799
+ @stats.players.each{|each| @reports[each].should include(
800
+ :is_blind_attack_opportunity,
801
+ :is_blind_attack_opportunity_taken,
802
+ :is_blind_defense_opportunity,
803
+ :is_blind_defense_opportunity_taken
804
+ )}
805
+ end
806
+
807
+ it "should report continuation bet statistics for each player" do
808
+ @continuation_bet_plugin.should_receive(:cbet_opportunity?).exactly(@stats.players.size)
809
+ @continuation_bet_plugin.should_receive(:cbet_opportunity_taken?).exactly(@stats.players.size)
810
+ @reports = @stats.reports
811
+ @stats.players.each{|each| @reports[each].should include(:is_cbet_opportunity)}
812
+ @stats.players.each{|each| @reports[each].should include(:is_cbet_opportunity_taken)}
813
+ end
814
+
815
+ it "should report cash statistics for each player" do
816
+ @cash_plugin.should_receive(:posted).exactly(@stats.players.size)
817
+ @cash_plugin.should_receive(:paid).exactly(@stats.players.size)
818
+ @cash_plugin.should_receive(:won).exactly(@stats.players.size)
819
+ @cash_plugin.should_receive(:cards).exactly(@stats.players.size)
820
+ @reports = @stats.reports
821
+ @stats.players.each{|each| @reports[each].should include(:posted)}
822
+ @stats.players.each{|each| @reports[each].should include(:paid)}
823
+ @stats.players.each{|each| @reports[each].should include(:won)}
824
+ @stats.players.each{|each| @reports[each].should include(:cards)}
825
+ end
826
+
827
+ it "should report aggression statistics for each player" do
828
+ @aggression_plugin.should_receive(:preflop_passive).exactly(@stats.players.size)
829
+ @aggression_plugin.should_receive(:preflop_aggressive).exactly(@stats.players.size)
830
+ @aggression_plugin.should_receive(:postflop_passive).exactly(@stats.players.size)
831
+ @aggression_plugin.should_receive(:postflop_aggressive).exactly(@stats.players.size)
832
+ @reports = @stats.reports
833
+ @stats.players.each{|each| @reports[each].should include(:preflop_passive)}
834
+ @stats.players.each{|each| @reports[each].should include(:preflop_aggressive)}
835
+ @stats.players.each{|each| @reports[each].should include(:postflop_passive)}
836
+ @stats.players.each{|each| @reports[each].should include(:postflop_aggressive)}
837
+ end
838
+
839
+ it "should report preflop raise statistics for each player" do
840
+ @preflop_raise_plugin.should_receive(:pfr_opportunity?).exactly(@stats.players.size)
841
+ @preflop_raise_plugin.should_receive(:pfr_opportunity_taken?).exactly(@stats.players.size)
842
+ @reports = @stats.reports
843
+ @stats.players.each{|each| @reports[each].should include(:is_pfr_opportunity)}
844
+ @stats.players.each{|each| @reports[each].should include(:is_pfr_opportunity_taken)}
845
+ end
846
+ end