rora 0.0.5
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/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
|