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 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@gmail.com
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
- This class handles poker logic for 5 card poker hands.
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) can be created using their
28
- value (10, 11, 12, 13, 14) or letter representation (T, J, Q, K, A)
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", "13D", "14H"])
36
- puts hand1 => 8H 9C 10C 11D 12H
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 => 3D 3C 3S 13D 14H
39
- puts hand2.rank => Three of a Kind
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
@@ -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", "13D", "14H"])
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 PokerHand
2
-
3
- class Card
4
- include Comparable
5
- attr_reader :suit, :value
6
-
7
- def initialize card_str
8
- card_str = card_str.gsub("T","10").gsub("J","11").gsub("Q","12").gsub("K","13").gsub("A","14")
9
- @value = card_str[0, card_str.length-1].to_i
10
- @suit = card_str[-1,1]
11
- end
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
- def <=> card2
14
- @value <=> card2.value
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
- def to_s
18
- @value.to_s + @suit
19
- end
20
- end # class Card
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
- end # class PokerHand
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 'poker_hand'
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.1.2
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 00:00:00 -08:00
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/tc_poker_hand.rb
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/tc_poker_hand.rb
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
@@ -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