ruby-poker 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|