robolson-ruby-poker 0.3.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 ADDED
@@ -0,0 +1,49 @@
1
+ 2008-12-30 (0.3.1)
2
+ * Fixed bug (#20407) Raise an exception when creating a new hand with duplicates
3
+ * Added PokerHand#uniq method
4
+ * Removed deprecated `Gem::manage_gems` from Rakefile
5
+ 2008-05-17 (0.3.0)
6
+ * Changed Card#== to compare based on card suit and face value. Before it only compared the face value of two cards. Warning: This change may potentially break your program if you were comparing Card objects directly.
7
+ * Replaced `PokerHand#arranged_hand` with `PokerHand#sort_using_rank` which is more descriptive. This loosely corresponds to bug #20194.
8
+ * Bug [#20196] 'rank' goes into an infinite loop.
9
+ * Bug [#20195] Allows the same card to be entered into the hand.
10
+ * Bug [#20344] sort_using_rank does not return expected results
11
+
12
+ 2008-04-20 (0.2.4)
13
+ * Modernized the Rakefile
14
+ * Updated to be compatible with Ruby 1.9
15
+
16
+ 2008-04-06 (0.2.2)
17
+ * Fixed bug where two hands that had the same values but different suits returned not equal
18
+
19
+ 2008-02-08 (0.2.1)
20
+ * Cards can be added to a hand after it is created by using (<<) on a PokerHand
21
+ * Cards can be deleted from a hand with PokerHand.delete()
22
+
23
+ 2008-01-21 (0.2.0)
24
+ * Merged Patrick Hurley's poker solver
25
+ * Added support for hands with >5 cards
26
+ * Straights with a low Ace count now
27
+ * to_s on a PokerHand now includes the rank after the card list
28
+ * Finally wrote the Unit Tests suite
29
+
30
+ 2008-01-12 (0.1.2)
31
+ * Fixed critical bug that was stopping the whole program to not work
32
+ * Added some test cases as a result
33
+ * More test cases coming soon
34
+
35
+ 2008-01-12 (0.1.1)
36
+ * Ranks are now a class.
37
+ * Extracted card, rank, and arrays methods to individual files
38
+ * Added gem packaging
39
+
40
+ 2008-01-10 (0.1.0)
41
+ * Initial version
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2008, Robert Olson
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in
12
+ the documentation and/or other materials provided with the distribution.
13
+ * Neither the name of the author nor the names of its
14
+ contributors may be used to endorse or promote products derived
15
+ from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = Poker library in Ruby
2
+
3
+ == Author
4
+
5
+ Rob Olson (rko618 [at] gmail)
6
+
7
+ == Description
8
+
9
+ Ruby-Poker handles the logic for getting the rank of a poker hand. It can also be used to compare two or more hands to determine which hand has the highest poker value.
10
+
11
+ Card representations can be passed to the PokerHand constructor as a string or an array. Face cards (cards ten, jack, queen, king, and ace) are created using their letter representation (T, J, Q, K, A).
12
+
13
+ == Install
14
+
15
+ sudo gem install ruby-poker
16
+
17
+ == Examples
18
+
19
+ In this section some examples show what can be done with this class.
20
+
21
+ require 'rubygems'
22
+ require 'ruby-poker'
23
+
24
+ hand1 = PokerHand.new("8H 9C TC JD QH")
25
+ hand2 = PokerHand.new(["3D", "3C", "3S", "KD", "AH"])
26
+ puts hand1 => 8h 9c Tc Jd Qh (Straight)
27
+ puts hand1.just_cards => 8h 9c Tc Jd Qh
28
+ puts hand1.rank => Straight
29
+ puts hand2 => 3d 3c 3s Kd Ah (Three of a kind)
30
+ puts hand2.rank => Three of a kind
31
+ puts hand1 > hand2 => true
32
+
33
+ == Duplicates
34
+
35
+ By default ruby-poker will not raise an exception if you add the same card to a hand twice. You can tell ruby-poker to not allow duplicates by doing the following
36
+
37
+ PokerHand.allow_duplicates = false
38
+
39
+ Place that line near the beginning of your program. The change is program wide so once allow_duplicates is set to false, _all_ poker hands will raise an exception if a duplicate card is added to the hand.
40
+
41
+ == Compatibility
42
+
43
+ Ruby-Poker is compatible with Ruby 1.8 and Ruby 1.9.
44
+
45
+ == History
46
+
47
+ In the 0.2.0 release Patrick Hurley's Texas Holdem code from http://rubyquiz.com/quiz24.html was merged into ruby-poker.
48
+
49
+ == License
50
+
51
+ This is free software; you can redistribute it and/or modify it under the terms of the BSD license. See LICENSE for more details.
data/Rakefile ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake/rdoctask'
4
+ require "rake/testtask"
5
+ require 'rake/gempackagetask'
6
+
7
+ begin
8
+ require "rubygems"
9
+ rescue LoadError
10
+ nil
11
+ end
12
+
13
+ RUBYPOKER_VERSION = "0.3.0"
14
+
15
+ Gem::Specification.new do |s|
16
+ s.name = "ruby-poker"
17
+ s.version = RUBYPOKER_VERSION
18
+ s.date = "2008-05-17"
19
+ s.rubyforge_project = "rubypoker"
20
+ s.platform = Gem::Platform::RUBY
21
+ s.summary = "Poker library in Ruby"
22
+ s.description = "Ruby library for comparing poker hands and determining the winner."
23
+ s.author = "Rob Olson"
24
+ s.email = "rko618@gmail.com"
25
+ s.homepage = "http://github.com/robolson/ruby-poker"
26
+ s.has_rdoc = true
27
+ s.files = ["CHANGELOG",
28
+ "examples/deck.rb",
29
+ "examples/quick_example.rb",
30
+ "lib/card.rb",
31
+ "lib/ruby-poker.rb",
32
+ "LICENSE",
33
+ "Rakefile",
34
+ "README.rdoc",
35
+ "ruby-poker.gemspec"]
36
+ s.test_files = ["test/test_card.rb",
37
+ "test/test_poker_hand.rb"]
38
+ s.require_paths << 'lib'
39
+
40
+ s.extra_rdoc_files = ["README", "CHANGELOG", "LICENSE"]
41
+ s.rdoc_options << '--title' << 'Ruby Poker Documentation' <<
42
+ '--main' << 'README.rdoc' <<
43
+ '--inline-source' << '-q'
44
+
45
+ # s.add_dependency("thoughtbot-shoulda", ["> 2.0.0"])
46
+ end
47
+
48
+ Rake::GemPackageTask.new(spec) do |pkg|
49
+ pkg.need_tar = true
50
+ pkg.need_zip = true
51
+ end
52
+
53
+ Rake::TestTask.new do |test|
54
+ test.libs << "test"
55
+ test.test_files = Dir[ "test/test_*.rb" ]
56
+ test.verbose = true
57
+ test.warning = true
58
+ end
59
+
60
+ desc "Start autotest"
61
+ task :autotest do
62
+ ruby "-I lib -w /usr/bin/autotest"
63
+ end
64
+
65
+ Rake::RDocTask.new(:docs) do |rdoc|
66
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG', 'LICENSE', 'lib/')
67
+ rdoc.main = 'README.rdoc'
68
+ rdoc.rdoc_dir = 'doc/html'
69
+ rdoc.title = 'Ruby Poker Documentation'
70
+ rdoc.options << '--inline-source'
71
+ end
data/examples/deck.rb ADDED
@@ -0,0 +1,29 @@
1
+ # This is a sample Deck implementation.
2
+ class Deck
3
+ def shuffle
4
+ deck_size = @cards.size
5
+ (deck_size * 2).times do
6
+ pos1, pos2 = rand(deck_size), rand(deck_size)
7
+ @cards[pos1], @cards[pos2] = @cards[pos2], @cards[pos1]
8
+ end
9
+ end
10
+
11
+ def initialize
12
+ @cards = []
13
+ Card::SUITS.each_byte do |suit|
14
+ # careful not to double include the aces...
15
+ Card::FACES[1..-1].each_byte do |face|
16
+ @cards.push(Card.new(face.chr, suit.chr))
17
+ end
18
+ end
19
+ shuffle()
20
+ end
21
+
22
+ def deal
23
+ @cards.pop
24
+ end
25
+
26
+ def empty?
27
+ @cards.empty?
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'ruby-poker'
3
+
4
+ hand1 = PokerHand.new("8H 9C TC JD QH")
5
+ hand2 = PokerHand.new(["3D", "3C", "3S", "KD", "AH"])
6
+ puts hand1
7
+ puts hand1.just_cards
8
+ puts hand1.rank
9
+ puts hand2
10
+ puts hand2.rank
11
+ puts hand1 > hand2
data/lib/card.rb ADDED
@@ -0,0 +1,125 @@
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
+ }
30
+
31
+ def Card.face_value(face)
32
+ if (face)
33
+ FACE_VALUES[face.upcase] - 1
34
+ else
35
+ nil
36
+ end
37
+ end
38
+
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
60
+
61
+ # Constructs this card object from another card object
62
+ def build_from_card(card)
63
+ @value = card.value
64
+ @suit = card.suit
65
+ @face = card.face
66
+ end
67
+
68
+ public
69
+
70
+ def initialize(*value)
71
+ if (value.size == 1)
72
+ if (value[0].respond_to?(:to_card))
73
+ build_from_card(value[0])
74
+ elsif (value[0].respond_to?(:to_str))
75
+ build_from_string(value[0])
76
+ elsif (value[0].respond_to?(:to_int))
77
+ build_from_value(value[0])
78
+ end
79
+ elsif (value.size == 2)
80
+ if (value[0].respond_to?(:to_str) &&
81
+ value[1].respond_to?(:to_str))
82
+ build_from_face_suit(value[0], value[1])
83
+ elsif (value[0].respond_to?(:to_int) &&
84
+ value[1].respond_to?(:to_int))
85
+ build_from_face_suit_values(value[0], value[1])
86
+ end
87
+ end
88
+ end
89
+
90
+ attr_reader :suit, :face, :value
91
+ include Comparable
92
+
93
+ # Returns a string containing the representation of Card
94
+ #
95
+ # Card.new("7c").to_s # => "7c"
96
+ def to_s
97
+ FACES[@face].chr + SUITS[@suit].chr
98
+ end
99
+
100
+ # If to_card is called on a `Card` it should return itself
101
+ def to_card
102
+ self
103
+ end
104
+
105
+ # Compare the face value of this card with another card. Returns:
106
+ # -1 if self is less than card2
107
+ # 0 if self is the same face value of card2
108
+ # 1 if self is greater than card2
109
+ def <=> card2
110
+ @face <=> card2.face
111
+ end
112
+
113
+ # Returns true if the cards are the same card. Meaning they
114
+ # have the same suit and the same face value.
115
+ def == card2
116
+ @value == card2.value
117
+ end
118
+ alias :eql? :==
119
+
120
+ # Compute a hash-code for this Card. Two Cards with the same
121
+ # content will have the same hash code (and will compare using eql?).
122
+ def hash
123
+ @value.hash
124
+ end
125
+ end
data/lib/ruby-poker.rb ADDED
@@ -0,0 +1,401 @@
1
+ require 'card.rb'
2
+
3
+ class PokerHand
4
+ include Comparable
5
+ attr_reader :hand
6
+
7
+ @@allow_duplicates = true # true by default
8
+ def self.allow_duplicates; @@allow_duplicates; end
9
+ def self.allow_duplicates=(v); @@allow_duplicates = v; end
10
+
11
+ # Returns a new PokerHand object. Accepts the cards represented
12
+ # in a string or an array
13
+ #
14
+ # PokerHand.new("3d 5c 8h Ks") # => #<PokerHand:0x5c673c ...
15
+ # PokerHand.new(["3d", "5c", "8h", "Ks"]) # => #<PokerHand:0x5c2d6c ...
16
+ def initialize(cards = [])
17
+ if cards.is_a? Array
18
+ @hand = cards.map do |card|
19
+ if card.is_a? Card
20
+ card
21
+ else
22
+ Card.new(card.to_s)
23
+ end
24
+ end
25
+ elsif cards.respond_to?(:to_str)
26
+ @hand = cards.scan(/\S{2,3}/).map { |str| Card.new(str) }
27
+ else
28
+ @hand = cards
29
+ end
30
+
31
+ check_for_duplicates if !@@allow_duplicates
32
+ end
33
+
34
+ # Returns a new PokerHand object with the cards sorted by suit
35
+ # The suit order is spades, hearts, diamonds, clubs
36
+ #
37
+ # PokerHand.new("3d 5c 8h Ks").by_suit.just_cards # => "Ks 8h 3d 5c"
38
+ def by_suit
39
+ PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
40
+ end
41
+
42
+ # Returns a new PokerHand object with the cards sorted by value
43
+ # with the highest value first.
44
+ #
45
+ # PokerHand.new("3d 5c 8h Ks").by_face.just_cards # => "Ks 8h 5c 3d"
46
+ def by_face
47
+ PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
48
+ end
49
+
50
+ # Returns string representation of the hand without the rank
51
+ #
52
+ # PokerHand.new(["3c", "Kh"]).just_cards # => "3c Kh"
53
+ def just_cards
54
+ @hand.join(" ")
55
+ end
56
+ alias :cards :just_cards
57
+
58
+ # Returns an array of the card values in the hand.
59
+ # The values returned are 1 less than the value on the card.
60
+ # For example: 2's will be shown as 1.
61
+ #
62
+ # PokerHand.new(["3c", "Kh"]).face_values # => [2, 12]
63
+ def face_values
64
+ @hand.map { |c| c.face }
65
+ end
66
+
67
+ # The =~ method does a regular expression match on the cards in this hand.
68
+ # This can be useful for many purposes. A common use is the check if a card
69
+ # exists in a hand.
70
+ #
71
+ # PokerHand.new("3d 4d 5d") =~ /8h/ # => nil
72
+ # PokerHand.new("3d 4d 5d") =~ /4d/ # => #<MatchData:0x615e18>
73
+ def =~ (re)
74
+ re.match(just_cards)
75
+ end
76
+
77
+ def royal_flush?
78
+ if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
79
+ [[10], arrange_hand(md)]
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ def straight_flush?
86
+ if (md = (/.(.)(.)(?: 1.\2){4}/.match(delta_transform(true))))
87
+ high_card = Card::face_value(md[1])
88
+ arranged_hand = fix_low_ace_display(md[0] + ' ' +
89
+ md.pre_match + ' ' + md.post_match)
90
+ [[9, high_card], arranged_hand]
91
+ else
92
+ false
93
+ end
94
+ end
95
+
96
+ def four_of_a_kind?
97
+ if (md = (by_face =~ /(.). \1. \1. \1./))
98
+ # get kicker
99
+ (md.pre_match + md.post_match).match(/(\S)/)
100
+ [
101
+ [8, Card::face_value(md[1]), Card::face_value($1)],
102
+ arrange_hand(md)
103
+ ]
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ def full_house?
110
+ if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./))
111
+ arranged_hand = arrange_hand(md[0] + ' ' +
112
+ md.pre_match + ' ' + md[2] + ' ' + md.post_match)
113
+ [
114
+ [7, Card::face_value(md[1]), Card::face_value(md[3])],
115
+ arranged_hand
116
+ ]
117
+ elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/))
118
+ arranged_hand = arrange_hand(md[4] + ' ' + md[1] + ' ' +
119
+ md.pre_match + ' ' + md[3] + ' ' + md.post_match)
120
+ [
121
+ [7, Card::face_value(md[5]), Card::face_value(md[2])],
122
+ arranged_hand
123
+ ]
124
+ else
125
+ false
126
+ end
127
+ end
128
+
129
+ def flush?
130
+ if (md = (by_suit =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/))
131
+ [
132
+ [
133
+ 6,
134
+ Card::face_value(md[1]),
135
+ *(md[3..6].map { |f| Card::face_value(f) })
136
+ ],
137
+ arrange_hand(md)
138
+ ]
139
+ else
140
+ false
141
+ end
142
+ end
143
+
144
+ def straight?
145
+ result = false
146
+ if hand.size >= 5
147
+ transform = delta_transform
148
+ # note we can have more than one delta 0 that we
149
+ # need to shuffle to the back of the hand
150
+ i = 0
151
+ until transform.match(/^\S{3}( [1-9x]\S\S)+( 0\S\S)*$/) or i >= hand.size do
152
+ # only do this once per card in the hand to avoid entering an
153
+ # infinite loop if all of the cards in the hand are the same
154
+ transform.gsub!(/(\s0\S\S)(.*)/, "\\2\\1") # moves the front card to the back of the string
155
+ i += 1
156
+ end
157
+ if (md = (/.(.). 1.. 1.. 1.. 1../.match(transform)))
158
+ high_card = Card::face_value(md[1])
159
+ arranged_hand = fix_low_ace_display(md[0] + ' ' + md.pre_match + ' ' + md.post_match)
160
+ result = [[5, high_card], arranged_hand]
161
+ end
162
+ end
163
+ end
164
+
165
+ def three_of_a_kind?
166
+ if (md = (by_face =~ /(.). \1. \1./))
167
+ # get kicker
168
+ arranged_hand = arrange_hand(md)
169
+ arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
170
+ [
171
+ [
172
+ 4,
173
+ Card::face_value(md[1]),
174
+ Card::face_value($1),
175
+ Card::face_value($2)
176
+ ],
177
+ arranged_hand
178
+ ]
179
+ else
180
+ false
181
+ end
182
+ end
183
+
184
+ def two_pair?
185
+ # \1 is the face value of the first pair
186
+ # \2 is the card in between the first pair and the second pair
187
+ # \3 is the face value of the second pair
188
+ if (md = (by_face =~ /(.). \1.(.*) (.). \3./))
189
+ # to get the kicker this does the following
190
+ # md[0] is the regex matched above which includes the first pair and
191
+ # the second pair but also some cards in the middle so we sub them out
192
+ # then we add on the cards that came before the first pair, the cards that
193
+ # we in between, and the cards that came after.
194
+ arranged_hand = arrange_hand(md[0].sub(md[2], '') + ' ' +
195
+ md.pre_match + ' ' + md[2] + ' ' + md.post_match)
196
+ arranged_hand.match(/(?:\S\S ){4}(\S)/)
197
+ [
198
+ [
199
+ 3,
200
+ Card::face_value(md[1]), # face value of the first pair
201
+ Card::face_value(md[3]), # face value of the second pair
202
+ Card::face_value($1) # face value of the kicker
203
+ ],
204
+ arranged_hand
205
+ ]
206
+ else
207
+ false
208
+ end
209
+ end
210
+
211
+ def pair?
212
+ if (md = (by_face =~ /(.). \1./))
213
+ # get kicker
214
+ arranged_hand = arrange_hand(md)
215
+ arranged_hand.match(/(?:\S\S ){2}(\S)\S\s+(\S)\S\s+(\S)/)
216
+ [
217
+ [
218
+ 2,
219
+ Card::face_value(md[1]),
220
+ Card::face_value($1),
221
+ Card::face_value($2),
222
+ Card::face_value($3)
223
+ ],
224
+ arranged_hand
225
+ ]
226
+ else
227
+ false
228
+ end
229
+ end
230
+
231
+ def highest_card?
232
+ result = by_face
233
+ [[1, *result.face_values[0..4]], result.hand.join(' ')]
234
+ end
235
+
236
+ OPS = [
237
+ ['Royal Flush', :royal_flush? ],
238
+ ['Straight Flush', :straight_flush? ],
239
+ ['Four of a kind', :four_of_a_kind? ],
240
+ ['Full house', :full_house? ],
241
+ ['Flush', :flush? ],
242
+ ['Straight', :straight? ],
243
+ ['Three of a kind', :three_of_a_kind?],
244
+ ['Two pair', :two_pair? ],
245
+ ['Pair', :pair? ],
246
+ ['Highest Card', :highest_card? ],
247
+ ]
248
+
249
+ # Returns the verbose hand rating
250
+ #
251
+ # PokerHand.new("4s 5h 6c 7d 8s").hand_rating # => "Straight"
252
+ def hand_rating
253
+ OPS.map { |op|
254
+ (method(op[1]).call()) ? op[0] : false
255
+ }.find { |v| v }
256
+ end
257
+
258
+ alias :rank :hand_rating
259
+
260
+ def score
261
+ OPS.map { |op|
262
+ method(op[1]).call()
263
+ }.find([0]) { |score| score }
264
+ end
265
+
266
+ # Returns a string of the hand arranged based on its rank. Usually this will be the
267
+ # same as by_face but there are some cases where it makes a difference.
268
+ #
269
+ # ph = PokerHand.new("AS 3S 5S 2S 4S")
270
+ # ph.sort_using_rank # => "5s 4s 3s 2s As"
271
+ # ph.by_face.just_cards # => "As 5s 4s 3s 2s"
272
+ def sort_using_rank
273
+ score[1]
274
+ end
275
+
276
+ # Returns string with a listing of the cards in the hand followed by the hand's rank.
277
+ #
278
+ # h = PokerHand.new("8c 8s")
279
+ # h.to_s # => "8c 8s (Pair)"
280
+ def to_s
281
+ just_cards + " (" + hand_rating + ")"
282
+ end
283
+
284
+ # Returns an array of `Card` objects that make up the `PokerHand`.
285
+ def to_a
286
+ @hand
287
+ end
288
+
289
+ alias :to_ary :to_a
290
+
291
+ def <=> other_hand
292
+ self.score[0] <=> other_hand.score[0]
293
+ end
294
+
295
+ # Add a card to the hand
296
+ #
297
+ # hand = PokerHand.new("5d")
298
+ # hand << "6s" # => Add a six of spades to the hand by passing a string
299
+ # hand << ["7h", "8d"] # => Add multiple cards to the hand using an array
300
+ def << new_cards
301
+ if new_cards.is_a?(Card) || new_cards.is_a?(String)
302
+ new_cards = [new_cards]
303
+ end
304
+
305
+ new_cards.each do |nc|
306
+ unless @@allow_duplicates
307
+ raise "A card with the value #{nc} already exists in this hand. Set PokerHand.allow_duplicates to true if you want to be able to add a card more than once." if self =~ /#{nc}/
308
+ end
309
+
310
+ @hand << Card.new(nc)
311
+ end
312
+ end
313
+
314
+ # Remove a card from the hand.
315
+ #
316
+ # hand = PokerHand.new("5d Jd")
317
+ # hand.delete("Jd") # => #<Card:0x5d0674 @value=23, @face=10, @suit=1>
318
+ # hand.just_cards # => "5d"
319
+ def delete card
320
+ @hand.delete(Card.new(card))
321
+ end
322
+
323
+ # Same concept as Array#uniq
324
+ def uniq
325
+ PokerHand.new(@hand.uniq)
326
+ end
327
+
328
+ # Resolving methods are just passed directly down to the @hand array
329
+ RESOLVING_METHODS = [:size, :+, :-]
330
+ RESOLVING_METHODS.each do |method|
331
+ class_eval %{
332
+ def #{method}(*args, &block)
333
+ @hand.#{method}(*args, &block)
334
+ end
335
+ }
336
+ end
337
+
338
+ protected
339
+
340
+ def check_for_duplicates
341
+ if @hand.size != @hand.uniq.size && !@@allow_duplicates
342
+ raise "You are attempting to create a hand that contains duplicate cards. Set PokerHand.allow_duplicates to true if you do not want to ignore this error."
343
+ end
344
+ end
345
+
346
+ # if md is a string, arrange_hand will remove extra white space
347
+ # if md is a MatchData, arrange_hand returns the matched segment
348
+ # followed by the pre_match and the post_match
349
+ def arrange_hand(md)
350
+ hand = if (md.respond_to?(:to_str))
351
+ md
352
+ else
353
+ md[0] + ' ' + md.pre_match + md.post_match
354
+ end
355
+ hand.strip.squeeze(" ") # remove extra whitespace
356
+ end
357
+
358
+ def delta_transform(use_suit = false)
359
+ aces = @hand.select { |c| c.face == Card::face_value('A') }
360
+ aces.map! { |c| Card.new(1,c.suit) }
361
+
362
+ base = if (use_suit)
363
+ (@hand + aces).sort_by { |c| [c.suit, c.face] }.reverse
364
+ else
365
+ (@hand + aces).sort_by { |c| [c.face, c.suit] }.reverse
366
+ end
367
+
368
+ result = base.inject(['',nil]) do |(delta_hand, prev_card), card|
369
+ if (prev_card)
370
+ delta = prev_card - card.face
371
+ else
372
+ delta = 0
373
+ end
374
+ # does not really matter for my needs
375
+ delta = 'x' if (delta > 9 || delta < 0)
376
+ delta_hand += delta.to_s + card.to_s + ' '
377
+ [delta_hand, card.face]
378
+ end
379
+
380
+ # we just want the delta transform, not the last cards face too
381
+ result[0].chop
382
+ end
383
+
384
+ def fix_low_ace_display(arranged_hand)
385
+ # remove card deltas (this routine is only used for straights)
386
+ arranged_hand.gsub!(/\S(\S\S)\s*/, "\\1 ")
387
+
388
+ # Fix "low aces"
389
+ arranged_hand.gsub!(/L(\S)/, "A\\1")
390
+
391
+ # Remove duplicate aces (this will not work if you have
392
+ # multiple decks or wild cards)
393
+ arranged_hand.gsub!(/((A\S).*)\2/, "\\1")
394
+
395
+ # cleanup white space
396
+ arranged_hand.gsub!(/\s+/, ' ')
397
+ # careful to use gsub as gsub! can return nil here
398
+ arranged_hand.gsub(/\s+$/, '')
399
+ end
400
+
401
+ end
@@ -0,0 +1,32 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "ruby-poker"
3
+ s.version = "0.3.0"
4
+ s.date = "2008-05-17"
5
+ s.rubyforge_project = "rubypoker"
6
+ s.platform = Gem::Platform::RUBY
7
+ s.summary = "Poker library in Ruby"
8
+ s.description = "Ruby library for comparing poker hands and determining the winner."
9
+ s.author = "Rob Olson"
10
+ s.email = "rko618@gmail.com"
11
+ s.homepage = "http://github.com/robolson/ruby-poker"
12
+ s.has_rdoc = true
13
+ s.files = ["CHANGELOG",
14
+ "examples/deck.rb",
15
+ "examples/quick_example.rb",
16
+ "lib/card.rb",
17
+ "lib/ruby-poker.rb",
18
+ "LICENSE",
19
+ "Rakefile",
20
+ "README.rdoc",
21
+ "ruby-poker.gemspec"]
22
+ s.test_files = ["test/test_card.rb",
23
+ "test/test_poker_hand.rb"]
24
+ s.require_paths << 'lib'
25
+
26
+ s.extra_rdoc_files = ["README", "CHANGELOG", "LICENSE"]
27
+ s.rdoc_options << '--title' << 'Ruby Poker Documentation' <<
28
+ '--main' << 'README.rdoc' <<
29
+ '--inline-source' << '-q'
30
+
31
+ # s.add_dependency("thoughtbot-shoulda", ["> 2.0.0"])
32
+ end
data/test/test_card.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'test/unit'
2
+ require 'card.rb'
3
+
4
+ class TestCard < Test::Unit::TestCase
5
+ def setup
6
+ # testing various input formats for cards
7
+ @c1 = Card.new("9c")
8
+ @c2 = Card.new("TD")
9
+ @c3 = Card.new("jh")
10
+ @c4 = Card.new("qS")
11
+ end
12
+
13
+ def test_build_from_card
14
+ c1 = Card.new("2c")
15
+ c2 = Card.new(c1)
16
+ assert_equal("2c", c2.to_s)
17
+ end
18
+
19
+ def test_class_face_value
20
+ assert_equal(0, Card.face_value('L'))
21
+ assert_equal(13, Card.face_value('A'))
22
+ end
23
+
24
+ def test_face
25
+ assert_equal(8, @c1.face)
26
+ assert_equal(9, @c2.face)
27
+ assert_equal(10, @c3.face)
28
+ assert_equal(11, @c4.face)
29
+ end
30
+
31
+ def test_suit
32
+ assert_equal(0, @c1.suit)
33
+ assert_equal(1, @c2.suit)
34
+ assert_equal(2, @c3.suit)
35
+ assert_equal(3, @c4.suit)
36
+ end
37
+
38
+ def test_value
39
+ assert_equal(7, @c1.value)
40
+ assert_equal(22, @c2.value)
41
+ assert_equal(37, @c3.value)
42
+ assert_equal(52, @c4.value)
43
+ end
44
+
45
+ def test_comparison
46
+ assert(@c1 < @c2)
47
+ assert(@c3 > @c2)
48
+ end
49
+
50
+ def test_equals
51
+ c = Card.new("9h")
52
+ assert_not_equal(@c1, c)
53
+ assert_equal(@c1, @c1)
54
+ end
55
+
56
+ def test_hash
57
+ assert_equal(15, @c1.hash)
58
+ end
59
+ end
@@ -0,0 +1,210 @@
1
+ require 'ruby-poker.rb'
2
+ require 'rubygems'
3
+ require 'shoulda'
4
+
5
+ class TestPokerHand < Test::Unit::TestCase
6
+ context "A PokerHand instance" do
7
+
8
+ setup do
9
+ @trips = PokerHand.new("2D 9C AS AH AC")
10
+ @full_boat = PokerHand.new(["2H", "2D", "4C", "4D", "4S"])
11
+ @flush = PokerHand.new("3D 6D 7D TD QD 5H 2S")
12
+ @straight = PokerHand.new("8H 9D TS JH QC AS")
13
+ end
14
+
15
+ # there are a lot of combinations that should be tested here. I will add more
16
+ # troublesome cases as I think of them.
17
+ should "sort using rank" do
18
+ assert_equal("As Ah Ac 9c 2d", @trips.sort_using_rank)
19
+ assert_equal("4s 4d 4c 2h 2d", @full_boat.sort_using_rank)
20
+ assert_equal("Qd Td 7d 6d 3d 2s 5h", @flush.sort_using_rank)
21
+ assert_equal("Qc Jh Ts 9d 8h As", @straight.sort_using_rank)
22
+
23
+ assert_equal("As Ah 3d 3c Kd", PokerHand.new("AS AH KD 3D 3C").sort_using_rank)
24
+ assert_equal("As Ah 3d 3c 2d", PokerHand.new("2D AS AH 3D 3C").sort_using_rank)
25
+ end
26
+
27
+ should "return card sorted by face value" do
28
+ assert_equal([13, 13, 13, 8, 1], @trips.by_face.hand.collect {|c| c.face})
29
+ end
30
+
31
+ should "return cards sorted by suit" do
32
+ assert_equal([3, 2, 1, 0, 0], @trips.by_suit.hand.collect {|c| c.suit})
33
+ end
34
+
35
+ should "return just the face values of the cards" do
36
+ assert_equal([1, 8, 13, 13, 13], @trips.face_values)
37
+ end
38
+
39
+ should "recognize a straight flush" do
40
+ assert !@flush.straight_flush?
41
+ assert !@straight.straight_flush?
42
+ assert PokerHand.new("8H 9H TH JH QH AS").straight_flush?
43
+ end
44
+
45
+ should "recognize a royal flush" do
46
+ assert !@flush.royal_flush?
47
+ assert PokerHand.new("AD KD QD JD TD").royal_flush?
48
+ end
49
+
50
+ should "recognize a flush" do
51
+ assert @flush.flush?
52
+ assert !@trips.flush?
53
+ end
54
+
55
+ should "recognize a four of a kind" do
56
+ assert !@trips.four_of_a_kind?
57
+ assert PokerHand.new("AD 9C AS AH AC")
58
+ end
59
+
60
+ should "recognize a full house" do
61
+ assert !@trips.full_house?
62
+ assert @full_boat.full_house?
63
+ end
64
+
65
+ should "recognize a straight" do
66
+ assert @straight.straight?
67
+ assert PokerHand.new("AH 2S 3D 4H 5D").straight?
68
+ end
69
+
70
+ should "recognize a three of a kind" do
71
+ assert @trips.three_of_a_kind?
72
+ end
73
+
74
+ should "recognize a two pair" do
75
+ assert PokerHand.new("2S 2D TH TD 4S").two_pair?
76
+ assert !PokerHand.new("6D 7C 5D 5H 3S").two_pair?
77
+ end
78
+
79
+ should "recognize a pair" do
80
+ assert !PokerHand.new("5C JC 2H 7S 3D").pair?
81
+ assert PokerHand.new("6D 7C 5D 5H 3S").pair?
82
+ end
83
+
84
+ should "recognize a hand with the rank highest_card" do
85
+ # hard to test, make sure it does not return null
86
+ assert PokerHand.new("2D 4S 6C 8C TH").highest_card?
87
+ end
88
+
89
+ should "have an instance variable hand that is an array of Cards" do
90
+ assert_instance_of Card, @trips.hand[0]
91
+ end
92
+
93
+ should "return the hand's rating as a string" do
94
+ assert_equal "Three of a kind", @trips.hand_rating
95
+ assert_equal "Full house", @full_boat.hand_rating
96
+ end
97
+
98
+ should "respond to rank" do
99
+ # rank is an alias for hand_rating
100
+ assert_respond_to @trips, :rank
101
+ end
102
+
103
+ should "return the hand as a string" do
104
+ assert_equal("2d 9c As Ah Ac", @trips.just_cards)
105
+ end
106
+
107
+ should "return the hand's score" do
108
+ assert_equal([4, 13, 8, 1], @trips.score[0])
109
+ end
110
+
111
+ should "be able to match regular expressions" do
112
+ assert_match(/9c/, @trips.to_s)
113
+ assert_no_match(/AD/, @trips.to_s)
114
+ end
115
+
116
+ should "return the correct number of cards in the hand" do
117
+ assert_equal(2, PokerHand.new("2c 3d").size)
118
+ end
119
+
120
+ should "be comparable to other PokerHands" do
121
+ hand1 = PokerHand.new("5C JC 2H 5S 3D")
122
+ hand2 = PokerHand.new("6D 7C 5D 5H 3S")
123
+ assert_equal(1, hand1 <=> hand2)
124
+ assert_equal(-1, hand2 <=> hand1)
125
+ end
126
+
127
+ should "be considered equal to other poker hands that contain the same cards" do
128
+ assert_equal(0, @trips <=> @trips)
129
+
130
+ hand1 = PokerHand.new("Ac Qc Ks Kd 9d 3c")
131
+ hand2 = PokerHand.new("Ah Qs 9h Kh Kc 3s")
132
+ assert_equal(0, hand1 <=> hand2)
133
+ end
134
+
135
+ should "be able to add a Card to itself" do
136
+ ph = PokerHand.new()
137
+ ph << "Qd"
138
+ ph << Card.new("2D")
139
+ ph << ["3d", "4d"]
140
+ assert_equal("Qd 2d 3d 4d", ph.just_cards)
141
+ end
142
+
143
+ should "be able to delete a card" do
144
+ ph = PokerHand.new("Ac")
145
+ ph.delete("Ac")
146
+ assert_equal(Array.new, ph.hand)
147
+ end
148
+
149
+ context "when duplicates are allowed" do
150
+ setup do
151
+ PokerHand.allow_duplicates = true
152
+ end
153
+
154
+ should "create a PokerHand of unique cards" do
155
+ uniq_ph = PokerHand.new("3s 4s 3s").uniq
156
+ assert_instance_of(PokerHand, uniq_ph) # want to be sure uniq hands back a PokerHand
157
+ assert_contains(uniq_ph.hand, Card.new('3s'))
158
+ assert_contains(uniq_ph.hand, Card.new('4s'))
159
+ end
160
+
161
+ should "allow five of a kind" do
162
+ # there is no five of a kind. This just tests to make sure
163
+ # that ruby-poker doesn't crash if given 5 of the same card
164
+ ph = PokerHand.new("KS KS KS KS KS")
165
+ assert_equal("Four of a kind", ph.rank)
166
+ end
167
+
168
+ should "allow duplicates on initialize" do
169
+ assert_nothing_raised RuntimeError do
170
+ PokerHand.new("3s 3s")
171
+ end
172
+ end
173
+
174
+ should "allow duplicate card to be added after initialize" do
175
+ ph = PokerHand.new("2d")
176
+ ph << "2d"
177
+ assert_equal("2d 2d", ph.just_cards)
178
+ end
179
+ end
180
+
181
+ context "when duplicates are not allowed" do
182
+ setup do
183
+ PokerHand.allow_duplicates = false
184
+ end
185
+
186
+ should "not allow duplicates on initialize" do
187
+ PokerHand.allow_duplicates = false
188
+
189
+ assert_raise RuntimeError do
190
+ PokerHand.new("3s 3s")
191
+ end
192
+
193
+ PokerHand.allow_duplicates = true
194
+ end
195
+
196
+ should "not allow duplicates after initialize" do
197
+ PokerHand.allow_duplicates = false
198
+
199
+ ph = PokerHand.new("2d")
200
+ assert_raise RuntimeError do
201
+ ph << "2d"
202
+ end
203
+
204
+ PokerHand.allow_duplicates = true
205
+ end
206
+ end
207
+
208
+ end
209
+ end
210
+
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: robolson-ruby-poker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Rob Olson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-17 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Ruby library for comparing poker hands and determining the winner.
17
+ email: rko618@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - CHANGELOG
25
+ - LICENSE
26
+ files:
27
+ - CHANGELOG
28
+ - examples/deck.rb
29
+ - examples/quick_example.rb
30
+ - lib/card.rb
31
+ - lib/ruby-poker.rb
32
+ - LICENSE
33
+ - Rakefile
34
+ - README.rdoc
35
+ - ruby-poker.gemspec
36
+ - README
37
+ has_rdoc: true
38
+ homepage: http://github.com/robolson/ruby-poker
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --title
42
+ - Ruby Poker Documentation
43
+ - --main
44
+ - README.rdoc
45
+ - --inline-source
46
+ - -q
47
+ require_paths:
48
+ - lib
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: rubypoker
65
+ rubygems_version: 1.2.0
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: Poker library in Ruby
69
+ test_files:
70
+ - test/test_card.rb
71
+ - test/test_poker_hand.rb