rholdem 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.
data/spec/game_spec.rb ADDED
@@ -0,0 +1,565 @@
1
+ require File.dirname(__FILE__) + "/../lib/game"
2
+ include Holdem
3
+
4
+ describe Game do
5
+ before(:each) do
6
+ @g = Game.new(5.0)
7
+ end
8
+
9
+ it "should be creatable with small bet size (5 in a 5/10 game)" do
10
+ @g.should_not be_nil
11
+ end
12
+
13
+ it "should set the small blind correctly" do
14
+ @g.sb.should == 2.5
15
+ end
16
+
17
+ it "should set the big blind correctly" do
18
+ @g.bb.should == 5.0
19
+ end
20
+
21
+ it "should be the pre-flop round" do
22
+ @g.round.should == :preflop
23
+ end
24
+
25
+ it "should get a new Deck to play with (pre-shuffled)" do
26
+ @g.deck.is_a?(Deck).should be_true
27
+ end
28
+
29
+ it "should have no known winners" do
30
+ @g.winners.should == []
31
+ end
32
+
33
+ it "should not have any cards on the board" do
34
+ @g.board.should == []
35
+ end
36
+
37
+ it "should create ten players by default" do
38
+ @g.players.each { |p| p.is_a?(Player).should be_true }
39
+ end
40
+
41
+ it "should allow a flexible number of players under 10" do
42
+ game = Game.new(5.0, 8)
43
+ game.players.length.should == 8
44
+ end
45
+
46
+ it "should not allow more than 11 players" do
47
+ lambda { Game.new(5.0, 12) }.should raise_error
48
+ end
49
+
50
+ it "should require at least 2 players" do
51
+ lambda { Game.new(5.0, 1) }.should raise_error
52
+ end
53
+
54
+ it "should collect small blind from first position" do
55
+ @g.players[0].in_round.should == 2.5
56
+ end
57
+
58
+ it "should collect big blind from second position" do
59
+ @g.players[1].in_round.should == 5.0
60
+ end
61
+
62
+ it "should increment the bets after the blinds are posted" do
63
+ @g.bets_in_round.should == 1
64
+ end
65
+
66
+ it "should increment the pot size when a bet is required" do
67
+ @g.pot.should == 7.5
68
+ end
69
+
70
+ it "should declare a winner if only one player is left" do
71
+ @g.players[0..8].each { |p| p.should_receive(:act).at_least(1).and_return(:weak) }
72
+ @g.players[9].should_receive(:act).once.and_return(:aggressive)
73
+ @g.run
74
+ @g.winners[0].should == @g.players[9]
75
+ end
76
+
77
+ it "should go straight to the showdown if one player is left" do
78
+ @g.players[0..8].each { |p| p.should_receive(:act).at_least(1).and_return(:weak) }
79
+ @g.players[9].should_receive(:act).once.and_return(:aggressive)
80
+ @g.run
81
+ @g.round.should == :showdown
82
+ end
83
+ end
84
+
85
+ describe Game, "accepting customized players" do
86
+ module Holdem
87
+ class CustomPlayer < Holdem::Player
88
+ attr_reader :position
89
+
90
+ def initialize(position=nil)
91
+ super() # should call this to init variables, need () so no args are passed
92
+ @position = position
93
+ end
94
+ end
95
+ end
96
+
97
+ it "should allow the Player class to be overridden" do
98
+ @g = Game.new(5.0) do
99
+ CustomPlayer.new
100
+ end
101
+ @g.players.each { |p| p.is_a?(CustomPlayer).should be_true }
102
+ end
103
+
104
+ it "should yield the current player's position if there is a block" do
105
+ @g = Game.new(5.0) do |position|
106
+ CustomPlayer.new(position)
107
+ end
108
+ (0..9).each do |i|
109
+ @g.players[i].position.should == i
110
+ end
111
+ end
112
+ end
113
+
114
+ describe Game, "determining when action settled" do
115
+ before(:each) do
116
+ @g = Game.new(5.0)
117
+ end
118
+
119
+ it "should know how much a player owes to continue" do
120
+ @g.players[2..9].each { |p| @g.owed(p).should == 5.0 }
121
+ @g.owed(@g.players[0]).should == 2.5
122
+ @g.owed(@g.players[1]).should == 0.0
123
+ end
124
+
125
+ it "should report betting as settled when all players have acted and there are no open bets" do
126
+ @g.players.each { |p| p.should_receive(:act).once.with(@g).and_return(:neutral) }
127
+ @g.run
128
+ @g.settled?.should be_true
129
+ end
130
+
131
+ it "should report 0.0 owed for folded players" do
132
+ @g.owed(@g.players[5]).should == 5.0
133
+ @g.players[5].fold
134
+ @g.owed(@g.players[5]).should == 0.0
135
+ end
136
+ end
137
+
138
+ describe Game, "player actions" do
139
+ before(:each) do
140
+ @g = Game.new(5.0)
141
+ end
142
+
143
+ it "should take amount owed plus bet size when betting" do
144
+ @g.players[0..8].each { |p| p.should_receive(:act).at_least(1).and_return(:neutral) }
145
+ @g.players[9].should_receive(:act).once.and_return(:aggressive)
146
+ @g.run
147
+ @g.pot.should == 100.0
148
+ end
149
+
150
+ it "should ask each player to act in a round" do
151
+ @g.players.each { |p| p.should_receive(:act).once.with(@g).and_return(:neutral) }
152
+ @g.run
153
+ end
154
+
155
+ it "should increment the bets in round when a bet / raise is made" do
156
+ @g.players[4].should_receive(:act).once.and_return(:aggressive)
157
+ [(0..3), (5..9)].each do |range|
158
+ @g.players[range].each { |p| p.should_receive(:act).at_least(1).and_return(:neutral) }
159
+ end
160
+
161
+ @g.run
162
+ @g.bets_in_round.should == 2
163
+ end
164
+
165
+ it "should stop the round when calling a raiser" do
166
+ @g.players[4].should_receive(:act).once.and_return(:aggressive)
167
+ [(0..3), (5..9)].each do |range|
168
+ @g.players[range].each { |p| p.should_receive(:act).at_least(1).and_return(:neutral) }
169
+ end
170
+ @g.run
171
+ @g.pot.should == 100.0
172
+ end
173
+
174
+ it "should not increment the bets in round if they are capped" do
175
+ @g.players.each { |p| p.should_receive(:act).at_least(1).and_return(:aggressive) }
176
+ @g.run
177
+ @g.bets_in_round.should == 4
178
+ end
179
+
180
+ it "should record a players 'true' action" do
181
+ @g.players.each { |p| p.should_receive(:act).at_least(1).and_return(:aggressive) }
182
+ @g.run
183
+ @g.players[8].actions.should_not include(:aggressive)
184
+ end
185
+
186
+ it "should skip a player if they've folded" do
187
+ @g.players[5].fold
188
+ [(0..4), (6..9)].each do |range|
189
+ @g.players[range].each { |p| p.should_receive(:act).once.and_return(:neutral) }
190
+ end
191
+ @g.players[5].should_not_receive(:act)
192
+
193
+ @g.run
194
+ end
195
+
196
+ it "should not allow a check if a bet is needed" do
197
+ @g.players.each { |p| p.should_receive(:act).once.and_return(:neutral) }
198
+ @g.run
199
+
200
+ @g.pot.should == 50.0
201
+ end
202
+
203
+ it "should fold for a weak action if a call is required" do
204
+ @g.players[4].should_receive(:act).once.and_return(:weak)
205
+ @g.run
206
+ @g.players[4].folded.should be_true
207
+ end
208
+
209
+ it "should raise an error if an invalid action is given" do
210
+ @g.players[0].should_receive(:act).once.and_return(:fugazi)
211
+ lambda { @g.run }.should raise_error
212
+ end
213
+
214
+ it "should run an entire game if run_game is called" do
215
+ @g.players.each { |p| p.should_receive(:act).exactly(4).and_return(:neutral) }
216
+ @g.run_game
217
+ @g.round.should == :showdown
218
+ end
219
+
220
+ it "should select the winners" do
221
+ @g.run_game
222
+ (@g.winners.length >= 1).should be_true
223
+ end
224
+ end
225
+
226
+ describe Game, "pre-flop round" do
227
+ before(:each) do
228
+ @g = Game.new(5.0)
229
+ end
230
+
231
+ it "should set the bet size correctly for first round" do
232
+ @g.bet_size.should == 5.0
233
+ end
234
+
235
+ it "should deal two hole cards to each player" do
236
+ @g.players.each { |p| p.hole_cards.length.should == 2 }
237
+ end
238
+
239
+ it "should start with the player after the big blind and move around" do
240
+ (2..5).each do |i|
241
+ @g.players[i].should_receive(:act).once.and_return(:neutral)
242
+ end
243
+
244
+ @g.players[6].should_receive(:act).and_raise('Fake Error')
245
+
246
+ [7, 8, 9, 0, 1].each do |i|
247
+ @g.players[i].should_not_receive(:act)
248
+ end
249
+
250
+ lambda { @g.run }.should raise_error
251
+ end
252
+
253
+ it "should allow up to 3 raises" do
254
+ @g.players.each { |p| p.should_receive(:act).at_least(1).and_return(:aggressive) }
255
+ @g.run
256
+ @g.pot.should == 200.0
257
+ end
258
+
259
+ it "should have the player after big blind act first" do
260
+ @g.should_receive(:do_betting).with(2)
261
+ @g.run
262
+ end
263
+
264
+ it "should allow the big blind the option in the pre-flop stage rather than just settling" do
265
+ [(0..0), (2..9)].each do |range|
266
+ @g.players[range].each { |p| p.should_receive(:act).twice.and_return(:neutral) }
267
+ end
268
+ @g.players[1].should_receive(:act).once.and_return(:aggressive)
269
+ @g.run
270
+ @g.pot.should == 100.0
271
+ end
272
+
273
+ it "should calculate bet size correctly for small blind when first to raise" do
274
+ @g.players[1..9].each { |p| p.should_receive(:act).at_least(1).and_return(:neutral) }
275
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
276
+ @g.run
277
+ @g.pot.should == 100.0
278
+ end
279
+
280
+ it "should increment the round to flop once finished" do
281
+ @g.run
282
+ @g.round.should == :flop
283
+ end
284
+ end
285
+
286
+ describe Game, "flop round" do
287
+ before(:each) do
288
+ @g = Game.new(5.0)
289
+ @g.run
290
+ end
291
+
292
+ it "should burn a card before betting starts" do
293
+ @g.should_receive(:burn).once
294
+ @g.run
295
+ end
296
+
297
+ it "should deal 3 cards to the board before betting starts" do
298
+ @g.run
299
+ @g.board.length.should == 3
300
+ end
301
+
302
+ it "should let players find their best hand" do
303
+ @g.players.each { |p| p.should_receive(:find_hand).once.with(@g.board) }
304
+ @g.run
305
+ end
306
+
307
+ it "should have 0 bets in this round before doing betting" do
308
+ @g.run
309
+ @g.bets_in_round.should == 0 #checking is default action
310
+ end
311
+
312
+ it "should reset players amount in round to zero before doing betting" do
313
+ @g.run
314
+ @g.players.each { |p| p.in_round.should == 0.0 }
315
+ end
316
+
317
+ it "should not reset players amount in pot to zero before round" do
318
+ @g.players.each { |p| p.in_pot.should == 5.0 }
319
+ end
320
+
321
+ it "should allow 1 bet and 3 raises" do
322
+ @g.players.each { |p| p.should_receive(:act).at_least(1).and_return(:aggressive) }
323
+ @g.run
324
+ @g.pot.should == 250.0
325
+ end
326
+
327
+ it "should have the player in the small blind act first" do
328
+ @g.should_receive(:do_betting).with(0)
329
+ @g.run
330
+ end
331
+
332
+ it "should have a bet size equal to the big blind" do
333
+ @g.bet_size.should == 5.0
334
+ end
335
+
336
+ it "should increment the round to turn when finished" do
337
+ @g.run
338
+ @g.round.should == :turn
339
+ end
340
+ end
341
+
342
+ describe Game, "turn round" do
343
+ before(:each) do
344
+ @g = Game.new(5.0)
345
+ @g.run
346
+ @g.run
347
+ end
348
+
349
+ it "should burn a card before betting starts" do
350
+ @g.should_receive(:burn).once
351
+ @g.run
352
+ end
353
+
354
+ it "should deal 1 card to the board before betting starts" do
355
+ @g.run
356
+ @g.board.length.should == 4
357
+ end
358
+
359
+ it "should let players find their best hand" do
360
+ @g.players.each { |p| p.should_receive(:find_hand).once.with(@g.board) }
361
+ @g.run
362
+ end
363
+
364
+ it "should have 0 bets in this round before doing betting" do
365
+ @g.run
366
+ @g.bets_in_round.should == 0 #checking is default action
367
+ end
368
+
369
+ it "should reset players amount in round to zero before doing betting" do
370
+ @g.run
371
+ @g.players.each { |p| p.in_round.should == 0.0 }
372
+ end
373
+
374
+ it "should not reset players amount in pot to zero before round" do
375
+ @g.players.each { |p| p.in_pot.should == 5.0 }
376
+ end
377
+
378
+ it "should allow 1 bet and 3 raises" do
379
+ @g.players.each { |p| p.should_receive(:act).at_least(1).and_return(:aggressive) }
380
+ @g.run
381
+ @g.pot.should == 450.0
382
+ end
383
+
384
+ it "should have the player in the small blind act first" do
385
+ @g.should_receive(:do_betting).with(0)
386
+ @g.run
387
+ end
388
+
389
+ it "should have a bet size equal to double the big blind" do
390
+ @g.bet_size.should == 10.0
391
+ end
392
+
393
+ it "should increment the round to river when finished" do
394
+ @g.run
395
+ @g.round.should == :river
396
+ end
397
+ end
398
+
399
+ describe Game, "river round" do
400
+ before(:each) do
401
+ @g = Game.new(5.0)
402
+ @g.run
403
+ @g.run
404
+ @g.run
405
+ end
406
+
407
+ it "should burn a card before betting starts" do
408
+ @g.should_receive(:burn).once
409
+ @g.run
410
+ end
411
+
412
+ it "should deal 1 card to the board before betting starts" do
413
+ @g.run
414
+ @g.board.length.should == 5
415
+ end
416
+
417
+ it "should let players find their best hand" do
418
+ @g.players.each { |p| p.should_receive(:find_hand).once.with(@g.board) }
419
+ @g.run
420
+ end
421
+
422
+ it "should have 0 bets in this round before doing betting" do
423
+ @g.run
424
+ @g.bets_in_round.should == 0 #checking is default action
425
+ end
426
+
427
+ it "should reset players amount in round to zero before doing betting" do
428
+ @g.run
429
+ @g.players.each { |p| p.in_round.should == 0.0 }
430
+ end
431
+
432
+ it "should not reset players amount in pot to zero before round" do
433
+ @g.players.each { |p| p.in_pot.should == 5.0 }
434
+ end
435
+
436
+ it "should allow 1 bet and 3 raises" do
437
+ @g.players.each { |p| p.should_receive(:act).at_least(1).and_return(:aggressive) }
438
+ @g.run
439
+ @g.pot.should == 450.0
440
+ end
441
+
442
+ it "should have the player in the small blind act first" do
443
+ @g.should_receive(:do_betting).with(0)
444
+ @g.run
445
+ end
446
+
447
+ it "should have a bet size equal to double the big blind" do
448
+ @g.bet_size.should == 10.0
449
+ end
450
+
451
+ it "should increment the round to showdown when finished" do
452
+ @g.run
453
+ @g.round.should == :showdown
454
+ end
455
+ end
456
+
457
+ describe Game, "showdown round" do
458
+
459
+ before(:each) do
460
+ @g = Game.new(5.0)
461
+ @g.run
462
+ @g.run
463
+ @g.run
464
+ end
465
+
466
+ it "should not allow a folded player to win" do
467
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
468
+ @g.players[1..9].each { |p| p.should_receive(:act).once.and_return(:weak) }
469
+ @g.players[1..9].each { |p| p.should_not_receive(:hand) }
470
+
471
+ @g.run
472
+ @g.winners.should == [ @g.players[0] ]
473
+ end
474
+
475
+ it "should pick the player with the highest hand" do
476
+ @g.run
477
+ sorted_players = @g.players.sort { |a,b| a.hand <=> b.hand }
478
+ @g.winners.should include(sorted_players[9])
479
+ end
480
+
481
+ it "should allow there to be multiple winners" do
482
+ winning_hand = Hand.new('As Ks Qs Js Ts')
483
+ @g.players[0].should_receive(:hand).at_least(1).and_return(winning_hand)
484
+ @g.players[1].should_receive(:hand).at_least(1).and_return(winning_hand)
485
+
486
+ @g.run
487
+ @g.winners.should include(@g.players[0])
488
+ @g.winners.should include(@g.players[1])
489
+ end
490
+
491
+ it "should award the entire pot to the winner" do
492
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
493
+ @g.players[1..9].each { |p| p.should_receive(:act).once.and_return(:weak) }
494
+
495
+ @g.run
496
+ @g.players[0].delta.should == (@g.pot - @g.players[0].in_pot)
497
+ end
498
+
499
+ it "should split the pot evenly if there are multiple winners" do
500
+ winning_hand = Hand.new('As Ks Qs Js Ts')
501
+ @g.players[0].should_receive(:hand).at_least(1).and_return(winning_hand)
502
+ @g.players[1].should_receive(:hand).at_least(1).and_return(winning_hand)
503
+
504
+ @g.run
505
+ num_winners = @g.winners.length # in case someone is delt a royal flush
506
+ @g.players[0].delta.should == ( (@g.pot/num_winners) - @g.players[0].in_pot)
507
+ @g.players[1].delta.should == ( (@g.pot/num_winners) - @g.players[1].in_pot)
508
+ end
509
+
510
+ it "should not award anything to players without winning hands" do
511
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
512
+ @g.players[1..9].each { |p| p.should_receive(:act).once.and_return(:weak) }
513
+
514
+ @g.run
515
+ @g.players[1..9].each { |p| p.delta.should == (0-p.in_pot)}
516
+ end
517
+ end
518
+
519
+ describe Game, "heads-up play" do
520
+
521
+ before(:each) do
522
+ @g = Game.new(5.0, 2)
523
+ end
524
+
525
+ it "should have the dealer post the small blind" do
526
+ @g.players[1].in_round.should == 2.5
527
+ end
528
+
529
+ it "should have the non-dealer post the big blind" do
530
+ @g.players[0].in_round.should == 5.0
531
+ end
532
+
533
+ it "should have the dealer act first pre-flop" do
534
+ @g.players[1].should_receive(:act).and_return(:weak)
535
+ @g.players[0].should_not_receive(:act).and_return(:neutral)
536
+ @g.run
537
+ end
538
+
539
+ it "should have the non-dealer act first on the flop" do
540
+ @g.run
541
+
542
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
543
+ @g.players[1].should_receive(:act).once.and_return(:weak)
544
+ @g.run
545
+ end
546
+
547
+ it "should have the non-dealer act first on the turn" do
548
+ @g.run
549
+ @g.run
550
+
551
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
552
+ @g.players[1].should_receive(:act).once.and_return(:weak)
553
+ @g.run
554
+ end
555
+
556
+ it "should have the non-dealer act first on the river" do
557
+ @g.run
558
+ @g.run
559
+ @g.run
560
+
561
+ @g.players[0].should_receive(:act).once.and_return(:aggressive)
562
+ @g.players[1].should_receive(:act).once.and_return(:weak)
563
+ @g.run
564
+ end
565
+ end