rora 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +17 -0
- data/README.md +258 -0
- data/Rakefile +12 -0
- data/lib/rora/hands.csv +7462 -0
- data/lib/rora/model/board.rb +95 -0
- data/lib/rora/model/card.rb +74 -0
- data/lib/rora/model/deck.rb +109 -0
- data/lib/rora/model/game.rb +8 -0
- data/lib/rora/model/hand.rb +126 -0
- data/lib/rora/model/hand_type.rb +53 -0
- data/lib/rora/model/player.rb +8 -0
- data/lib/rora/model/pot.rb +21 -0
- data/lib/rora/model/rank.rb +52 -0
- data/lib/rora/model/starting_hand.rb +102 -0
- data/lib/rora/model/suit.rb +43 -0
- data/lib/rora/model/table.rb +40 -0
- data/lib/rora/repository/hand_repository.rb +133 -0
- data/lib/rora/repository/starting_hand_repository.rb +23 -0
- data/lib/rora.rb +12 -0
- data/test/rora/model/board_test.rb +137 -0
- data/test/rora/model/card_test.rb +57 -0
- data/test/rora/model/deck_test.rb +114 -0
- data/test/rora/model/hand_test.rb +91 -0
- data/test/rora/model/hand_type_test.rb +39 -0
- data/test/rora/model/pot_test.rb +28 -0
- data/test/rora/model/rank_test.rb +45 -0
- data/test/rora/model/starting_hand_test.rb +71 -0
- data/test/rora/model/suit_test.rb +45 -0
- data/test/rora/repository/hand_repository_test.rb +83 -0
- data/test/rora/repository/starting_hand_repository_test.rb +17 -0
- data/test/rora_test.rb +5 -0
- metadata +119 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
#
|
2
|
+
# The logical table area where community cards are dealt.
|
3
|
+
#
|
4
|
+
# A board can be initialized with flop, turn and river cards.
|
5
|
+
#
|
6
|
+
# # A board with the flop set
|
7
|
+
# board_one = Board.new "AS,KS,QS"
|
8
|
+
#
|
9
|
+
# # A board with the flop and turn set
|
10
|
+
# board_two = Board.new "AS,KS,QS,JS"
|
11
|
+
#
|
12
|
+
# # A board with the flop, turn and river set.
|
13
|
+
# board_three = Board.new "AS,KS,QS,JS,TS"
|
14
|
+
#
|
15
|
+
# # An empty baord can be created as well.
|
16
|
+
# board_four = Board.new
|
17
|
+
#
|
18
|
+
# Boards may also be populated programmatically using the flop, turn and
|
19
|
+
# river methods respectively:
|
20
|
+
#
|
21
|
+
# board = Board.new
|
22
|
+
# board.flop = "AS,KS,QS"
|
23
|
+
# board.turn = "JS"
|
24
|
+
# board.river = "TS"
|
25
|
+
#
|
26
|
+
class Board
|
27
|
+
attr_reader :flop, :turn, :river
|
28
|
+
|
29
|
+
def initialize cards=nil
|
30
|
+
return if cards.nil?
|
31
|
+
|
32
|
+
cds = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
|
33
|
+
raise ArgumentError, "3 to 5 cards are required to create a board, #{cds.size} cards provided" if cds.size < 3 || cds.size > 5
|
34
|
+
raise ArgumentError, "The board contains duplicate cards" if cds.uniq.length != cds.length
|
35
|
+
|
36
|
+
@flop = cds[0..2]
|
37
|
+
@turn = cds[3] if cds.size >= 4
|
38
|
+
@river = cds[4] if cds.size == 5
|
39
|
+
end
|
40
|
+
|
41
|
+
def flop
|
42
|
+
@flop.dup if !@flop.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def flop= cards
|
46
|
+
raise ArgumentError, "Cannot deal a flop with empty array of cards" if cards.nil? || cards.empty?
|
47
|
+
|
48
|
+
cds = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
|
49
|
+
raise ArgumentError, "3 cards are required on the flop, #{cds.size} cards provided" if cds.size !=3
|
50
|
+
raise ArgumentError, "The flop contains duplicate cards" if cds.uniq.length != cds.length
|
51
|
+
@flop = cds[0..2]
|
52
|
+
end
|
53
|
+
|
54
|
+
def turn= card
|
55
|
+
cd = card.kind_of?(Card) ? card : Card.new(card)
|
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
|
58
|
+
@turn = cd
|
59
|
+
end
|
60
|
+
|
61
|
+
def river= card
|
62
|
+
cd = card.kind_of?(Card) ? card : Card.new(card)
|
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
|
65
|
+
@river = cd
|
66
|
+
end
|
67
|
+
|
68
|
+
def cards
|
69
|
+
cds = Array.new
|
70
|
+
cds += @flop if !@flop.nil?
|
71
|
+
cds.push(@turn) if !@turn.nil?
|
72
|
+
cds.push(@river) if !@river.nil?
|
73
|
+
cds
|
74
|
+
end
|
75
|
+
|
76
|
+
# Determines if the board contains the given card.
|
77
|
+
def contains? argument
|
78
|
+
if argument.kind_of? Card
|
79
|
+
return cards.include?(argument)
|
80
|
+
end
|
81
|
+
cards.include? Card.new(argument)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Determines if the board contains any of the given cards.
|
85
|
+
def contains_any? argument
|
86
|
+
if argument.kind_of? Array
|
87
|
+
argument.each {|card| return true if cards.include? card}
|
88
|
+
end
|
89
|
+
if argument.kind_of? String
|
90
|
+
Card.to_cards(argument).each {|card| return true if cards.include? card}
|
91
|
+
end
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# One of 52 of cards that are used to play card games.
|
3
|
+
#
|
4
|
+
# A card is comprised of one rank and one suit, with 13 ranks and 4 suits there
|
5
|
+
# are 52 unique playing cards. A card can be created in one of two ways:
|
6
|
+
#
|
7
|
+
# The first method uses the Rank and Suit enumerators
|
8
|
+
# card = Card.new(Rank::KING, Suit::HEART)
|
9
|
+
#
|
10
|
+
# The second method uses rank and suit values.
|
11
|
+
# card = Card.new("KH")
|
12
|
+
#
|
13
|
+
# Rank and suit values are case insensitive, so these work as well
|
14
|
+
# card = Card.new("Kh")
|
15
|
+
# card = Card.new("kH")
|
16
|
+
#
|
17
|
+
class Card
|
18
|
+
attr_reader :rank, :suit
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
if(args.size == 2)
|
22
|
+
@rank = args[0]
|
23
|
+
@suit = args[1]
|
24
|
+
end
|
25
|
+
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])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
@rank.id * @suit.id
|
34
|
+
end
|
35
|
+
|
36
|
+
def key
|
37
|
+
@rank.key + @suit.key
|
38
|
+
end
|
39
|
+
|
40
|
+
def value
|
41
|
+
@rank.value + @suit.value
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
"#{@rank.value} of #{@suit.value}s"
|
46
|
+
end
|
47
|
+
|
48
|
+
def eql? card
|
49
|
+
self == card
|
50
|
+
end
|
51
|
+
|
52
|
+
def == card
|
53
|
+
self.id == card.id
|
54
|
+
end
|
55
|
+
|
56
|
+
def hash
|
57
|
+
return self.id
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.to_cards string
|
61
|
+
cards = Array.new
|
62
|
+
if !string.include?(",") && !string.include?(" ")
|
63
|
+
string.scan(/../).each { |chars| cards << Card.new(chars) }
|
64
|
+
else
|
65
|
+
string.split(string.include?(",") ? "," : " ").each { |chars| cards << Card.new(chars) }
|
66
|
+
end
|
67
|
+
cards
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
"Card: name='#{name}' value='#{@rank.key}#{@suit.key}' id=#{id}"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#
|
2
|
+
# A complete set of 52 playing cards.
|
3
|
+
#
|
4
|
+
# A deck of cards may be used for playing a great variety of card games, with
|
5
|
+
# varying elements of skill and chance, some of which are played for money.
|
6
|
+
#
|
7
|
+
class Deck
|
8
|
+
attr_reader :cards
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@cards = Array.new
|
12
|
+
Suit.values.each do |suit|
|
13
|
+
Rank.values.each do |rank|
|
14
|
+
@cards << Card.new(rank, suit)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Shuffles all cards in the deck.
|
20
|
+
def shuffle
|
21
|
+
@cards.shuffle!
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the number of cards in the deck.
|
26
|
+
def size
|
27
|
+
@cards.size
|
28
|
+
end
|
29
|
+
|
30
|
+
# Deals a single card from the deck.
|
31
|
+
def deal
|
32
|
+
@cards.delete_at 0
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determines if the deck is empty.
|
36
|
+
def empty?
|
37
|
+
@cards.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a shallow copy of the cards in the deck.
|
41
|
+
def cards
|
42
|
+
@cards.dup
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the total number of cards with the given rank in the deck.
|
46
|
+
def count_cards_with_rank rank
|
47
|
+
count = 0;
|
48
|
+
@cards.each do |card|
|
49
|
+
count +=1 if card.rank.eql?(rank)
|
50
|
+
end
|
51
|
+
count
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the total number of cards with the given suit in the deck.
|
55
|
+
def count_cards_with_suit suit
|
56
|
+
count = 0;
|
57
|
+
@cards.each do |card|
|
58
|
+
count +=1 if card.suit.eql?(suit)
|
59
|
+
end
|
60
|
+
count
|
61
|
+
end
|
62
|
+
|
63
|
+
# Removes cards from the deck.
|
64
|
+
#
|
65
|
+
# This method can remove a Card, an Enumerable (an Array or Hash
|
66
|
+
# of Cards) or a StartingHand from the deck.
|
67
|
+
def remove argument
|
68
|
+
if argument.kind_of? Card
|
69
|
+
@cards.delete argument
|
70
|
+
end
|
71
|
+
if argument.kind_of? Enumerable
|
72
|
+
argument.each { |card| @cards.delete card }
|
73
|
+
end
|
74
|
+
if argument.kind_of? StartingHand
|
75
|
+
argument.cards.each { |card| @cards.delete card }
|
76
|
+
end
|
77
|
+
if argument.kind_of? Board
|
78
|
+
argument.cards.each { |card| @cards.delete card }
|
79
|
+
end
|
80
|
+
if argument.kind_of? String
|
81
|
+
Card.to_cards(argument).each { |card| @cards.delete card }
|
82
|
+
end
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
# Determines if the deck contains the given card.
|
87
|
+
def contains? argument
|
88
|
+
if argument.kind_of? Card
|
89
|
+
return @cards.include?(argument)
|
90
|
+
end
|
91
|
+
@cards.include? Card.new(argument)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Determines if the deck contains any of the given cards.
|
95
|
+
def contains_any? argument
|
96
|
+
if argument.kind_of? Array
|
97
|
+
argument.each {|card| return true if @cards.include? card}
|
98
|
+
end
|
99
|
+
if argument.kind_of? String
|
100
|
+
Card.to_cards(argument).each {|card| return true if @cards.include? card}
|
101
|
+
end
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def combination number
|
106
|
+
cards.combination(number).to_a
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#
|
2
|
+
# A subset of cards held at one time by a player during a game.
|
3
|
+
#
|
4
|
+
# A hand consists of exactly five cards, and can be created in one of two ways.
|
5
|
+
#
|
6
|
+
# A hand can be created with an array of cards.
|
7
|
+
# hand = Hand.new([c1,c2,c3,c4,c5])
|
8
|
+
#
|
9
|
+
# A hand can be created with string representation of each card.
|
10
|
+
# hand = Hand.new("ACKCJS9H4H")
|
11
|
+
#
|
12
|
+
class Hand
|
13
|
+
attr_reader :cards, :hand_repository
|
14
|
+
|
15
|
+
def initialize cards
|
16
|
+
@hand_repository = HandRepository.instance
|
17
|
+
@cards = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
|
18
|
+
|
19
|
+
raise ArgumentError, "Exactly 5 cards are required to create a hand, #{cards.size} provided" if @cards.size != 5
|
20
|
+
raise ArgumentError, "The hand contains duplicate cards" if @cards.uniq.length != @cards.length
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the hand id.
|
24
|
+
def id
|
25
|
+
i = 1
|
26
|
+
@cards.each { |card| i = i * card.id }
|
27
|
+
i
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the hand hash key.
|
31
|
+
def hash_key
|
32
|
+
j = 1
|
33
|
+
@cards.each { |card| j = j * card.rank.id }
|
34
|
+
flush? ? (j * 67) : j
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the hand value.
|
38
|
+
def value
|
39
|
+
@cards.map { |card| "#{card.key}" }.join(",")
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the hand strength, from 1 (strongest) to 7462 (weakest).
|
43
|
+
def score
|
44
|
+
resolve_hand_attribute(0)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the probability of this hand being dealt.
|
48
|
+
def probability
|
49
|
+
resolve_hand_attribute(3)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the hand name.
|
53
|
+
def name
|
54
|
+
resolve_hand_attribute(2)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the hand type.
|
58
|
+
def type
|
59
|
+
HandType.get resolve_hand_attribute(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns all cards contained in the hand.
|
63
|
+
def cards
|
64
|
+
@cards.dup
|
65
|
+
end
|
66
|
+
|
67
|
+
# Determines if this hand is a flush.
|
68
|
+
def flush?
|
69
|
+
for i in 0..@cards.size - 2 do
|
70
|
+
return false if @cards[i].suit != @cards[i+1].suit
|
71
|
+
end
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
# Determines if this hand is a straight flush.
|
76
|
+
def straight_flush?
|
77
|
+
type == HandType::STRAIGHT_FLUSH
|
78
|
+
end
|
79
|
+
|
80
|
+
# Determines if this hand is four of a kind.
|
81
|
+
def four_of_a_kind?
|
82
|
+
type == HandType::FOUR_OF_A_KIND
|
83
|
+
end
|
84
|
+
|
85
|
+
# Determines if this hand is a full house.
|
86
|
+
def full_house?
|
87
|
+
type == HandType::FULL_HOUSE
|
88
|
+
end
|
89
|
+
|
90
|
+
# Determines if this hand is a straight.
|
91
|
+
def straight?
|
92
|
+
type == HandType::STRAIGHT
|
93
|
+
end
|
94
|
+
|
95
|
+
# Determines if this hand is a three of a kind.
|
96
|
+
def three_of_a_kind?
|
97
|
+
type == HandType::THREE_OF_A_KIND
|
98
|
+
end
|
99
|
+
|
100
|
+
# Determines if this hand is two pair.
|
101
|
+
def two_pair?
|
102
|
+
type == HandType::TWO_PAIR
|
103
|
+
end
|
104
|
+
|
105
|
+
# Determines if this hand has one pair.
|
106
|
+
def one_pair?
|
107
|
+
type == HandType::ONE_PAIR
|
108
|
+
end
|
109
|
+
|
110
|
+
# Determines if this hand is a high card.
|
111
|
+
def high_card?
|
112
|
+
type == HandType::HIGH_CARD
|
113
|
+
end
|
114
|
+
|
115
|
+
# Determines if this hand contains the given cards.
|
116
|
+
def contains cards
|
117
|
+
@cards.contains cards
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def resolve_hand_attribute value
|
123
|
+
@hand_repository.find(hash_key)[value]
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#
|
2
|
+
# The nine different types of 5-card poker hand.
|
3
|
+
#
|
4
|
+
# A 5-card poker hand will be one of the following hand types:
|
5
|
+
#
|
6
|
+
# 1. High Card
|
7
|
+
# 2. One Pair
|
8
|
+
# 3. Two Pair
|
9
|
+
# 4. Three of a Kind
|
10
|
+
# 5. Straight
|
11
|
+
# 6. Flush
|
12
|
+
# 7. Full House
|
13
|
+
# 8. Four of a Kind
|
14
|
+
# 9. Straight Flush
|
15
|
+
#
|
16
|
+
class HandType
|
17
|
+
attr_reader :key, :value, :order
|
18
|
+
|
19
|
+
def initialize(key, value, order)
|
20
|
+
@key = key
|
21
|
+
@value = value
|
22
|
+
@order = order
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.values
|
26
|
+
[HIGH_CARD, ONE_PAIR, TWO_PAIR, THREE_OF_A_KIND, STRAIGHT, FLUSH, FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get(key)
|
30
|
+
self.values.each do |type|
|
31
|
+
return type if type.key.casecmp(key) == 0
|
32
|
+
end
|
33
|
+
raise ArgumentError, "No hand type exists for key " + key
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"HandType: key='#{@key}', value='#{@value}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
private :new
|
42
|
+
end
|
43
|
+
|
44
|
+
HIGH_CARD = new("HC", "High Card", 1)
|
45
|
+
ONE_PAIR = new("1P", "One Pair", 2)
|
46
|
+
TWO_PAIR = new("2P", "Two Pair", 3)
|
47
|
+
THREE_OF_A_KIND = new("3K", "Three of a Kind", 4)
|
48
|
+
STRAIGHT = new("ST", "Straight", 5)
|
49
|
+
FLUSH = new("FL", "Flush", 6)
|
50
|
+
FULL_HOUSE = new("FH", "Full House", 7)
|
51
|
+
FOUR_OF_A_KIND = new("4K", "Four of a Kind", 8)
|
52
|
+
STRAIGHT_FLUSH = new("SF", "Straight Flush", 9)
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# The sum of money that players wager during a single hand or game.
|
3
|
+
#
|
4
|
+
class Pot
|
5
|
+
|
6
|
+
attr_reader :total
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@total = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def add bet
|
13
|
+
@total += bet
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Pot: " + sprintf("$%.02f" , @total)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# The thirteen different hierarchical values in a deck of cards.
|
3
|
+
#
|
4
|
+
# A deck of cards has thirteen ranks: 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen,
|
5
|
+
# King, Ace.
|
6
|
+
#
|
7
|
+
# The ranks have a natural order, with the weakest value (0) assigned to the 2
|
8
|
+
# and the strongest value (12) assigned to the ace.
|
9
|
+
#
|
10
|
+
class Rank
|
11
|
+
attr_reader :id, :key, :value, :order
|
12
|
+
|
13
|
+
def initialize(id, key, value, order)
|
14
|
+
@id = id
|
15
|
+
@key = key
|
16
|
+
@value = value
|
17
|
+
@order = order
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.values
|
21
|
+
[TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get(key)
|
25
|
+
self.values.each do |rank|
|
26
|
+
return rank if rank.key.casecmp(key) == 0
|
27
|
+
end
|
28
|
+
raise ArgumentError, "No rank exists for key " + key
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"Rank: id=#{@id}, key='#{@key}', value='#{@value}'"
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
private :new
|
37
|
+
end
|
38
|
+
|
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)
|
52
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
#
|
2
|
+
# Two cards, also known as hole cards or pocket cards, which belong solely to
|
3
|
+
# one player and remain hidden from the other players.
|
4
|
+
#
|
5
|
+
class StartingHand
|
6
|
+
attr_reader :id, :key, :cards
|
7
|
+
|
8
|
+
def initialize cards
|
9
|
+
@cards = Array.new, @id = 1, @key = 1
|
10
|
+
|
11
|
+
@cards = cards.kind_of?(Array) ? cards : Card.to_cards(cards)
|
12
|
+
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
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns all cards contained in the starting hand.
|
21
|
+
def cards
|
22
|
+
@cards.dup
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the starting hand value.
|
26
|
+
def value
|
27
|
+
@cards.map { |card| "#{card.key}" }.join(",")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the shorthand notation for the starting hand.
|
31
|
+
#
|
32
|
+
# It is often desirable to have a short hand notation for starting hands,
|
33
|
+
# ignoring card suits and simply describing whether the starting hand is
|
34
|
+
# suited or not. The shorthand notation removes suit characters, and
|
35
|
+
# appends an 'o' (offsuit) or 's' (suited) to the card ranks.
|
36
|
+
#
|
37
|
+
# Starting hand consisting of the Ace of Clubs and Jack of Clubs:
|
38
|
+
# card.value == 'AC,JC'
|
39
|
+
# card.short_value == 'JCs'
|
40
|
+
#
|
41
|
+
# Starting hand consisting of the Ten of Hearts and Eight of Clubs:
|
42
|
+
# card.value == 'TH,8C',
|
43
|
+
# card.short_value == 'T8o'
|
44
|
+
def short_value
|
45
|
+
@cards[0].rank.key + @cards[1].rank.key + (suited? ? "s" : "o")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Determines if the starting hand is a pocket pair.
|
49
|
+
def pocket_pair?
|
50
|
+
cards[0].rank == cards[1].rank
|
51
|
+
end
|
52
|
+
|
53
|
+
# Determines if the starting hand is suited.
|
54
|
+
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
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns all possible starting hands.
|
62
|
+
#
|
63
|
+
# There are exaclty 1,326 (52c2) starting hands. This method returns
|
64
|
+
# a list containing every possible starting hand.
|
65
|
+
def self.all_starting_hands
|
66
|
+
StartingHandRepository.instance.all_starting_hands
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns all distinct starting hands.
|
70
|
+
#
|
71
|
+
# While there are 1,324 starting hands, many of these starting hands have
|
72
|
+
# the same value in poker. To elaborate on this a bit, consider the
|
73
|
+
# number of hands a player could have containing a Jack and a Seven:
|
74
|
+
#
|
75
|
+
# J♣ 7♦
|
76
|
+
# J♠ 7♦
|
77
|
+
# J♥ 7♦
|
78
|
+
# J♦ 7♦
|
79
|
+
# J♣ 7♠
|
80
|
+
# J♠ 7♠
|
81
|
+
# J♥ 7♠
|
82
|
+
# J♦ 7♠
|
83
|
+
# J♣ 4♥
|
84
|
+
# J♠ 4♥
|
85
|
+
#
|
86
|
+
# This list goes on a bit further further. You might be surprised to know
|
87
|
+
# that there are 16 starting hand combinations that contain exactly one
|
88
|
+
# Jack and one Seven. Out of this list of 16, only two card values have any
|
89
|
+
# relevance in a poker game - Jack-Seven suited and Jack-Seven unsuited.
|
90
|
+
#
|
91
|
+
# This exercise demonstrates that while there are 1,324 starting hands to
|
92
|
+
# contend with, the number of distinct starting hands is dramatically
|
93
|
+
# lower. This is because card suits don’t tend to affect the score of the
|
94
|
+
# hand.
|
95
|
+
#
|
96
|
+
# Once all 1,324 poker hands are collapsed into distinct values, we end up
|
97
|
+
# with just 169 starting hands!
|
98
|
+
def self.distinct_starting_hands
|
99
|
+
StartingHandRepository.instance.distinct_starting_hands
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# The four categories in a deck of cards.
|
3
|
+
#
|
4
|
+
# Each card bears one of four symbols showing which suit it belongs to. A deck
|
5
|
+
# of cards has four suits: hearts, clubs, spades, and diamonds.
|
6
|
+
#
|
7
|
+
class Suit
|
8
|
+
attr_reader :id, :key, :value
|
9
|
+
|
10
|
+
def initialize(id, key, value)
|
11
|
+
@id = id
|
12
|
+
@key = key
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.values
|
17
|
+
[HEART, SPADE, CLUB, DIAMOND]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get(key)
|
21
|
+
self.values.each do |suit|
|
22
|
+
return suit if suit.key.casecmp(key) == 0
|
23
|
+
end
|
24
|
+
raise ArgumentError, "No suit exists for key " + key
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"Suit: id=#{@id}, key='#{@key}', value='#{@value}'"
|
29
|
+
end
|
30
|
+
|
31
|
+
def == suit
|
32
|
+
self.id == suit.id
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
private :new
|
37
|
+
end
|
38
|
+
|
39
|
+
HEART = new(43, "H", "Heart")
|
40
|
+
SPADE = new(47, "S", "Spade")
|
41
|
+
CLUB = new(53, "C", "Club")
|
42
|
+
DIAMOND = new(59, "D", "Diamond")
|
43
|
+
end
|