rora 0.0.6 → 0.4.0
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/README.md +59 -15
- data/Rakefile +0 -1
- data/lib/rora.rb +4 -0
- data/lib/rora/{hands.csv → 5-card-hands.csv} +0 -0
- data/lib/rora/7-card-hands.csv +323765 -0
- data/lib/rora/cards.csv +52 -0
- data/lib/rora/flushes.csv +40 -0
- data/lib/rora/model/board.rb +2 -2
- data/lib/rora/model/card.rb +17 -14
- data/lib/rora/model/deck.rb +23 -0
- data/lib/rora/model/equity.rb +18 -0
- data/lib/rora/model/hand.rb +12 -27
- data/lib/rora/model/hand_type.rb +1 -1
- data/lib/rora/model/pot.rb +1 -1
- data/lib/rora/model/rank.rb +34 -16
- data/lib/rora/model/starting_hand.rb +22 -18
- data/lib/rora/model/suit.rb +26 -11
- data/lib/rora/model/table.rb +0 -3
- data/lib/rora/repository/card_repository.rb +17 -0
- data/lib/rora/repository/hand_repository.rb +60 -20
- data/lib/rora/repository/starting_hand_repository.rb +2 -1
- data/lib/rora/utils/equity_calculator.rb +47 -0
- data/lib/rora/utils/hand_ranking_generator.rb +135 -0
- data/test/rora/model/board_test.rb +19 -20
- data/test/rora/model/card_test.rb +45 -15
- data/test/rora/model/deck_test.rb +24 -0
- data/test/rora/model/hand_test.rb +5 -29
- data/test/rora/model/hand_type_test.rb +3 -3
- data/test/rora/model/pot_test.rb +2 -2
- data/test/rora/model/rank_test.rb +10 -2
- data/test/rora/model/seat_test.rb +1 -1
- data/test/rora/model/starting_hand_test.rb +8 -16
- data/test/rora/model/suit_test.rb +3 -3
- data/test/rora/model/table_test.rb +8 -8
- data/test/rora/repository/hand_repository_test.rb +14 -16
- data/test/rora/utils/equity_calculator_test.rb +53 -0
- data/test/rora_test.rb +1 -1
- metadata +13 -6
- data/test/rora/model/game_test.rb +0 -23
data/lib/rora/cards.csv
ADDED
@@ -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
|
data/lib/rora/model/board.rb
CHANGED
@@ -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.
|
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.
|
64
|
+
raise ArgumentError, "The board already contains the #{cd.value}" if cards.include? cd
|
65
65
|
@river = cd
|
66
66
|
end
|
67
67
|
|
data/lib/rora/model/card.rb
CHANGED
@@ -15,29 +15,31 @@
|
|
15
15
|
# card = Card.new("kH")
|
16
16
|
#
|
17
17
|
class Card
|
18
|
-
|
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
|
33
|
-
|
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
|
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
|
-
|
51
|
+
return false if !card.kind_of? Card
|
52
|
+
uid == card.uid
|
50
53
|
end
|
51
54
|
|
52
55
|
def hash
|
53
|
-
|
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
|
-
"
|
70
|
+
"#{value}"
|
68
71
|
end
|
69
72
|
|
70
73
|
end
|
data/lib/rora/model/deck.rb
CHANGED
@@ -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
|
data/lib/rora/model/hand.rb
CHANGED
@@ -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
|
23
|
-
def
|
24
|
-
|
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
|
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
|
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.
|
107
|
+
@hand_repository.evaluate_5_card_hand(cards)[value]
|
123
108
|
end
|
124
109
|
|
125
110
|
end
|
data/lib/rora/model/hand_type.rb
CHANGED
data/lib/rora/model/pot.rb
CHANGED
data/lib/rora/model/rank.rb
CHANGED
@@ -8,8 +8,10 @@
|
|
8
8
|
# and the strongest value (12) assigned to the ace.
|
9
9
|
#
|
10
10
|
class Rank
|
11
|
-
|
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
|
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
|
-
"
|
50
|
+
"#{@value}"
|
33
51
|
end
|
34
52
|
|
35
53
|
class << self
|
36
54
|
private :new
|
37
55
|
end
|
38
56
|
|
39
|
-
|
40
|
-
THREE = new(3, "3", "Three",
|
41
|
-
FOUR = new(5, "4", "Four",
|
42
|
-
FIVE = new(7, "5", "Five",
|
43
|
-
SIX = new(11, "6", "Six",
|
44
|
-
SEVEN = new(13, "7", "Seven",
|
45
|
-
EIGHT = new(17, "8", "Eight",
|
46
|
-
NINE = new(19, "9", "Nine",
|
47
|
-
TEN = new(23, "T", "Ten",
|
48
|
-
JACK = new(29, "J", "Jack",
|
49
|
-
QUEEN = new(31, "Q", "Queen",
|
50
|
-
KING = new(37, "K", "King",
|
51
|
-
ACE = new(41, "A", "Ace",
|
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 :
|
7
|
-
|
6
|
+
attr_reader :cards, :key, :id
|
7
|
+
|
8
8
|
def initialize cards
|
9
|
-
@cards = Array
|
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.
|
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.
|
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
|
-
|
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
|
-
|
107
|
+
value
|
104
108
|
end
|
105
109
|
|
106
110
|
end
|