rora 0.0.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +59 -15
  2. data/Rakefile +0 -1
  3. data/lib/rora.rb +4 -0
  4. data/lib/rora/{hands.csv → 5-card-hands.csv} +0 -0
  5. data/lib/rora/7-card-hands.csv +323765 -0
  6. data/lib/rora/cards.csv +52 -0
  7. data/lib/rora/flushes.csv +40 -0
  8. data/lib/rora/model/board.rb +2 -2
  9. data/lib/rora/model/card.rb +17 -14
  10. data/lib/rora/model/deck.rb +23 -0
  11. data/lib/rora/model/equity.rb +18 -0
  12. data/lib/rora/model/hand.rb +12 -27
  13. data/lib/rora/model/hand_type.rb +1 -1
  14. data/lib/rora/model/pot.rb +1 -1
  15. data/lib/rora/model/rank.rb +34 -16
  16. data/lib/rora/model/starting_hand.rb +22 -18
  17. data/lib/rora/model/suit.rb +26 -11
  18. data/lib/rora/model/table.rb +0 -3
  19. data/lib/rora/repository/card_repository.rb +17 -0
  20. data/lib/rora/repository/hand_repository.rb +60 -20
  21. data/lib/rora/repository/starting_hand_repository.rb +2 -1
  22. data/lib/rora/utils/equity_calculator.rb +47 -0
  23. data/lib/rora/utils/hand_ranking_generator.rb +135 -0
  24. data/test/rora/model/board_test.rb +19 -20
  25. data/test/rora/model/card_test.rb +45 -15
  26. data/test/rora/model/deck_test.rb +24 -0
  27. data/test/rora/model/hand_test.rb +5 -29
  28. data/test/rora/model/hand_type_test.rb +3 -3
  29. data/test/rora/model/pot_test.rb +2 -2
  30. data/test/rora/model/rank_test.rb +10 -2
  31. data/test/rora/model/seat_test.rb +1 -1
  32. data/test/rora/model/starting_hand_test.rb +8 -16
  33. data/test/rora/model/suit_test.rb +3 -3
  34. data/test/rora/model/table_test.rb +8 -8
  35. data/test/rora/repository/hand_repository_test.rb +14 -16
  36. data/test/rora/utils/equity_calculator_test.rb +53 -0
  37. data/test/rora_test.rb +1 -1
  38. metadata +13 -6
  39. data/test/rora/model/game_test.rb +0 -23
@@ -0,0 +1,52 @@
1
+ AH,2
2
+ KH,3
3
+ QH,5
4
+ JH,7
5
+ TH,11
6
+ 9H,13
7
+ 8H,17
8
+ 7H,19
9
+ 6H,23
10
+ 5H,29
11
+ 4H,31
12
+ 3H,37
13
+ 2H,41
14
+ AD,43
15
+ KD,47
16
+ QD,53
17
+ JD,59
18
+ TD,61
19
+ 9D,67
20
+ 8D,71
21
+ 7D,73
22
+ 6D,79
23
+ 5D,83
24
+ 4D,89
25
+ 3D,97
26
+ 2D,101
27
+ AS,103
28
+ KS,107
29
+ QS,109
30
+ JS,113
31
+ TS,127
32
+ 9S,131
33
+ 8S,137
34
+ 7S,139
35
+ 6S,149
36
+ 5S,151
37
+ 4S,157
38
+ 3S,163
39
+ 2S,167
40
+ AC,173
41
+ KC,179
42
+ QC,181
43
+ JC,191
44
+ TC,193
45
+ 9C,197
46
+ 8C,199
47
+ 7C,211
48
+ 6C,223
49
+ 5C,227
50
+ 4C,229
51
+ 3C,233
52
+ 2C,239
@@ -0,0 +1,40 @@
1
+ 800,5,H
2
+ 1120,5,H
3
+ 480,5,H
4
+ 1568,5,H
5
+ 672,5,H
6
+ 288,5,H
7
+ 320,6,H
8
+ 448,6,H
9
+ 192,6,H
10
+ 128,7,H
11
+ 12500,5,S
12
+ 28125,5,S
13
+ 43750,5,S
14
+ 18750,5,S
15
+ 153125,5,S
16
+ 65625,5,S
17
+ 31250,6,S
18
+ 109375,6,S
19
+ 46875,6,S
20
+ 78125,7,S
21
+ 151263,5,C
22
+ 67228,5,C
23
+ 252105,5,C
24
+ 168070,5,C
25
+ 100842,5,C
26
+ 420175,5,C
27
+ 235298,6,C
28
+ 588245,6,C
29
+ 352947,6,C
30
+ 823543,7,C
31
+ 972,5,D
32
+ 2430,5,D
33
+ 3402,5,D
34
+ 11907,5,D
35
+ 6075,5,D
36
+ 8505,5,D
37
+ 1458,6,D
38
+ 3645,6,D
39
+ 5103,6,D
40
+ 2187,7,D
@@ -54,14 +54,14 @@ class Board
54
54
  def turn= card
55
55
  cd = card.kind_of?(Card) ? card : Card.new(card)
56
56
  raise RuntimeError, "The flop must be dealt before the turn card is dealt" if @flop.nil?
57
- raise ArgumentError, "The board already contains the #{cd.name}" if cards.include? cd
57
+ raise ArgumentError, "The board already contains the #{cd.value}" if cards.include? cd
58
58
  @turn = cd
59
59
  end
60
60
 
61
61
  def river= card
62
62
  cd = card.kind_of?(Card) ? card : Card.new(card)
63
63
  raise RuntimeError, "The turn card must be dealt before the river card is dealt" if @turn.nil?
64
- raise ArgumentError, "The board already contains the #{cd.name}" if cards.include? cd
64
+ raise ArgumentError, "The board already contains the #{cd.value}" if cards.include? cd
65
65
  @river = cd
66
66
  end
67
67
 
@@ -15,29 +15,31 @@
15
15
  # card = Card.new("kH")
16
16
  #
17
17
  class Card
18
- attr_reader :rank, :suit
18
+ include Comparable
19
19
 
20
+ attr_reader :rank, :suit, :key, :uid
21
+
20
22
  def initialize(*args)
21
23
  if(args.size == 2)
22
24
  @rank = args[0]
23
25
  @suit = args[1]
24
26
  end
25
27
  if(args.size == 1)
26
- raise ArgumentError, "#{args} is an invalid card sequence" if args[0].length != 2
27
- @rank = Rank.get(args[0][0])
28
- @suit = Suit.get(args[0][1])
28
+ raise ArgumentError, "#{args[0]} is an invalid card sequence" if args[0].length != 2
29
+ @rank = Rank.get(args[0][0].chr)
30
+ @suit = Suit.get(args[0][1].chr)
29
31
  end
32
+ @key = @rank.key + @suit.key
33
+ @card_repository = CardRepository.instance
34
+ @uid = @card_repository.get(@key)
30
35
  end
31
36
 
32
- def id
33
- @rank.id * @suit.id
37
+ def <=>(card)
38
+ return self.rank <=> card.rank if card.rank != self.rank
39
+ self.suit <=> card.suit
34
40
  end
35
41
 
36
- def key
37
- @rank.key + @suit.key
38
- end
39
-
40
- def name
42
+ def value
41
43
  "#{@rank.value} of #{@suit.value}s"
42
44
  end
43
45
 
@@ -46,11 +48,12 @@ class Card
46
48
  end
47
49
 
48
50
  def == card
49
- self.id == card.id
51
+ return false if !card.kind_of? Card
52
+ uid == card.uid
50
53
  end
51
54
 
52
55
  def hash
53
- return self.id
56
+ uid
54
57
  end
55
58
 
56
59
  def self.to_cards string
@@ -64,7 +67,7 @@ class Card
64
67
  end
65
68
 
66
69
  def to_s
67
- "Card: #{name}"
70
+ "#{value}"
68
71
  end
69
72
 
70
73
  end
@@ -14,6 +14,29 @@ class Deck
14
14
  @cards << Card.new(rank, suit)
15
15
  end
16
16
  end
17
+ @cards.sort!
18
+ end
19
+
20
+ # Retains all cards with the given suit or rank, removing all others.
21
+ def retain_all argument
22
+ if argument.kind_of? Suit
23
+ @cards = @cards.find_all{|card| card.suit.eql?(argument) }
24
+ end
25
+ if argument.kind_of? Rank
26
+ @cards = @cards.find_all{|card| card.rank.eql?(argument) }
27
+ end
28
+ self
29
+ end
30
+
31
+ # Removes all cards with the given suit or rank, retaining all others.
32
+ def remove_all argument
33
+ if argument.kind_of? Suit
34
+ @cards = @cards.find_all{|card| !card.suit.eql?(argument) }
35
+ end
36
+ if argument.kind_of? Rank
37
+ @cards = @cards.find_all{|card| !card.rank.eql?(argument) }
38
+ end
39
+ self
17
40
  end
18
41
 
19
42
  # Shuffles all cards in the deck.
@@ -0,0 +1,18 @@
1
+ class Equity
2
+
3
+ attr_accessor :starting_hand, :total_hands, :hands_won, :hands_tied, :value
4
+
5
+ def initialize(total_hands_played, total_hands_won, result)
6
+ @starting_hand = result[0]
7
+ @total_hands = total_hands_played
8
+ @hands_won = result[1]
9
+ @hands_tied = total_hands_played - total_hands_won
10
+ total_equity = @hands_won + @hands_tied
11
+ @value = (total_equity == 0 ? 0.00 : total_equity.quo(@total_hands) * 100.00)
12
+ end
13
+
14
+ def to_s
15
+ "starting hand: #{@starting_hand.key} => hands: #{@total_hands}, won: #{@hands_won}, tied: #{@hands_tied}, equity: #{sprintf("%05.3f", @value)}%"
16
+ end
17
+
18
+ end
@@ -11,43 +11,24 @@
11
11
  #
12
12
  class Hand
13
13
  attr_reader :cards
14
-
14
+
15
15
  def initialize cards
16
16
  @hand_repository = HandRepository.instance
17
- @cards = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
17
+ @cards = (cards.kind_of?(Array) ? cards : Card.to_cards(cards)).sort
18
18
  raise ArgumentError, "Exactly 5 cards are required to create a hand, #{cards.size} provided" if @cards.size != 5
19
19
  raise ArgumentError, "The hand contains duplicate cards" if @cards.uniq.length != @cards.length
20
20
  end
21
21
 
22
- # Returns the hand id.
23
- def id
24
- i = 1
25
- @cards.each { |card| i = i * card.id }
26
- i
27
- end
28
-
29
- # Returns the hand hash key.
30
- def hash_key
31
- j = 1
32
- @cards.each { |card| j = j * card.rank.id }
33
- flush? ? (j * 67) : j
34
- end
35
-
36
- # Returns the hand value.
37
- def value
38
- @cards.map { |card| "#{card.key}" }.join(",")
22
+ # Returns the hand key.
23
+ def key
24
+ @cards.map { |card| "#{card.key}" }.join
39
25
  end
40
26
 
41
- # Returns the hand strength, from 1 (strongest) to 7462 (weakest).
27
+ # Returns the hand score, from 1 (strongest) to 7462 (weakest).
42
28
  def score
43
29
  resolve_hand_attribute(0)
44
30
  end
45
31
 
46
- # Returns the probability of this hand being dealt.
47
- def probability
48
- resolve_hand_attribute(3)
49
- end
50
-
51
32
  # Returns the hand name.
52
33
  def name
53
34
  resolve_hand_attribute(2)
@@ -65,7 +46,7 @@ class Hand
65
46
 
66
47
  # Determines if this hand is a flush.
67
48
  def flush?
68
- for i in 0..@cards.size - 2 do
49
+ for i in 0..(@cards.size - 2) do
69
50
  return false if @cards[i].suit != @cards[i+1].suit
70
51
  end
71
52
  true
@@ -116,10 +97,14 @@ class Hand
116
97
  @cards.contains cards
117
98
  end
118
99
 
100
+ def to_s
101
+ "#{@cards.inject('') { |string, card| string << card.key }} (#{name})"
102
+ end
103
+
119
104
  private
120
105
 
121
106
  def resolve_hand_attribute value
122
- @hand_repository.find(hash_key)[value]
107
+ @hand_repository.evaluate_5_card_hand(cards)[value]
123
108
  end
124
109
 
125
110
  end
@@ -30,7 +30,7 @@ class HandType
30
30
  self.values.each do |type|
31
31
  return type if type.key.casecmp(key) == 0
32
32
  end
33
- raise ArgumentError, "No hand type exists for key " + key
33
+ raise ArgumentError, "No hand type exists for key '#{key}'"
34
34
  end
35
35
 
36
36
  def to_s
@@ -10,7 +10,7 @@ class Pot
10
10
 
11
11
  # Adds the specified bet to the pot
12
12
  def add bet
13
- raise ArgumentError if bet < 0
13
+ raise ArgumentError, "Can only add positive values" if bet < 0
14
14
  @value += bet
15
15
  self
16
16
  end
@@ -8,8 +8,10 @@
8
8
  # and the strongest value (12) assigned to the ace.
9
9
  #
10
10
  class Rank
11
- attr_reader :id, :key, :value, :order
11
+ include Comparable
12
12
 
13
+ attr_reader :id, :key, :value, :order
14
+
13
15
  def initialize(id, key, value, order)
14
16
  @id = id
15
17
  @key = key
@@ -23,30 +25,46 @@ class Rank
23
25
 
24
26
  def self.get(key)
25
27
  self.values.each do |rank|
26
- return rank if rank.key.casecmp(key) == 0
28
+ return rank if(rank.key.casecmp(key) == 0)
27
29
  end
28
30
  raise ArgumentError, "No rank exists for key '#{key}'"
29
31
  end
30
32
 
33
+ def <=>(rank)
34
+ self.order <=> rank.order
35
+ end
36
+
37
+ def eql? rank
38
+ self == rank
39
+ end
40
+
41
+ def == rank
42
+ self.key == rank.key
43
+ end
44
+
45
+ def hash
46
+ return self.rank.ord
47
+ end
48
+
31
49
  def to_s
32
- "Rank: #{@value}"
50
+ "#{@value}"
33
51
  end
34
52
 
35
53
  class << self
36
54
  private :new
37
55
  end
38
56
 
39
- TWO = new(2, "2", "Two", 0)
40
- THREE = new(3, "3", "Three", 1)
41
- FOUR = new(5, "4", "Four", 2)
42
- FIVE = new(7, "5", "Five", 3)
43
- SIX = new(11, "6", "Six", 4)
44
- SEVEN = new(13, "7", "Seven", 5)
45
- EIGHT = new(17, "8", "Eight", 6)
46
- NINE = new(19, "9", "Nine", 7)
47
- TEN = new(23, "T", "Ten", 8)
48
- JACK = new(29, "J", "Jack", 9)
49
- QUEEN = new(31, "Q", "Queen", 10)
50
- KING = new(37, "K", "King", 11)
51
- ACE = new(41, "A", "Ace", 12)
57
+ TWO = new(2, "2", "Two", 13)
58
+ THREE = new(3, "3", "Three", 12)
59
+ FOUR = new(5, "4", "Four", 11)
60
+ FIVE = new(7, "5", "Five", 10)
61
+ SIX = new(11, "6", "Six", 9)
62
+ SEVEN = new(13, "7", "Seven", 8)
63
+ EIGHT = new(17, "8", "Eight", 7)
64
+ NINE = new(19, "9", "Nine", 6)
65
+ TEN = new(23, "T", "Ten", 5)
66
+ JACK = new(29, "J", "Jack", 4)
67
+ QUEEN = new(31, "Q", "Queen", 3)
68
+ KING = new(37, "K", "King", 2)
69
+ ACE = new(41, "A", "Ace", 1)
52
70
  end
@@ -3,18 +3,14 @@
3
3
  # one player and remain hidden from the other players.
4
4
  #
5
5
  class StartingHand
6
- attr_reader :id, :key, :cards
7
-
6
+ attr_reader :cards, :key, :id
7
+
8
8
  def initialize cards
9
- @cards = Array.new, @id = 1, @key = 1
10
-
11
- @cards = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
9
+ @cards = (cards.kind_of?(Array) ? cards : Card.to_cards(cards)).sort
12
10
  raise ArgumentError, "Exactly 2 cards are required to create a starting hand, #{@cards.size} provided" if @cards.size != 2
13
- raise ArgumentError, "The hand contains duplicate cards" if @cards.uniq.length != @cards.length
14
-
15
- @cards.each {|card| @id *= card.id}
16
- @cards.each {|card| @key *= card.rank.id}
17
- @key = suited? ? @key * 67 : @key
11
+ raise ArgumentError, "The starting hand contains duplicate cards" if @cards.uniq.length != @cards.length
12
+ @key = @cards.inject('') { |string, card| string << card.key }
13
+ @id = @cards.inject(1) { |product, card| product * card.uid }
18
14
  end
19
15
 
20
16
  # Returns all cards contained in the starting hand.
@@ -22,11 +18,10 @@ class StartingHand
22
18
  @cards.dup
23
19
  end
24
20
 
25
- # Returns the starting hand value.
26
21
  def value
27
- @cards.map { |card| "#{card.key}" }.join(",")
22
+ @cards.map { |card| "#{card.value}" }.join(", ")
28
23
  end
29
-
24
+
30
25
  # Returns the shorthand notation for the starting hand.
31
26
  #
32
27
  # It is often desirable to have a short hand notation for starting hands,
@@ -52,10 +47,7 @@ class StartingHand
52
47
 
53
48
  # Determines if the starting hand is suited.
54
49
  def suited?
55
- for i in 0..@cards.size - 2 do
56
- return false if @cards[i].suit != @cards[i+1].suit
57
- end
58
- true
50
+ @cards[0].suit == @cards[1].suit
59
51
  end
60
52
 
61
53
  # Returns all possible starting hands.
@@ -99,8 +91,20 @@ class StartingHand
99
91
  StartingHandRepository.instance.distinct_starting_hands
100
92
  end
101
93
 
94
+ def eql? starting_hand
95
+ self == starting_hand
96
+ end
97
+
98
+ def == starting_hand
99
+ self.id == starting_hand.id
100
+ end
101
+
102
+ def hash
103
+ return self.id
104
+ end
105
+
102
106
  def to_s
103
- "StartingHand => " + @cards[0].to_s + ", " + @cards[1].to_s
107
+ value
104
108
  end
105
109
 
106
110
  end