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 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