rora 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -33,7 +33,7 @@ A complete deck of 52 cards will contain exactly thirteen ranks - numerical valu
33
33
  ## Cards
34
34
 
35
35
  ### Overview
36
- A playing card is constructed with one suit and one rank. A card can be created in by using the Suit and Rank classes as described above, or their equivalent character values.
36
+ A playing card is constructed with exactly one suit and one rank. A card can be created by using the Suit and Rank classes as described above, or their equivalent character values.
37
37
 
38
38
  # Example 1: Creating a card with Rank and Suit instances.
39
39
  card = Card.new Rank::ACE, Suit::SPADE
@@ -46,7 +46,7 @@ A playing card is constructed with one suit and one rank. A card can be created
46
46
 
47
47
  ### Creating multiple cards at once
48
48
 
49
- You can create multiple cards at once by using the to_cards class method. The to_cards takes a series suit-rank characters seperated by commas or white spaces and returns an Array of Cards.
49
+ You can create multiple cards at once by using the to_cards class method. The to_cards takes a series suit-rank characters seperated by commas or white spaces and returns an array of cards.
50
50
 
51
51
  # Example 1: Create an array of cards by providing a comma-seperated string.
52
52
  cards = Card.to_cards "AS,KS,QS,JS"
@@ -60,7 +60,7 @@ You can create multiple cards at once by using the to_cards class method. The to
60
60
  ## Hands
61
61
 
62
62
  ### Overview
63
- A hand consists of exactly five cards, and can be constructed by providing an Array of cards or an equivalent sequence of suit-rank character values.
63
+ A hand consists of exactly five cards, and can be constructed by providing an array of cards or an equivalent sequence of suit-rank character values.
64
64
 
65
65
  # Example 1: Creating a hand with a comma-seperated string.
66
66
  hand = Hand.new "AS,KS,QS,JS,TS"
@@ -71,7 +71,7 @@ A hand consists of exactly five cards, and can be constructed by providing an Ar
71
71
  # Example 3: Creating a hand with an Array of cards.
72
72
  hand = Hand.new [Card.new("AS"), Card.new("KS"), Card.new("QS"), Card.new("JS"), Card.new("TS")]
73
73
 
74
- # Example 4: Creating a hand with an Array of cards, using Card.to_cards
74
+ # Example 4: Creating a hand with an array of cards, using Card.to_cards
75
75
  hand = Hand.new Card.to_cards("AS,KS,QS,JS,TS")
76
76
 
77
77
  ### Hand Score
@@ -92,7 +92,7 @@ Each hand in poker has a rank, or score. While many software libraries provide t
92
92
  puts seven_high.name => 'High Card'
93
93
  puts seven_high.probability => 0.50 // Probability of receiving any High Card
94
94
 
95
- ### Hand Detection
95
+ ### Hand Type Detection
96
96
 
97
97
  You can query a hand to determine what kind of hand it is.
98
98
 
@@ -111,13 +111,20 @@ Hand type detection extends to the following methods:
111
111
  - two_pair?
112
112
  - one_pair?
113
113
  - high_card?
114
+
115
+ ### Hand Type Probability
116
+
117
+ You can query a hand to determine the probability of drawing that type of hand.
118
+
119
+ # Example 1: Determine the probability of a Royal Flush
120
+ puts Hand.new("AS,KS,QS,JS,TS").probability => 0.0015
114
121
 
115
122
  ## Starting Hands
116
123
 
117
124
  Beyond the code samples presented below, the following links provide more information about [texas hold'em starting hands](http://www.moralesce.com/2012/01/21/holdem-starting-hands/) and [poker hand evaluation](http://www.moralesce.com/2011/11/26/poker-hand-evaluation/).
118
125
 
119
126
  ### Overview
120
- A starting hand is also referred to as a players pocket cards, or hole cards. A starting hand consists of exactly two cards held by one player, unseen by opponents. Starting hands can be created by proiding an Array of Cards of an equivalent sequence of suit-rank character values.
127
+ A starting hand is also referred to as a player's pocket cards, or hole cards. A starting hand consists of exactly two cards, and can be constructed by providing an array of cards or an equivalent sequence of suit-rank character values.
121
128
 
122
129
  # Example 1: Creating a starting hand with a comma-seperated string.
123
130
  starting_hand = StartingHand.new "AS,KS"
@@ -133,7 +140,9 @@ A starting hand is also referred to as a players pocket cards, or hole cards. A
133
140
 
134
141
  ### Creating an array of all starting hands
135
142
 
136
- # Returns an array of all 1324 starting hands.
143
+ Given a deck of 52 cards, choosing any two card yields 1326 distinct 2 card combinations
144
+
145
+ # Returns an array of all 1326 starting hands.
137
146
  all = StartingHand.all_starting_hands
138
147
 
139
148
 
@@ -188,27 +197,35 @@ The remove method takes a variety of arguments.
188
197
  deck.remove starting_hand
189
198
 
190
199
  ### Inspecting the Deck
191
- You can query the deck to determine whether it contains a specific card or at least one card in a group.
192
-
193
- # Determines if the Ace of Spades is in the deck.
194
- deck.contains Card.new("AS")
200
+ You can query the deck to determine whether it contains a specific card or at least one card in a group.
195
201
 
196
202
  # Determines if the Ace of Spades is in the deck.
197
203
  deck.contains "AS"
198
204
 
199
- # Determines if any Ace is in the deck.
200
- deck.contains [Card.new("AS"), Card.new("AH"), Card.new("AD"), Card.new("AC")]
205
+ # Determines if the Ace of Spades is in the deck.
206
+ deck.contains Card.new("AS")
201
207
 
202
208
  # Determines if any Ace is in the deck.
203
- deck.contains "AS,AH,AD,AC"
209
+ deck.contains_any "AS,AH,AD,AC"
210
+
211
+ # Determines if any Ace is in the deck.
212
+ deck.contains_any [Card.new("AS"), Card.new("AH"), Card.new("AD"), Card.new("AC")]
213
+
214
+ # Determines if there are any cards in the deck
215
+ deck.empty?
216
+
217
+ # Returns the number of Aces left in the deck
218
+ deck.count_cards_with_rank Rank::ACE
219
+
220
+ # Returns the number of Clubs left in the deck
221
+ deck.count_cards_with_suit Suit::CLUB
204
222
 
205
223
 
206
224
  ### Combinations
207
225
  The combination method allows you to enumerate through card subsets. Given a combination value greater than 1, the method will return a two-dimensional array.
208
226
 
209
-
210
227
  # Chooses every possible 2 card combination from the deck. Assuming the deck contains 52
211
- # cards, this example will return 1324 2-card combinations.
228
+ # cards, this example will return 1326 2-card combinations.
212
229
  cards = deck.combination 2
213
230
 
214
231
  # Chooses every possible 5 card combination from the deck. Assuming the deck contains 52
@@ -218,7 +235,7 @@ The combination method allows you to enumerate through card subsets. Given a com
218
235
  ## Boards
219
236
 
220
237
  ### Overview
221
- A board represents the logical table area where community cards are dealt. A board can be setup with either 0, 3, 4 or 5 cards to represent and empty board, the flop, turn, or river (respectively).
238
+ A board represents the logical table area where community cards are dealt. A board can be setup with either 0, 3, 4 or 5 unique cards to represent and empty board, the flop, turn, or river (respectively).
222
239
 
223
240
  # Creates an empty board.
224
241
  board = Board.new
@@ -233,7 +250,7 @@ A board represents the logical table area where community cards are dealt. A boa
233
250
  board = Board.new "KS,QS,7H,4C,3H"
234
251
 
235
252
  ### Subsequent Betting Rounds
236
- An empty board can be populated afterwads.
253
+ An empty board can be populated after constrution as well - indeed, this is the most common usage.
237
254
 
238
255
  board = Board.new
239
256
  board.flop = "AS,KS,QS"
@@ -252,7 +269,180 @@ You can query the board to determine whether it contains a specific card or at l
252
269
  board.contains "AS"
253
270
 
254
271
  # Determines if any Ace is on the board.
255
- board.contains [Card.new("AS"), Card.new("AH"), Card.new("AD"), Card.new("AC")]
272
+ board.contains_any [Card.new("AS"), Card.new("AH"), Card.new("AD"), Card.new("AC")]
256
273
 
257
274
  # Determines if any Ace is on the board.
258
- board.contains "AS,AH,AD,AC"
275
+ board.contains_any "AS,AH,AD,AC"
276
+
277
+ # You can query to the board for flop, turn and river cards.
278
+ puts board.flop => '2H', '3C', 'JH'
279
+ puts board.turn => 'AS'
280
+ puts board.river => 'JS'
281
+
282
+ # You can query the board to return all cards on the board
283
+ puts board.cards => '2H', '3C', 'JH', 'AS', 'JS'
284
+
285
+ ## Pots
286
+
287
+ A pot represents the sum of money that players compete for during a hand of poker.
288
+
289
+ # Creates a new pot.
290
+ pot = Pot.new
291
+
292
+ ### Adding Money to the Pot
293
+
294
+ You can add positive sums of money to the pot. At the moment the pot assumes only one kind of (implicit) currency.
295
+
296
+ # Add money to the pot
297
+ pot.add(5)
298
+ puts pot.value => '5'
299
+
300
+ # Chain add operations
301
+ pot.add(5).add(5).add(5)
302
+ puts pot.value => '15'
303
+
304
+ # Add decimal values to the pot
305
+ pot.add(2.50).add(1.25)
306
+ puts pot.value => '3.75'
307
+
308
+ # The to_s representation returns formatted monetary amount
309
+ pot.add(300)
310
+ puts pot.to_s => '$300.00'
311
+
312
+ ## Tables
313
+
314
+ A poker table where players compete for pots. A newly created table will have one deck of 52 cards, one pot with zero dollars and one empty board. You can specify the number of seats at the table - if not provided, a table will be created with 9 seats. A table must have a minimum of 2 seats.
315
+
316
+ # Creates a table with 9 seats
317
+ table = Table.new
318
+
319
+ # Creates a table with 6 seats
320
+ table = Table.new 6
321
+
322
+ ### Swapping pots, boards and decks
323
+
324
+ You can provide a table with different decks, boards and pots. This configuration is chainable, as shown in the example below:
325
+
326
+ deck = Deck.new
327
+ pot = Pot.new
328
+ board = Board.new
329
+
330
+ # Creates a table, and configures the table to use the specified board, pot and deck.
331
+ table = Table.new
332
+ table.with(deck).with(board).with(pot)
333
+
334
+ ### Managing players
335
+ A poker table won't do us any good unless we have a few players sitting in on a game. We can add players to the table using the add method:
336
+
337
+ table = Table.new
338
+ james = Player.new("James")
339
+ sally = Player.new("Sally")
340
+
341
+ # james will automatcially take seat number 1
342
+ table.add james
343
+
344
+ # sally will automatcially take seat number 2
345
+ table.add sally
346
+
347
+ At a real poker table, players are free to sit at any available seat at the table. You can specify which seat a player sits at:
348
+
349
+ table = Table.new
350
+ james = Player.new("James")
351
+ sally = Player.new("Sally")
352
+
353
+ # james will sit at seat number 2
354
+ table.add james 2
355
+
356
+ # sally will sit at seat number 6
357
+ table.add sally 6
358
+
359
+ This index is not zero based. In other words, seat 1 is the first seat, you cannot specify to sit at 'seat 0', there's no such thing! If the specified seat is already taken by another player an exception is raised. If the specified seat doesn't exist (you specify to seat a player at seat 11 at a 9-seated table) and exception will be raised.
360
+
361
+ You can remove players by using the tables' remove method:
362
+
363
+ # Create a table and a couple of players
364
+ table = Table.new
365
+ james = Player.new("James")
366
+ sally = Player.new("Sally")
367
+
368
+ # james will automatcially take seat number 1
369
+ table.add james
370
+
371
+ # james will be removed, seat number 1 will be free
372
+ table.remove james
373
+
374
+ ### Inspecting the table
375
+
376
+ # Returns the number of seats at the table
377
+ table.size
378
+
379
+ # Determines if all of the seats at the table are occupied
380
+ table.full?
381
+
382
+ # Determines if none of the seats at the table are occupied
383
+ table.empty?
384
+
385
+ # Returns all players sitting at the table as an array
386
+ table.players
387
+
388
+ # Returns seat number 3
389
+ third_seat = table.seat(3)
390
+
391
+ ### Poker positions at the table
392
+
393
+ There are a few positions that are relevant in a poker game, and the rora api makes it trivial to identify key positions. The dealer, or 'button' is a rotating position - after every hand a new dealer is chosen by moving the 'button' or 'puck' in a clockwise fashion around the table.
394
+
395
+ # Returns the seat with the button.
396
+ table.the_button
397
+
398
+ The small blind sits to the immediate left of the dealer, and is required to post one half sized bet before before a hand begins.
399
+
400
+ # Returns the seat with the small blind.
401
+ table.the_small_blind
402
+
403
+ # In a heads up (2-player) game, the small blind is also the button!
404
+ table = Table.new
405
+ james = Player.new("James")
406
+ sally = Player.new("Sally")
407
+
408
+ puts table.the_button.player.name => 'James'
409
+ puts table.the_small_blind.player.name => 'James'
410
+ puts table.the_big_blind.player.name => 'Sally'
411
+
412
+ The big blind sits to the immediate left of the small blind, and must post one full sized bet before a hand begins.
413
+
414
+ # Returns the seat with the big blind.
415
+ table.the_big_blind
416
+
417
+ UTG sits to the immediate left of the big blind, and is the first player to act in the pre-flop (i.e. first) betting round. The player to the left the big blind is always the first player to act in the preflop betting round. In a heads up (i.e. 2-player game), the 'next' player is our other player, the small blind.
418
+
419
+ # Returns the seat that is first to act, or 'under the gun'
420
+ table.under_the_gun
421
+
422
+ # In a heads up (2-player) game, the small blind is also the first to act (under the gun)!
423
+ table = Table.new
424
+ james = Player.new("James")
425
+ sally = Player.new("Sally")
426
+
427
+ puts table.the_small_blind.player.name => 'James'
428
+ puts table.the_big_blind.player.name => 'Sally'
429
+ puts table.under_the_gun.player.name => 'James'
430
+
431
+
432
+ ### Gaps in player positions
433
+ There is no need for players to sit directly beside each other. Rora maintains an internal linked list of players and their relative positions, so there can be gaps between seated players.
434
+
435
+ # Create a table with three players
436
+ table = Table.new
437
+ james = Player.new("James")
438
+ sally = Player.new("Sally")
439
+ frank = Player.new("Frank")
440
+
441
+ # Seats 1, 3, 4, 5 and 7 will be unoccupied, that's ok!
442
+ table.add james 2
443
+ table.add sally 6
444
+ table.add frank 8
445
+
446
+ puts table.the_small_blind.player.name => 'James'
447
+ puts table.the_big_blind.player.name => 'Sally'
448
+ puts table.under_the_gun.player.name => 'Frank'
@@ -65,6 +65,12 @@ class Board
65
65
  @river = cd
66
66
  end
67
67
 
68
+ # Returns the number of community cards that have been dealt.
69
+ def size
70
+ cards.size
71
+ end
72
+
73
+ # Returns the community cards that have been dealt
68
74
  def cards
69
75
  cds = Array.new
70
76
  cds += @flop if !@flop.nil?
@@ -92,4 +98,8 @@ class Board
92
98
  false
93
99
  end
94
100
 
101
+ def to_s
102
+ "Board: flop=#{@flop.map { |card| "#{card.key}" }.join(",")}"
103
+ end
104
+
95
105
  end
@@ -37,10 +37,6 @@ class Card
37
37
  @rank.key + @suit.key
38
38
  end
39
39
 
40
- def value
41
- @rank.value + @suit.value
42
- end
43
-
44
40
  def name
45
41
  "#{@rank.value} of #{@suit.value}s"
46
42
  end
@@ -68,7 +64,7 @@ class Card
68
64
  end
69
65
 
70
66
  def to_s
71
- "Card: name='#{name}' value='#{@rank.key}#{@suit.key}' id=#{id}"
67
+ "Card: #{name}"
72
68
  end
73
69
 
74
70
  end
@@ -1,8 +1,76 @@
1
1
  class Game
2
- attr_reader :table, :players, :dealer
2
+ attr_reader :table, :buy_in, :started
3
3
 
4
- def initialize
4
+ def initialize arguments=nil
5
+ @table = arguments.nil? ? Table.new : (arguments[:table].nil? ? Table.new : arguments[:table])
6
+ @buy_in = arguments.nil? ? 50 : (arguments[:buy_in].nil ? 20 : arguments[:buy_in])
7
+ @log = GameLogger.new
8
+ @started = false
9
+ end
10
+
11
+ def start
12
+ @started = true
13
+ end
14
+
15
+ def add player
16
+ raise ArgumentError, "#{player.name} cannot join game, stack of #{player.stack} is less than the buy in amount #{@buy_in}" if player.stack < @buy_in
17
+ raise ArgumentError, "#{player.name} cannot join game, the game has already started" if @started
18
+ player.join_game self
19
+ end
20
+
21
+ def remove player
22
+ player.leave_game
23
+ end
24
+
25
+ def pot
26
+ @table.pot
27
+ end
28
+
29
+ def shuffle_deck
30
+ @log.debug("Shuffling the deck")
31
+ @table.deck.shuffle
32
+ end
33
+
34
+ def assign_button
35
+ @table.pass_the_buck
36
+ @log.info("#{@table.the_button.player.name} will be the dealer for this hand");
37
+ end
38
+
39
+ def post_small_blind
40
+ @table.the_small_blind.player.post_small_blind
41
+ @log.info("#{@table.the_small_blind.player.name} posts the small blind");
42
+ end
43
+
44
+ def post_big_blind
45
+ @table.the_big_blind.player.post_big_blind
46
+ @log.info("#{@table.the_big_blind.player.name} posts the big blind");
47
+ @log.info("The pot is at #{@table.pot.value}");
48
+ end
49
+
50
+ def deal_pocket_cards
51
+ @table.players.each do |player|
52
+ player.add table.deck.deal
53
+ end
54
+ @table.players.each do |player|
55
+ player.add table.deck.deal
56
+ end
57
+ end
58
+
59
+ def place_bet amount
60
+ @table.pot.add amount
61
+ end
62
+
63
+ def run_preflop_betting_round
64
+ @log.info("-----------------------------------------------------------------")
65
+ first_to_act = @table.under_the_gun
66
+ first_to_act.player.act
67
+ number = first_to_act.number
68
+ seat_number = @table.the_seat_after(first_to_act).number
5
69
 
70
+ while seat_number != number
71
+ @table.seat(seat_number).player.act
72
+ seat_number = @table.the_seat_after(@table.seat(seat_number)).number
73
+ end
6
74
  end
7
75
 
8
76
  end
@@ -10,12 +10,11 @@
10
10
  # hand = Hand.new("ACKCJS9H4H")
11
11
  #
12
12
  class Hand
13
- attr_reader :cards, :hand_repository
13
+ attr_reader :cards
14
14
 
15
15
  def initialize cards
16
16
  @hand_repository = HandRepository.instance
17
17
  @cards = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
18
-
19
18
  raise ArgumentError, "Exactly 5 cards are required to create a hand, #{cards.size} provided" if @cards.size != 5
20
19
  raise ArgumentError, "The hand contains duplicate cards" if @cards.uniq.length != @cards.length
21
20
  end
@@ -1,8 +1,73 @@
1
- # To change this template, choose Tools | Templates
2
- # and open the template in the editor.
3
-
1
+ #
2
+ # A player in a Texas Hold'em game.
3
+ #
4
4
  class Player
5
- def initialize
6
-
5
+ attr_reader :name, :stack, :game
6
+
7
+ def initialize name, stack
8
+ raise ArgumentError, "The player name cannot be nil" if name.nil?
9
+ @name = name
10
+ @stack = stack.nil? ? 0 : stack
11
+ @sitting_out = true
12
+ @table = nil
13
+ @cards = []
14
+ @log = GameLogger.new
15
+ end
16
+
17
+ def join_game game, seat=nil
18
+ raise ArgumentError, "#{@name} cannot join this game, the table is full" if game.table.full?
19
+ raise ArgumentError, "#{@name} cannot join this game, this player is already playing another game" if !@game.nil?
20
+ game.table.add self, seat
21
+ @game = game
22
+ end
23
+
24
+ def leave_game
25
+ end_session
26
+ @game = nil
27
+ end
28
+
29
+ def start_session
30
+ raise ArgumentError, "#{@name} is not sitting at a table" if @game.nil?
31
+ @sitting_out = false
32
+ end
33
+
34
+ def end_session
35
+ @sitting_out = true
36
+ end
37
+
38
+ def sitting_out?
39
+ @sitting_out
40
+ end
41
+
42
+ def add card
43
+ @cards << card
44
+ end
45
+
46
+ def starting_hand
47
+ StartingHand.new(@cards)
48
+ end
49
+
50
+ def post_small_blind
51
+ raise_error_if_sitting_out "cannot post the small blind"
52
+ @game.place_bet @game.buy_in / 200.00
53
+ end
54
+
55
+ def post_big_blind
56
+ raise_error_if_sitting_out "cannot post the big blind"
57
+ @game.place_bet @game.buy_in / 100.00
7
58
  end
59
+
60
+ def act
61
+ raise_error_if_sitting_out "cannot act"
62
+ action = "folds"
63
+ @log.info("#{name} #{action}")
64
+ end
65
+
66
+ private
67
+
68
+ def raise_error_if_sitting_out message
69
+ raise ArgumentError, "#{name} has not joined a game, #{message}" if game.nil?
70
+ raise ArgumentError, "#{name} is sitting out, #{message}" if sitting_out?
71
+ end
72
+
8
73
  end
@@ -2,20 +2,21 @@
2
2
  # The sum of money that players wager during a single hand or game.
3
3
  #
4
4
  class Pot
5
-
6
- attr_reader :total
5
+ attr_reader :value
7
6
 
8
7
  def initialize
9
- @total = 0
8
+ @value = 0
10
9
  end
11
-
10
+
11
+ # Adds the specified bet to the pot
12
12
  def add bet
13
- @total += bet
13
+ raise ArgumentError if bet < 0
14
+ @value += bet
14
15
  self
15
16
  end
16
17
 
17
18
  def to_s
18
- "Pot: " + sprintf("$%.02f" , @total)
19
+ "Pot: " + sprintf("$%.02f" , @value)
19
20
  end
20
21
 
21
22
  end
@@ -25,11 +25,11 @@ class Rank
25
25
  self.values.each do |rank|
26
26
  return rank if rank.key.casecmp(key) == 0
27
27
  end
28
- raise ArgumentError, "No rank exists for key " + key
28
+ raise ArgumentError, "No rank exists for key '#{key}'"
29
29
  end
30
30
 
31
31
  def to_s
32
- "Rank: id=#{@id}, key='#{@key}', value='#{@value}'"
32
+ "Rank: #{@value}"
33
33
  end
34
34
 
35
35
  class << self
@@ -0,0 +1,31 @@
1
+ #
2
+ # A seat at a poker table.
3
+ #
4
+ # Once a table is constructed, each seat at the table will contain a reference to
5
+ # the seat before it and the seat after it. In effect, the seat implements a very
6
+ # basic doubly-linked list.
7
+ #
8
+ class Seat
9
+ attr_accessor :prev, :next, :player, :button
10
+ attr_reader :number
11
+
12
+ def initialize number
13
+ raise ArgumentError, "Must create a seat number with a value of 1 or greater" if number < 1
14
+ @number = number
15
+ end
16
+
17
+ # Determines whether the seat is taken.
18
+ def taken?
19
+ return !@player.nil?
20
+ end
21
+
22
+ # Determines whether this seat has the dealer button.
23
+ def button?
24
+ return !button.nil?
25
+ end
26
+
27
+ def to_s
28
+ "Seat: #{@number}"
29
+ end
30
+
31
+ end
@@ -99,4 +99,8 @@ class StartingHand
99
99
  StartingHandRepository.instance.distinct_starting_hands
100
100
  end
101
101
 
102
+ def to_s
103
+ "StartingHand => " + @cards[0].to_s + ", " + @cards[1].to_s
104
+ end
105
+
102
106
  end
@@ -25,7 +25,7 @@ class Suit
25
25
  end
26
26
 
27
27
  def to_s
28
- "Suit: id=#{@id}, key='#{@key}', value='#{@value}'"
28
+ "Suit: #{@value}s"
29
29
  end
30
30
 
31
31
  def == suit