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 +49 -0
- data/LICENSE +27 -0
- data/README.rdoc +51 -0
- data/Rakefile +71 -0
- data/examples/deck.rb +29 -0
- data/examples/quick_example.rb +11 -0
- data/lib/card.rb +125 -0
- data/lib/ruby-poker.rb +401 -0
- data/ruby-poker.gemspec +32 -0
- data/test/test_card.rb +59 -0
- data/test/test_poker_hand.rb +210 -0
- metadata +71 -0
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
|
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
|
data/ruby-poker.gemspec
ADDED
@@ -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
|