rora 0.0.6 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|