ruby-poker 0.1.2 → 0.2.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/CHANGELOG +7 -1
- data/README +16 -8
- data/examples/deck.rb +28 -0
- data/examples/quick_example.rb +2 -1
- data/lib/card.rb +88 -18
- data/lib/ruby-poker.rb +280 -1
- data/test/test_card.rb +44 -0
- data/test/test_poker_hand.rb +112 -0
- metadata +7 -7
- data/lib/poker_hand.rb +0 -159
- data/lib/poker_helper.rb +0 -23
- data/lib/rank.rb +0 -48
- data/test/tc_poker_hand.rb +0 -15
data/CHANGELOG
CHANGED
@@ -7,4 +7,10 @@
|
|
7
7
|
2008-01-12 (0.1.2)
|
8
8
|
* Fixed critical bug that was stopping the whole program to not work
|
9
9
|
* Added some test cases as a result
|
10
|
-
* More test cases coming soon
|
10
|
+
* More test cases coming soon
|
11
|
+
2008-01-21 (0.2.0)
|
12
|
+
* Merged Patrick Hurley's poker solver
|
13
|
+
* Added support for hands with >5 cards
|
14
|
+
* Straights with a low Ace count now
|
15
|
+
* to_s on a PokerHand now includes the rank after the card list
|
16
|
+
* Finally wrote the Unit Tests suite
|
data/README
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
== Author
|
4
4
|
|
5
|
-
Robert Olson mailto:rko618
|
5
|
+
Robert Olson mailto:rko618 [at] gmail.com
|
6
6
|
|
7
7
|
== License
|
8
8
|
|
@@ -21,20 +21,28 @@ The homepage of this project is located at
|
|
21
21
|
|
22
22
|
== Description
|
23
23
|
|
24
|
-
|
24
|
+
Ruby-Poker handles the logic for poker hands.
|
25
25
|
|
26
26
|
Card representations can be passed to the PokerHand constructor as a string or an array.
|
27
|
-
Face cards (cards ten, jack, queen, king, and ace)
|
28
|
-
|
27
|
+
Face cards (cards ten, jack, queen, king, and ace) are created using their
|
28
|
+
letter representation (T, J, Q, K, A).
|
29
29
|
|
30
30
|
== Examples
|
31
31
|
|
32
32
|
In this section some examples show what can be done with this class.
|
33
33
|
|
34
34
|
hand1 = PokerHand.new("8H 9C TC JD QH")
|
35
|
-
hand2 = PokerHand.new(["3D", "3C", "3S", "
|
36
|
-
puts hand1 =>
|
35
|
+
hand2 = PokerHand.new(["3D", "3C", "3S", "KD", "AH"])
|
36
|
+
puts hand1 => 8h 9c Tc Jd Qh (Straight)
|
37
|
+
puts hand1.just_cards => 8h 9c Tc Jd Qh
|
37
38
|
puts hand1.rank => Straight
|
38
|
-
puts hand2 =>
|
39
|
-
puts hand2.rank => Three of a
|
39
|
+
puts hand2 => 3d 3c 3s Kd Ah (Three of a kind)
|
40
|
+
puts hand2.rank => Three of a kind
|
40
41
|
puts hand1 > hand2 => true
|
42
|
+
|
43
|
+
== Background
|
44
|
+
|
45
|
+
I (Robert Olson) wrote all of the code in the original version of ruby-poker which only
|
46
|
+
allowed for hands of 5 cards. I later discovered the amazing poker solving code written by
|
47
|
+
Patrick Hurley for http://rubyquiz.com/quiz24.html and merged it into
|
48
|
+
ruby-poker 0.2.0.
|
data/examples/deck.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Deck
|
2
|
+
def shuffle
|
3
|
+
deck_size = @cards.size
|
4
|
+
(deck_size * 2).times do
|
5
|
+
pos1, pos2 = rand(deck_size), rand(deck_size)
|
6
|
+
@cards[pos1], @cards[pos2] = @cards[pos2], @cards[pos1]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@cards = []
|
12
|
+
Card::SUITS.each_byte do |suit|
|
13
|
+
# careful not to double include the aces...
|
14
|
+
Card::FACES[1..-1].each_byte do |face|
|
15
|
+
@cards.push(Card.new(face.chr, suit.chr))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
shuffle()
|
19
|
+
end
|
20
|
+
|
21
|
+
def deal
|
22
|
+
@cards.pop
|
23
|
+
end
|
24
|
+
|
25
|
+
def empty?
|
26
|
+
@cards.empty?
|
27
|
+
end
|
28
|
+
end
|
data/examples/quick_example.rb
CHANGED
@@ -2,8 +2,9 @@ require 'rubygems'
|
|
2
2
|
require 'ruby-poker'
|
3
3
|
|
4
4
|
hand1 = PokerHand.new("8H 9C TC JD QH")
|
5
|
-
hand2 = PokerHand.new(["3D", "3C", "3S", "
|
5
|
+
hand2 = PokerHand.new(["3D", "3C", "3S", "KD", "AH"])
|
6
6
|
puts hand1
|
7
|
+
puts hand1.just_cards
|
7
8
|
puts hand1.rank
|
8
9
|
puts hand2
|
9
10
|
puts hand2.rank
|
data/lib/card.rb
CHANGED
@@ -1,22 +1,92 @@
|
|
1
|
-
class
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
class Card
|
2
|
+
SUITS = "cdhs"
|
3
|
+
FACES = "L23456789TJQKA"
|
4
|
+
SUIT_LOOKUP = {
|
5
|
+
'c' => 0,
|
6
|
+
'd' => 1,
|
7
|
+
'h' => 2,
|
8
|
+
's' => 3,
|
9
|
+
'C' => 0,
|
10
|
+
'D' => 1,
|
11
|
+
'H' => 2,
|
12
|
+
'S' => 3,
|
13
|
+
}
|
14
|
+
FACE_VALUES = {
|
15
|
+
'L' => 1, # this is a magic low ace
|
16
|
+
'2' => 2,
|
17
|
+
'3' => 3,
|
18
|
+
'4' => 4,
|
19
|
+
'5' => 5,
|
20
|
+
'6' => 6,
|
21
|
+
'7' => 7,
|
22
|
+
'8' => 8,
|
23
|
+
'9' => 9,
|
24
|
+
'T' => 10,
|
25
|
+
'J' => 11,
|
26
|
+
'Q' => 12,
|
27
|
+
'K' => 13,
|
28
|
+
'A' => 14,
|
29
|
+
}
|
12
30
|
|
13
|
-
|
14
|
-
|
31
|
+
def Card.face_value(face)
|
32
|
+
if (face)
|
33
|
+
FACE_VALUES[face.upcase] - 1
|
34
|
+
else
|
35
|
+
nil
|
15
36
|
end
|
37
|
+
end
|
16
38
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
39
|
+
protected
|
40
|
+
|
41
|
+
def build_from_string(card)
|
42
|
+
build_from_face_suit(card[0,1], card[1,1])
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_from_value(value)
|
46
|
+
@value = value
|
47
|
+
@suit = value / FACES.size()
|
48
|
+
@face = (value % FACES.size())
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_from_face_suit(face, suit)
|
52
|
+
@face = Card::face_value(face)
|
53
|
+
@suit = SUIT_LOOKUP[suit]
|
54
|
+
@value = (@suit * FACES.size()) + (@face - 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_from_face_suit_values(face, suit)
|
58
|
+
build_from_value((face - 1) + (suit * FACES.size()))
|
59
|
+
end
|
21
60
|
|
22
|
-
|
61
|
+
public
|
62
|
+
|
63
|
+
# got a little carried away with this constructor ;-)
|
64
|
+
def initialize(*value)
|
65
|
+
if (value.size == 1)
|
66
|
+
if (value[0].respond_to?(:to_str))
|
67
|
+
build_from_string(value[0])
|
68
|
+
elsif (value[0].respond_to?(:to_int))
|
69
|
+
build_from_value(value[0])
|
70
|
+
end
|
71
|
+
elsif (value.size == 2)
|
72
|
+
if (value[0].respond_to?(:to_str) &&
|
73
|
+
value[1].respond_to?(:to_str))
|
74
|
+
build_from_face_suit(value[0], value[1])
|
75
|
+
elsif (value[0].respond_to?(:to_int) &&
|
76
|
+
value[1].respond_to?(:to_int))
|
77
|
+
build_from_face_suit_values(value[0], value[1])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
attr_reader :suit, :face, :value
|
83
|
+
include Comparable
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
FACES[@face].chr + SUITS[@suit].chr
|
87
|
+
end
|
88
|
+
|
89
|
+
def <=> card2
|
90
|
+
@face <=> card2.face
|
91
|
+
end
|
92
|
+
end
|
data/lib/ruby-poker.rb
CHANGED
@@ -1 +1,280 @@
|
|
1
|
-
require '
|
1
|
+
require 'card.rb'
|
2
|
+
|
3
|
+
class PokerHand
|
4
|
+
include Comparable
|
5
|
+
attr_reader :hand
|
6
|
+
|
7
|
+
def initialize(cards = [])
|
8
|
+
if cards.is_a? Array
|
9
|
+
@hand = cards.map { |str| Card.new(str.to_s) }
|
10
|
+
elsif cards.respond_to?(:to_str)
|
11
|
+
@hand = cards.scan(/\S{2,3}/).map { |str| Card.new(str) }
|
12
|
+
else
|
13
|
+
@hand = cards
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def face_values
|
18
|
+
@hand.map { |c| c.face }
|
19
|
+
end
|
20
|
+
|
21
|
+
def by_suit
|
22
|
+
PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
|
23
|
+
end
|
24
|
+
|
25
|
+
def by_face
|
26
|
+
PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
|
27
|
+
end
|
28
|
+
|
29
|
+
def =~ (re)
|
30
|
+
re.match(@hand.join(' '))
|
31
|
+
end
|
32
|
+
|
33
|
+
def royal_flush?
|
34
|
+
if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
|
35
|
+
[[10], arrange_hand(md)]
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def straight_flush?
|
42
|
+
if (md = (/.(.)(.)(?: 1.\2){4}/.match(delta_transform(true))))
|
43
|
+
high_card = Card::face_value(md[1])
|
44
|
+
arranged_hand = fix_low_ace_display(md[0] + ' ' +
|
45
|
+
md.pre_match + ' ' + md.post_match)
|
46
|
+
[[9, high_card], arranged_hand]
|
47
|
+
else
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def four_of_a_kind?
|
53
|
+
if (md = (by_face =~ /(.). \1. \1. \1./))
|
54
|
+
# get kicker
|
55
|
+
(md.pre_match + md.post_match).match(/(\S)/)
|
56
|
+
[
|
57
|
+
[8, Card::face_value(md[1]), Card::face_value($1)],
|
58
|
+
arrange_hand(md)
|
59
|
+
]
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def full_house?
|
66
|
+
if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./))
|
67
|
+
arranged_hand = arrange_hand(md[0] + ' ' +
|
68
|
+
md.pre_match + ' ' + md[2] + ' ' + md.post_match)
|
69
|
+
[
|
70
|
+
[7, Card::face_value(md[1]), Card::face_value(md[3])],
|
71
|
+
arranged_hand
|
72
|
+
]
|
73
|
+
elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/))
|
74
|
+
arranged_hand = arrange_hand(md[4] + ' ' + md[1] + ' ' +
|
75
|
+
md.pre_match + ' ' + md[3] + ' ' + md.post_match)
|
76
|
+
[
|
77
|
+
[7, Card::face_value(md[5]), Card::face_value(md[2])],
|
78
|
+
arranged_hand
|
79
|
+
]
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def flush?
|
86
|
+
if (md = (by_suit =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/))
|
87
|
+
[
|
88
|
+
[
|
89
|
+
6,
|
90
|
+
Card::face_value(md[1]),
|
91
|
+
*(md[3..6].map { |f| Card::face_value(f) })
|
92
|
+
],
|
93
|
+
arrange_hand(md)
|
94
|
+
]
|
95
|
+
else
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def straight?
|
101
|
+
result = false
|
102
|
+
if hand.size >= 5
|
103
|
+
transform = delta_transform
|
104
|
+
# note we can have more than one delta 0 that we
|
105
|
+
# need to shuffle to the back of the hand
|
106
|
+
until transform.match(/^\S{3}( [1-9x]\S\S)+( 0\S\S)*$/) do
|
107
|
+
transform.gsub!(/(\s0\S\S)(.*)/, "\\2\\1")
|
108
|
+
end
|
109
|
+
if (md = (/.(.). 1.. 1.. 1.. 1../.match(transform)))
|
110
|
+
high_card = Card::face_value(md[1])
|
111
|
+
arranged_hand = fix_low_ace_display(md[0] + ' ' +
|
112
|
+
md.pre_match + ' ' + md.post_match)
|
113
|
+
result = [[5, high_card], arranged_hand]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def three_of_a_kind?
|
119
|
+
if (md = (by_face =~ /(.). \1. \1./))
|
120
|
+
# get kicker
|
121
|
+
arranged_hand = arrange_hand(md)
|
122
|
+
arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
|
123
|
+
[
|
124
|
+
[
|
125
|
+
4,
|
126
|
+
Card::face_value(md[1]),
|
127
|
+
Card::face_value($1),
|
128
|
+
Card::face_value($2)
|
129
|
+
],
|
130
|
+
arranged_hand
|
131
|
+
]
|
132
|
+
else
|
133
|
+
false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def two_pair?
|
138
|
+
if (md = (by_face =~ /(.). \1.(.*) (.). \3./))
|
139
|
+
# get kicker
|
140
|
+
arranged_hand = arrange_hand(md[0] + ' ' +
|
141
|
+
md.pre_match + ' ' + md[2] + ' ' + md.post_match)
|
142
|
+
arranged_hand.match(/(?:\S\S ){4}(\S)/)
|
143
|
+
[
|
144
|
+
[
|
145
|
+
3,
|
146
|
+
Card::face_value(md[1]),
|
147
|
+
Card::face_value(md[3]),
|
148
|
+
Card::face_value($1)
|
149
|
+
],
|
150
|
+
arranged_hand
|
151
|
+
]
|
152
|
+
else
|
153
|
+
false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def pair?
|
158
|
+
if (md = (by_face =~ /(.). \1./))
|
159
|
+
# get kicker
|
160
|
+
arranged_hand = arrange_hand(md)
|
161
|
+
arranged_hand.match(/(?:\S\S ){2}(\S)\S\s+(\S)\S\s+(\S)/)
|
162
|
+
[
|
163
|
+
[
|
164
|
+
2,
|
165
|
+
Card::face_value(md[1]),
|
166
|
+
Card::face_value($1),
|
167
|
+
Card::face_value($2),
|
168
|
+
Card::face_value($3)
|
169
|
+
],
|
170
|
+
arranged_hand
|
171
|
+
]
|
172
|
+
else
|
173
|
+
false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def highest_card?
|
178
|
+
result = by_face
|
179
|
+
[[1, *result.face_values[0..4]], result.hand.join(' ')]
|
180
|
+
end
|
181
|
+
|
182
|
+
OPS = [
|
183
|
+
['Royal Flush', :royal_flush? ],
|
184
|
+
['Straight Flush', :straight_flush? ],
|
185
|
+
['Four of a kind', :four_of_a_kind? ],
|
186
|
+
['Full house', :full_house? ],
|
187
|
+
['Flush', :flush? ],
|
188
|
+
['Straight', :straight? ],
|
189
|
+
['Three of a kind', :three_of_a_kind?],
|
190
|
+
['Two pair', :two_pair? ],
|
191
|
+
['Pair', :pair? ],
|
192
|
+
['Highest Card', :highest_card? ],
|
193
|
+
]
|
194
|
+
|
195
|
+
def hand_rating
|
196
|
+
OPS.map { |op|
|
197
|
+
(method(op[1]).call()) ? op[0] : false
|
198
|
+
}.find { |v| v }
|
199
|
+
end
|
200
|
+
|
201
|
+
alias :rank :hand_rating
|
202
|
+
|
203
|
+
def score
|
204
|
+
OPS.map { |op|
|
205
|
+
method(op[1]).call()
|
206
|
+
}.find([0]) { |score| score }
|
207
|
+
end
|
208
|
+
|
209
|
+
def arranged_hand
|
210
|
+
score[1] + " (#{hand_rating})"
|
211
|
+
end
|
212
|
+
|
213
|
+
def just_cards
|
214
|
+
@hand.join(" ")
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
just_cards + " (" + hand_rating + ")"
|
219
|
+
end
|
220
|
+
|
221
|
+
def <=> other_hand
|
222
|
+
self.score <=> other_hand.score
|
223
|
+
end
|
224
|
+
|
225
|
+
protected
|
226
|
+
|
227
|
+
def arrange_hand(md)
|
228
|
+
hand = if (md.respond_to?(:to_str))
|
229
|
+
md
|
230
|
+
else
|
231
|
+
md[0] + ' ' + md.pre_match + md.post_match
|
232
|
+
end
|
233
|
+
hand.gsub!(/\s+/, ' ')
|
234
|
+
hand.gsub(/\s+$/,'')
|
235
|
+
end
|
236
|
+
|
237
|
+
def delta_transform(use_suit = false)
|
238
|
+
aces = @hand.select { |c| c.face == Card::face_value('A') }
|
239
|
+
aces.map! { |c| Card.new(1,c.suit) }
|
240
|
+
|
241
|
+
base = if (use_suit)
|
242
|
+
(@hand + aces).sort_by { |c| [c.suit, c.face] }.reverse
|
243
|
+
else
|
244
|
+
(@hand + aces).sort_by { |c| [c.face, c.suit] }.reverse
|
245
|
+
end
|
246
|
+
|
247
|
+
result = base.inject(['',nil]) do |(delta_hand, prev_card), card|
|
248
|
+
if (prev_card)
|
249
|
+
delta = prev_card - card.face
|
250
|
+
else
|
251
|
+
delta = 0
|
252
|
+
end
|
253
|
+
# does not really matter for my needs
|
254
|
+
delta = 'x' if (delta > 9 || delta < 0)
|
255
|
+
delta_hand += delta.to_s + card.to_s + ' '
|
256
|
+
[delta_hand, card.face]
|
257
|
+
end
|
258
|
+
|
259
|
+
# we just want the delta transform, not the last cards face too
|
260
|
+
result[0].chop
|
261
|
+
end
|
262
|
+
|
263
|
+
def fix_low_ace_display(arranged_hand)
|
264
|
+
# remove card deltas (this routine is only used for straights)
|
265
|
+
arranged_hand.gsub!(/\S(\S\S)\s*/, "\\1 ")
|
266
|
+
|
267
|
+
# Fix "low aces"
|
268
|
+
arranged_hand.gsub!(/L(\S)/, "A\\1")
|
269
|
+
|
270
|
+
# Remove duplicate aces (this will not work if you have
|
271
|
+
# multiple decks or wild cards)
|
272
|
+
arranged_hand.gsub!(/((A\S).*)\2/, "\\1")
|
273
|
+
|
274
|
+
# cleanup white space
|
275
|
+
arranged_hand.gsub!(/\s+/, ' ')
|
276
|
+
# careful to use gsub as gsub! can return nil here
|
277
|
+
arranged_hand.gsub(/\s+$/, '')
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
data/test/test_card.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Code Generated by ZenTest v. 3.8.0
|
2
|
+
# classname: asrt / meth = ratio%
|
3
|
+
# Card: 0 / 4 = 0.00%
|
4
|
+
|
5
|
+
require 'test/unit' unless defined? $ZENTEST and $ZENTEST
|
6
|
+
require 'card.rb'
|
7
|
+
|
8
|
+
class TestCard < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
# testing various input formats for cards
|
11
|
+
@c1 = Card.new("9c")
|
12
|
+
@c2 = Card.new("TD")
|
13
|
+
@c3 = Card.new("jh")
|
14
|
+
@c4 = Card.new("qS")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_class_face_value
|
18
|
+
assert_equal(0, Card.face_value('L'))
|
19
|
+
assert_equal(13, Card.face_value('A'))
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_face
|
23
|
+
assert_equal(8, @c1.face)
|
24
|
+
assert_equal(9, @c2.face)
|
25
|
+
assert_equal(10, @c3.face)
|
26
|
+
assert_equal(11, @c4.face)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_suit
|
30
|
+
assert_equal(0, @c1.suit)
|
31
|
+
assert_equal(1, @c2.suit)
|
32
|
+
assert_equal(2, @c3.suit)
|
33
|
+
assert_equal(3, @c4.suit)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_value
|
37
|
+
assert_equal(7, @c1.value)
|
38
|
+
assert_equal(22, @c2.value)
|
39
|
+
assert_equal(37, @c3.value)
|
40
|
+
assert_equal(52, @c4.value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Number of errors detected: 9
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Code Generated by ZenTest v. 3.8.0
|
2
|
+
# classname: asrt / meth = ratio%
|
3
|
+
# PokerHand: 0 / 24 = 0.00%
|
4
|
+
|
5
|
+
require 'test/unit' unless defined? $ZENTEST and $ZENTEST
|
6
|
+
require 'ruby-poker.rb'
|
7
|
+
|
8
|
+
class TestPokerHand < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@trips = PokerHand.new("2D 9C AS AH AC")
|
11
|
+
@full_boat = PokerHand.new(["2H", "2D", "4C", "4D", "4S"])
|
12
|
+
@flush = PokerHand.new("3D 6D 7D TD QD 5H 2S")
|
13
|
+
@straight = PokerHand.new("8H 9D TS JH QC AS")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_arranged_hand
|
17
|
+
assert_equal("As Ah Ac 9c 2d (Three of a kind)", @trips.arranged_hand)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_by_face
|
21
|
+
assert_equal([13, 13, 13, 8, 1], @trips.by_face.hand.collect {|c| c.face})
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_by_suit
|
25
|
+
assert_equal([3, 2, 1, 0, 0], @trips.by_suit.hand.collect {|c| c.suit})
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_face_values
|
29
|
+
assert_equal([1, 8, 13, 13, 13], @trips.face_values)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_flush_eh
|
33
|
+
assert @flush.flush?
|
34
|
+
assert !@trips.flush?
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_four_of_a_kind_eh
|
38
|
+
assert !@trips.four_of_a_kind?
|
39
|
+
assert PokerHand.new("AD 9C AS AH AC")
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_full_house_eh
|
43
|
+
assert !@trips.full_house?
|
44
|
+
assert @full_boat.full_house?
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_hand
|
48
|
+
assert_equal 5, @trips.hand.size
|
49
|
+
assert_instance_of Card, @trips.hand[0]
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_hand_rating
|
53
|
+
assert_equal "Three of a kind", @trips.hand_rating
|
54
|
+
assert_equal "Full house", @full_boat.hand_rating
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_rank
|
58
|
+
# rank is an alias for hand_rating
|
59
|
+
assert_not_nil @trips.rank
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_highest_card_eh
|
63
|
+
# hard to test, make sure it does not return null
|
64
|
+
assert PokerHand.new("2D 4S 6C 8C TH").highest_card?
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_just_cards
|
68
|
+
assert_equal("2d 9c As Ah Ac", @trips.just_cards)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_pair_eh
|
72
|
+
assert !PokerHand.new("5C JC 2H 7S 3D").pair?
|
73
|
+
assert PokerHand.new("6D 7C 5D 5H 3S").pair?
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_royal_flush_eh
|
77
|
+
assert !@flush.royal_flush?
|
78
|
+
assert PokerHand.new("AD KD QD JD TD").royal_flush?
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_score
|
82
|
+
assert_equal([4, 13, 8, 1], @trips.score[0])
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_straight_eh
|
86
|
+
assert @straight.straight?
|
87
|
+
assert PokerHand.new("AH 2S 3D 4H 5D").straight?
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_straight_flush_eh
|
91
|
+
assert !@flush.straight_flush?
|
92
|
+
assert !@straight.straight_flush?
|
93
|
+
assert PokerHand.new("8H 9H TH JH QH AS").straight_flush?
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_three_of_a_kind_eh
|
97
|
+
assert @trips.three_of_a_kind?
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_two_pair_eh
|
101
|
+
assert PokerHand.new("2S 2D TH TD 4S").two_pair?
|
102
|
+
assert !PokerHand.new("6D 7C 5D 5H 3S").two_pair?
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_comparisons
|
106
|
+
assert_equal(0, @trips <=> @trips)
|
107
|
+
assert_equal(1, PokerHand.new("5C JC 2H 5S 3D") <=> PokerHand.new("6D 7C 5D 5H 3S"))
|
108
|
+
assert_equal(-1, PokerHand.new("6D 7C 5D 5H 3S") <=> PokerHand.new("5C JC 2H 5S 3D"))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Number of errors detected: 20
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-poker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Olson
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-01-
|
12
|
+
date: 2008-01-21 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -24,13 +24,12 @@ extra_rdoc_files:
|
|
24
24
|
- CHANGELOG
|
25
25
|
- LICENSE
|
26
26
|
files:
|
27
|
+
- examples/deck.rb
|
27
28
|
- examples/quick_example.rb
|
28
29
|
- lib/card.rb
|
29
|
-
- lib/poker_hand.rb
|
30
|
-
- lib/poker_helper.rb
|
31
|
-
- lib/rank.rb
|
32
30
|
- lib/ruby-poker.rb
|
33
|
-
- test/
|
31
|
+
- test/test_card.rb
|
32
|
+
- test/test_poker_hand.rb
|
34
33
|
- README
|
35
34
|
- CHANGELOG
|
36
35
|
- LICENSE
|
@@ -61,4 +60,5 @@ signing_key:
|
|
61
60
|
specification_version: 2
|
62
61
|
summary: Ruby library for determining the winner in a game of poker.
|
63
62
|
test_files:
|
64
|
-
- test/
|
63
|
+
- test/test_card.rb
|
64
|
+
- test/test_poker_hand.rb
|
data/lib/poker_hand.rb
DELETED
@@ -1,159 +0,0 @@
|
|
1
|
-
require 'poker_helper.rb'
|
2
|
-
require 'rank.rb'
|
3
|
-
require 'card.rb'
|
4
|
-
|
5
|
-
class PokerHand
|
6
|
-
|
7
|
-
include PokerHelper
|
8
|
-
include Comparable
|
9
|
-
attr_reader :cards, :values_hash
|
10
|
-
|
11
|
-
def initialize cards
|
12
|
-
@cards = Array.new
|
13
|
-
@values_hash = Hash.new(0)
|
14
|
-
|
15
|
-
begin
|
16
|
-
if cards.class == String
|
17
|
-
cards = cards.split
|
18
|
-
end
|
19
|
-
|
20
|
-
cards.each {|c| @cards << Card.new(c)}
|
21
|
-
|
22
|
-
@cards.each {|c| @values_hash[c.value] += 1}
|
23
|
-
rescue
|
24
|
-
raise "Unable to process cards input. Please check the documentation for acceptable input formats"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def rank
|
29
|
-
@rank ||= self.determine_rank
|
30
|
-
end
|
31
|
-
|
32
|
-
# Returns an array of all the values in the hand. Does not
|
33
|
-
# sort the values before returning them so Array#sort should
|
34
|
-
# be called on the return value if sorting is desired.
|
35
|
-
def values
|
36
|
-
@cards.collect {|c| c.value}
|
37
|
-
end
|
38
|
-
|
39
|
-
# Flush if the cards are all the same suit.
|
40
|
-
def flush?
|
41
|
-
@cards.collect {|c| c.suit}.uniq.size == 1
|
42
|
-
end
|
43
|
-
|
44
|
-
# Straight if all cards are consecutive values.
|
45
|
-
def straight?
|
46
|
-
straight = true # innocent until proven guilty
|
47
|
-
c_values = self.values.sort
|
48
|
-
i = c_values.first
|
49
|
-
c_values.each do |v|
|
50
|
-
straight &&= (v == i)
|
51
|
-
i += 1
|
52
|
-
end
|
53
|
-
return straight
|
54
|
-
end
|
55
|
-
|
56
|
-
# Royal Flush: Ten, Jack, Queen, King, Ace, in same suit.
|
57
|
-
def royal_flush?
|
58
|
-
self.flush? and self.values.sort == [10,11,12,13,14]
|
59
|
-
end
|
60
|
-
|
61
|
-
# Straight Flush: All cards are consecutive values of same suit.
|
62
|
-
def straight_flush?
|
63
|
-
self.flush? and self.straight?
|
64
|
-
end
|
65
|
-
|
66
|
-
# Full House: Three of a kind and a pair.
|
67
|
-
def full_house?
|
68
|
-
@values_hash.has_value?(3) and @values_hash.has_value?(2)
|
69
|
-
end
|
70
|
-
|
71
|
-
# Three of a Kind: Three cards of the same value.
|
72
|
-
def three_of_a_kind?
|
73
|
-
@values_hash.has_value?(3) and not full_house?
|
74
|
-
end
|
75
|
-
|
76
|
-
# Four of a Kind: Four cards of the same value.
|
77
|
-
def four_of_a_kind?
|
78
|
-
@values_hash.has_value?(4)
|
79
|
-
end
|
80
|
-
|
81
|
-
# Two Pair: Two different pairs.
|
82
|
-
def two_pair?
|
83
|
-
@values_hash.select {|k, v| v == 2}.size == 2 # check if two seperate values have two occurances
|
84
|
-
end
|
85
|
-
|
86
|
-
# One Pair: Two cards of the same value.
|
87
|
-
def one_pair?
|
88
|
-
@values_hash.select {|k, v| v == 2}.size == 1
|
89
|
-
end
|
90
|
-
|
91
|
-
def <=> hand2
|
92
|
-
if self.rank != hand2.rank
|
93
|
-
self.rank <=> hand2.rank
|
94
|
-
else # we have a tie... do a tie breaker
|
95
|
-
case self.rank.value # both hands have the same rank
|
96
|
-
when 0, 4, 5, 8 # highest card, straight, flush, straight flush
|
97
|
-
# check who has the highest card, if same move to the next card, etc
|
98
|
-
self.values.sort.reverse <=> hand2.values.sort.reverse
|
99
|
-
when 1 # two people with one pair
|
100
|
-
# check which pair is higher, if the pairs are the same then remove the pairs and check
|
101
|
-
# for highest card
|
102
|
-
hand1_pair = @values_hash.index(2) # get key(card value) of the duplicate pair
|
103
|
-
hand2_pair = hand2.values_hash.index(2)
|
104
|
-
if hand1_pair == hand2_pair
|
105
|
-
return singles(self.values).reverse <=> singles(hand2.values).reverse
|
106
|
-
else
|
107
|
-
return hand1_pair <=> hand2_pair
|
108
|
-
end
|
109
|
-
when 2 # two people with two pairs
|
110
|
-
# check who has the higher pair, if the pairs are the same
|
111
|
-
# then remove the pairs and check for highest card
|
112
|
-
hand1_pairs = duplicates(self.values).reverse
|
113
|
-
hand2_pairs = duplicates(hand2.values).reverse
|
114
|
-
if hand1_pairs == hand2_pairs
|
115
|
-
return singles(self.values) <=> singles(hand2.values) # there will be only one card remaining
|
116
|
-
else
|
117
|
-
return hand1_pairs <=> hand2_pairs
|
118
|
-
end
|
119
|
-
when 3, 6 # three_of_a_kind, full house
|
120
|
-
@values_hash.index(3) <=> hand2.values_hash.index(3)
|
121
|
-
when 7 # four of a kind
|
122
|
-
@values_hash.index(4) <=> hand2.values_hash.index(4)
|
123
|
-
when 9 # royal flush
|
124
|
-
return 0 # royal flushes always tie
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def to_s
|
130
|
-
@cards.inject("") {|str, c| str << "#{c.to_s} "}.rstrip
|
131
|
-
end
|
132
|
-
|
133
|
-
protected
|
134
|
-
|
135
|
-
def determine_rank
|
136
|
-
if royal_flush?
|
137
|
-
r = Rank::ROYAL_FLUSH
|
138
|
-
elsif straight_flush?
|
139
|
-
r = Rank::STRAIGHT_FLUSH
|
140
|
-
elsif four_of_a_kind?
|
141
|
-
r = Rank::FOUR_OF_A_KIND
|
142
|
-
elsif full_house?
|
143
|
-
r = Rank::FULL_HOUSE
|
144
|
-
elsif flush?
|
145
|
-
r = Rank::FLUSH
|
146
|
-
elsif straight?
|
147
|
-
r = Rank::STRAIGHT
|
148
|
-
elsif three_of_a_kind?
|
149
|
-
r = Rank::THREE_OF_A_KIND
|
150
|
-
elsif two_pair?
|
151
|
-
r = Rank::TWO_PAIR
|
152
|
-
elsif one_pair?
|
153
|
-
r = Rank::PAIR
|
154
|
-
else
|
155
|
-
r = Rank::HIGH_CARD
|
156
|
-
end
|
157
|
-
return Rank.new(r)
|
158
|
-
end
|
159
|
-
end # class PokerHand
|
data/lib/poker_helper.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module PokerHelper
|
2
|
-
# Returns array of elements that only occur once
|
3
|
-
# [1, 1, 2, 3] => [2, 3]
|
4
|
-
def singles array1
|
5
|
-
counts = Hash.new(0)
|
6
|
-
array1.each do |value|
|
7
|
-
counts[value] += 1
|
8
|
-
end
|
9
|
-
|
10
|
-
return counts.collect {|key,value| value == 1 ? key : nil }.compact.sort
|
11
|
-
end
|
12
|
-
|
13
|
-
# Returns an array containing values that we duplicated in the original array
|
14
|
-
# [1, 2, 3, 1] => [1]
|
15
|
-
def duplicates array1
|
16
|
-
counts = Hash.new(0)
|
17
|
-
array1.each do |value|
|
18
|
-
counts[value] += 1
|
19
|
-
end
|
20
|
-
|
21
|
-
return counts.collect {|key,value| value > 1 ? key : nil }.compact.sort
|
22
|
-
end
|
23
|
-
end # module ArrayHelper
|
data/lib/rank.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
class PokerHand
|
2
|
-
|
3
|
-
# Rank objects maintain the poker value of the hand. Ex. Full House
|
4
|
-
class Rank
|
5
|
-
include Comparable
|
6
|
-
attr_accessor :value
|
7
|
-
|
8
|
-
HIGH_CARD = 0
|
9
|
-
PAIR = 1
|
10
|
-
TWO_PAIR = 2
|
11
|
-
THREE_OF_A_KIND = 3
|
12
|
-
STRAIGHT = 4
|
13
|
-
FLUSH = 5
|
14
|
-
FULL_HOUSE = 6
|
15
|
-
FOUR_OF_A_KIND = 7
|
16
|
-
STRAIGHT_FLUSH = 8
|
17
|
-
ROYAL_FLUSH = 9
|
18
|
-
|
19
|
-
# Creates a new Rank instance of <code>value</code>
|
20
|
-
# Values can be 0-9. Consider using the Rank constants
|
21
|
-
# for the hand values.
|
22
|
-
def initialize value
|
23
|
-
@value = value
|
24
|
-
end
|
25
|
-
|
26
|
-
# Returns the text representation of the poker rank. Ex. "Two Pair"
|
27
|
-
def to_s
|
28
|
-
case @value
|
29
|
-
when 0: "High Card"
|
30
|
-
when 1: "Pair"
|
31
|
-
when 2: "Two Pair"
|
32
|
-
when 3: "Three of a Kind"
|
33
|
-
when 4: "Straight"
|
34
|
-
when 5: "Flush"
|
35
|
-
when 6: "Full House"
|
36
|
-
when 7: "Four of a Kind"
|
37
|
-
when 8: "Straight Flush"
|
38
|
-
when 9: "Royal Flush"
|
39
|
-
else "Unknown"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def <=> other
|
44
|
-
@value <=> other.value
|
45
|
-
end
|
46
|
-
end # class Rank
|
47
|
-
|
48
|
-
end
|
data/test/tc_poker_hand.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
require 'test/unit'
|
3
|
-
require 'rubygems'
|
4
|
-
require 'ruby-poker'
|
5
|
-
|
6
|
-
class TestPokerHand < Test::Unit::TestCase
|
7
|
-
def setup
|
8
|
-
@hand1 = PokerHand.new("6D 7C 5D 5H 3S")
|
9
|
-
@hand2 = PokerHand.new(["5C", "JC", "2H", "5S", "3D"])
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_comparable
|
13
|
-
assert_equal(-1, @hand1 <=> @hand2)
|
14
|
-
end
|
15
|
-
end
|