ruby-poker 0.3.2 → 1.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG +14 -9
- data/README.rdoc +11 -7
- data/Rakefile +6 -67
- data/examples/deck.rb +3 -3
- data/lib/ruby-poker.rb +0 -1
- data/lib/ruby-poker/card.rb +56 -56
- data/lib/ruby-poker/poker_hand.rb +191 -90
- data/ruby-poker.gemspec +20 -22
- data/test/test_card.rb +53 -20
- data/test/test_helper.rb +3 -4
- data/test/test_poker_hand.rb +344 -34
- metadata +44 -44
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9070959501edf045c9d745325c51573314ecbc11
|
4
|
+
data.tar.gz: 8616e190cbcce32c2ad4f9455df760f0649fbb59
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f0dae2e671b57b3c98d092b200544b8e4423adedf4d66a4c360f202e2df39b9ef96f1e62b66286c1a78d1bd8f2cbd4f335df2c8831bffbd14fbccf292bc6a0ba
|
7
|
+
data.tar.gz: deb4dba594571df9ec74a1b368f11d86e10db12003bc849cecf19810eed73d94c252d14c4d77354332399d3876dfb2c5bfd3ef0b8346d0badc893019ddf001bc
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
2013-12-28 (1.0.0)
|
2
|
+
* Add Gemfile
|
3
|
+
* Add "Empty Hand" rank for empty hands. Fixes #1.
|
4
|
+
* Simplify internal calculation of `value` in Card class. Fixes #2.
|
5
|
+
|
1
6
|
2009-07-12 (0.3.2)
|
2
7
|
* Reorganized ruby-poker's lib folder to match the standard layout for gems. This makes ruby-poker compatible with Rip.
|
3
8
|
* Bug [#26276] improper two_pair? behavior. Applied patch by Uro.
|
@@ -6,7 +11,7 @@
|
|
6
11
|
|
7
12
|
2009-01-24 (0.3.1)
|
8
13
|
* Bug [#23623] undefined method <=> for nil:NilClass
|
9
|
-
|
14
|
+
|
10
15
|
2008-12-30 (0.3.1)
|
11
16
|
* Bug (#20407) Raise an exception when creating a new hand with duplicates
|
12
17
|
* Added PokerHand#uniq method
|
@@ -18,34 +23,34 @@
|
|
18
23
|
* Bug [#20196] 'rank' goes into an infinite loop.
|
19
24
|
* Bug [#20195] Allows the same card to be entered into the hand.
|
20
25
|
* Bug [#20344] sort_using_rank does not return expected results
|
21
|
-
|
26
|
+
|
22
27
|
2008-04-20 (0.2.4)
|
23
28
|
* Modernized the Rakefile
|
24
29
|
* Updated to be compatible with Ruby 1.9
|
25
|
-
|
30
|
+
|
26
31
|
2008-04-06 (0.2.2)
|
27
32
|
* Fixed bug where two hands that had the same values but different suits returned not equal
|
28
|
-
|
33
|
+
|
29
34
|
2008-02-08 (0.2.1)
|
30
35
|
* Cards can be added to a hand after it is created by using (<<) on a PokerHand
|
31
36
|
* Cards can be deleted from a hand with PokerHand.delete()
|
32
|
-
|
37
|
+
|
33
38
|
2008-01-21 (0.2.0)
|
34
39
|
* Merged Patrick Hurley's poker solver
|
35
40
|
* Added support for hands with >5 cards
|
36
41
|
* Straights with a low Ace count now
|
37
42
|
* to_s on a PokerHand now includes the rank after the card list
|
38
43
|
* Finally wrote the Unit Tests suite
|
39
|
-
|
44
|
+
|
40
45
|
2008-01-12 (0.1.2)
|
41
|
-
* Fixed critical bug that was
|
46
|
+
* Fixed critical bug that was causing the whole program to not work
|
42
47
|
* Added some test cases as a result
|
43
48
|
* More test cases coming soon
|
44
|
-
|
49
|
+
|
45
50
|
2008-01-12 (0.1.1)
|
46
51
|
* Ranks are now a class.
|
47
52
|
* Extracted card, rank, and arrays methods to individual files
|
48
53
|
* Added gem packaging
|
49
|
-
|
54
|
+
|
50
55
|
2008-01-10 (0.1.0)
|
51
56
|
* Initial version
|
data/README.rdoc
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
= Poker library in Ruby
|
2
2
|
===
|
3
3
|
|
4
|
-
Author:: {Rob Olson}[
|
4
|
+
Author:: {Rob Olson}[https://github.com/robolson]
|
5
5
|
Email:: [first name] [at] thinkingdigitally.com
|
6
|
-
GitHub::
|
6
|
+
GitHub:: https://github.com/robolson/ruby-poker
|
7
7
|
|
8
8
|
== Description
|
9
9
|
|
@@ -13,13 +13,13 @@ Card representations can be passed to the PokerHand constructor as a string or a
|
|
13
13
|
|
14
14
|
== Install
|
15
15
|
|
16
|
-
|
16
|
+
gem install ruby-poker
|
17
17
|
|
18
18
|
== Example
|
19
19
|
|
20
20
|
require 'rubygems'
|
21
21
|
require 'ruby-poker'
|
22
|
-
|
22
|
+
|
23
23
|
hand1 = PokerHand.new("8H 9C TC JD QH")
|
24
24
|
hand2 = PokerHand.new(["3D", "3C", "3S", "KD", "AH"])
|
25
25
|
puts hand1 => 8h 9c Tc Jd Qh (Straight)
|
@@ -34,12 +34,16 @@ Card representations can be passed to the PokerHand constructor as a string or a
|
|
34
34
|
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
|
35
35
|
|
36
36
|
PokerHand.allow_duplicates = false
|
37
|
-
|
37
|
+
|
38
38
|
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.
|
39
39
|
|
40
40
|
== Compatibility
|
41
41
|
|
42
|
-
Ruby-Poker is compatible with Ruby 1.8
|
42
|
+
Ruby-Poker is compatible with Ruby 1.8, Ruby 1.9, Ruby 2.0.
|
43
|
+
|
44
|
+
== RDoc
|
45
|
+
|
46
|
+
View the {generated documentation for ruby-poker}[http://rdoc.info/gems/ruby-poker/frames] on rdoc.info.
|
43
47
|
|
44
48
|
== History
|
45
49
|
|
@@ -47,4 +51,4 @@ In the 0.2.0 release Patrick Hurley's Texas Holdem code from http://www.rubyquiz
|
|
47
51
|
|
48
52
|
== License
|
49
53
|
|
50
|
-
This is free software; you can redistribute it and/or modify it under the terms of the BSD license. See LICENSE for more details.
|
54
|
+
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
CHANGED
@@ -1,70 +1,9 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'metric_fu'
|
6
|
-
rescue LoadError
|
7
|
-
end
|
8
|
-
|
9
|
-
RUBYPOKER_VERSION = "0.3.2"
|
10
|
-
|
11
|
-
spec = Gem::Specification.new do |s|
|
12
|
-
s.name = "ruby-poker"
|
13
|
-
s.version = RUBYPOKER_VERSION
|
14
|
-
s.date = "2009-07-27"
|
15
|
-
s.rubyforge_project = "rubypoker"
|
16
|
-
s.platform = Gem::Platform::RUBY
|
17
|
-
s.summary = "Poker library in Ruby"
|
18
|
-
s.description = "Ruby library for comparing poker hands and determining the winner."
|
19
|
-
s.author = "Rob Olson"
|
20
|
-
s.email = "rob@thinkingdigitally.com"
|
21
|
-
s.homepage = "http://github.com/robolson/ruby-poker"
|
22
|
-
s.has_rdoc = true
|
23
|
-
s.files = ["CHANGELOG",
|
24
|
-
"examples/deck.rb",
|
25
|
-
"examples/quick_example.rb",
|
26
|
-
"lib/ruby-poker.rb",
|
27
|
-
"lib/ruby-poker/card.rb",
|
28
|
-
"lib/ruby-poker/poker_hand.rb",
|
29
|
-
"LICENSE",
|
30
|
-
"Rakefile",
|
31
|
-
"README.rdoc",
|
32
|
-
"ruby-poker.gemspec"]
|
33
|
-
s.test_files = ["test/test_helper.rb", "test/test_card.rb", "test/test_poker_hand.rb"]
|
34
|
-
s.require_paths << 'lib'
|
35
|
-
|
36
|
-
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG", "LICENSE"]
|
37
|
-
s.rdoc_options << '--title' << 'Ruby Poker Documentation' <<
|
38
|
-
'--main' << 'README.rdoc' <<
|
39
|
-
'--inline-source' << '-q'
|
40
|
-
|
41
|
-
s.add_development_dependency('thoughtbot-shoulda', '> 2.0.0')
|
42
|
-
end
|
43
|
-
|
44
|
-
require 'rake/gempackagetask'
|
45
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
46
|
-
pkg.need_tar = true
|
47
|
-
pkg.need_zip = true
|
48
|
-
end
|
49
|
-
|
50
1
|
require 'rake/testtask'
|
51
|
-
Rake::TestTask.new
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
desc "Start autotest"
|
58
|
-
task :autotest do
|
59
|
-
ruby "-I lib -w /usr/bin/autotest"
|
60
|
-
end
|
61
|
-
|
62
|
-
require 'rake/rdoctask'
|
63
|
-
Rake::RDocTask.new(:docs) do |rdoc|
|
64
|
-
rdoc.main = 'README.rdoc'
|
65
|
-
rdoc.rdoc_dir = 'rdoc'
|
66
|
-
rdoc.title = "Ruby Poker #{RUBYPOKER_VERSION}"
|
67
|
-
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG', 'LICENSE', 'lib/**/*.rb')
|
2
|
+
Rake::TestTask.new do |t|
|
3
|
+
t.libs = ['lib']
|
4
|
+
t.verbose = true
|
5
|
+
t.warning = true
|
6
|
+
t.test_files = FileList['test/test_card.rb', 'test/test_poker_hand.rb']
|
68
7
|
end
|
69
8
|
|
70
|
-
task :default => :test
|
9
|
+
task :default => :test
|
data/examples/deck.rb
CHANGED
@@ -10,7 +10,7 @@ class Deck
|
|
10
10
|
end
|
11
11
|
shuffle
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def shuffle
|
15
15
|
@cards = @cards.sort_by { rand }
|
16
16
|
return self
|
@@ -21,7 +21,7 @@ class Deck
|
|
21
21
|
def deal
|
22
22
|
@cards.pop
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
# delete an array or a single card from the deck
|
26
26
|
# converts a string to a new card, if a string is given
|
27
27
|
def burn(burn_cards)
|
@@ -29,7 +29,7 @@ class Deck
|
|
29
29
|
if burn_cards.is_a?(Card) || burn_cards.is_a?(String)
|
30
30
|
burn_cards = [burn_cards]
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
burn_cards.map! do |c|
|
34
34
|
c = Card.new(c) unless c.class == Card
|
35
35
|
@cards.delete(c)
|
data/lib/ruby-poker.rb
CHANGED
data/lib/ruby-poker/card.rb
CHANGED
@@ -8,98 +8,98 @@ class Card
|
|
8
8
|
's' => 3
|
9
9
|
}
|
10
10
|
FACE_VALUES = {
|
11
|
-
'L' =>
|
12
|
-
'2' =>
|
13
|
-
'3' =>
|
14
|
-
'4' =>
|
15
|
-
'5' =>
|
16
|
-
'6' =>
|
17
|
-
'7' =>
|
18
|
-
'8' =>
|
19
|
-
'9' =>
|
20
|
-
'T' =>
|
21
|
-
'J' =>
|
22
|
-
'Q' =>
|
23
|
-
'K' =>
|
24
|
-
'A' =>
|
11
|
+
'L' => 0, # this is a low ace
|
12
|
+
'2' => 1,
|
13
|
+
'3' => 2,
|
14
|
+
'4' => 3,
|
15
|
+
'5' => 4,
|
16
|
+
'6' => 5,
|
17
|
+
'7' => 6,
|
18
|
+
'8' => 7,
|
19
|
+
'9' => 8,
|
20
|
+
'T' => 9,
|
21
|
+
'J' => 10,
|
22
|
+
'Q' => 11,
|
23
|
+
'K' => 12,
|
24
|
+
'A' => 13
|
25
25
|
}
|
26
26
|
|
27
27
|
def Card.face_value(face)
|
28
|
-
face.upcase
|
29
|
-
if face == 'L' || !FACE_VALUES.has_key?(face)
|
30
|
-
nil
|
31
|
-
else
|
32
|
-
FACE_VALUES[face] - 1
|
33
|
-
end
|
28
|
+
FACE_VALUES[face.upcase]
|
34
29
|
end
|
35
30
|
|
36
31
|
private
|
37
|
-
|
38
|
-
def build_from_value(
|
39
|
-
@
|
40
|
-
@
|
41
|
-
@face = (value % FACES.size())
|
32
|
+
|
33
|
+
def build_from_value(given_value)
|
34
|
+
@suit = given_value / 13
|
35
|
+
@face = given_value % 13
|
42
36
|
end
|
43
37
|
|
44
38
|
def build_from_face_suit(face, suit)
|
45
39
|
suit.downcase!
|
46
40
|
@face = Card::face_value(face)
|
47
41
|
@suit = SUIT_LOOKUP[suit]
|
48
|
-
|
42
|
+
raise ArgumentError, "Invalid card: \"#{face}#{suit}\"" unless @face and @suit
|
49
43
|
end
|
50
44
|
|
51
|
-
def build_from_face_suit_values(
|
52
|
-
|
45
|
+
def build_from_face_suit_values(face_int, suit_int)
|
46
|
+
@face = face_int
|
47
|
+
@suit = suit_int
|
53
48
|
end
|
54
|
-
|
49
|
+
|
55
50
|
def build_from_string(card)
|
56
51
|
build_from_face_suit(card[0,1], card[1,1])
|
57
52
|
end
|
58
|
-
|
53
|
+
|
59
54
|
# Constructs this card object from another card object
|
60
55
|
def build_from_card(card)
|
61
|
-
@value = card.value
|
62
56
|
@suit = card.suit
|
63
57
|
@face = card.face
|
64
58
|
end
|
65
|
-
|
59
|
+
|
66
60
|
public
|
67
61
|
|
68
|
-
def initialize(*
|
69
|
-
if (
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
62
|
+
def initialize(*args)
|
63
|
+
if (args.size == 1)
|
64
|
+
arg = args.first
|
65
|
+
if (arg.respond_to?(:to_card))
|
66
|
+
build_from_card(arg)
|
67
|
+
elsif (arg.respond_to?(:to_str))
|
68
|
+
build_from_string(arg)
|
69
|
+
elsif (arg.respond_to?(:to_int))
|
70
|
+
build_from_value(arg)
|
76
71
|
end
|
77
|
-
elsif (
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
72
|
+
elsif (args.size == 2)
|
73
|
+
arg1, arg2 = args
|
74
|
+
if (arg1.respond_to?(:to_str) &&
|
75
|
+
arg2.respond_to?(:to_str))
|
76
|
+
build_from_face_suit(arg1, arg2)
|
77
|
+
elsif (arg1.respond_to?(:to_int) &&
|
78
|
+
arg2.respond_to?(:to_int))
|
79
|
+
build_from_face_suit_values(arg1, arg2)
|
84
80
|
end
|
85
81
|
end
|
86
82
|
end
|
87
83
|
|
88
|
-
attr_reader :suit, :face
|
84
|
+
attr_reader :suit, :face
|
89
85
|
include Comparable
|
90
86
|
|
87
|
+
def value
|
88
|
+
(@suit * 13) + @face
|
89
|
+
end
|
90
|
+
|
91
91
|
# Returns a string containing the representation of Card
|
92
92
|
#
|
93
93
|
# Card.new("7c").to_s # => "7c"
|
94
94
|
def to_s
|
95
95
|
FACES[@face].chr + SUITS[@suit].chr
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
# If to_card is called on a `Card` it should return itself
|
99
99
|
def to_card
|
100
100
|
self
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
# Compare the face value of this card with another card. Returns:
|
104
104
|
# -1 if self is less than card2
|
105
105
|
# 0 if self is the same face value of card2
|
@@ -107,20 +107,20 @@ class Card
|
|
107
107
|
def <=> card2
|
108
108
|
@face <=> card2.face
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
# Returns true if the cards are the same card. Meaning they
|
112
112
|
# have the same suit and the same face value.
|
113
113
|
def == card2
|
114
|
-
|
114
|
+
value == card2.value
|
115
115
|
end
|
116
116
|
alias :eql? :==
|
117
|
-
|
117
|
+
|
118
118
|
# Compute a hash-code for this Card. Two Cards with the same
|
119
|
-
# content will have the same hash code (and will compare using eql?).
|
119
|
+
# content will have the same hash code (and will compare using eql?).
|
120
120
|
def hash
|
121
|
-
|
121
|
+
value.hash
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
# A card's natural value is the closer to it's intuitive value in a deck
|
125
125
|
# in the range of 1 to 52. Aces are low with a value of 1. Uses the bridge
|
126
126
|
# order of suits: clubs, diamonds, hearts, and spades. The formula used is:
|
@@ -1,32 +1,34 @@
|
|
1
1
|
class PokerHand
|
2
2
|
include Comparable
|
3
|
+
include Enumerable
|
3
4
|
attr_reader :hand
|
4
|
-
|
5
|
+
|
5
6
|
@@allow_duplicates = true # true by default
|
6
7
|
def self.allow_duplicates; @@allow_duplicates; end
|
7
8
|
def self.allow_duplicates=(v); @@allow_duplicates = v; end
|
8
|
-
|
9
|
+
|
9
10
|
# Returns a new PokerHand object. Accepts the cards represented
|
10
11
|
# in a string or an array
|
11
12
|
#
|
12
13
|
# PokerHand.new("3d 5c 8h Ks") # => #<PokerHand:0x5c673c ...
|
13
14
|
# PokerHand.new(["3d", "5c", "8h", "Ks"]) # => #<PokerHand:0x5c2d6c ...
|
14
15
|
def initialize(cards = [])
|
15
|
-
|
16
|
-
|
16
|
+
@hand = case cards
|
17
|
+
when Array
|
18
|
+
cards.map do |card|
|
17
19
|
if card.is_a? Card
|
18
20
|
card
|
19
21
|
else
|
20
22
|
Card.new(card.to_s)
|
21
23
|
end
|
22
24
|
end
|
23
|
-
|
24
|
-
|
25
|
+
when String
|
26
|
+
cards.scan(/\S{2}/).map { |str| Card.new(str) }
|
25
27
|
else
|
26
|
-
|
28
|
+
cards
|
27
29
|
end
|
28
|
-
|
29
|
-
check_for_duplicates
|
30
|
+
|
31
|
+
check_for_duplicates unless allow_duplicates
|
30
32
|
end
|
31
33
|
|
32
34
|
# Returns a new PokerHand object with the cards sorted by suit
|
@@ -37,14 +39,14 @@ class PokerHand
|
|
37
39
|
PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
|
38
40
|
end
|
39
41
|
|
40
|
-
# Returns a new PokerHand object with the cards sorted by value
|
42
|
+
# Returns a new PokerHand object with the cards sorted by face value
|
41
43
|
# with the highest value first.
|
42
44
|
#
|
43
45
|
# PokerHand.new("3d 5c 8h Ks").by_face.just_cards # => "Ks 8h 5c 3d"
|
44
46
|
def by_face
|
45
47
|
PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
|
46
48
|
end
|
47
|
-
|
49
|
+
|
48
50
|
# Returns string representation of the hand without the rank
|
49
51
|
#
|
50
52
|
# PokerHand.new(["3c", "Kh"]).just_cards # => "3c Kh"
|
@@ -52,7 +54,7 @@ class PokerHand
|
|
52
54
|
@hand.join(" ")
|
53
55
|
end
|
54
56
|
alias :cards :just_cards
|
55
|
-
|
57
|
+
|
56
58
|
# Returns an array of the card values in the hand.
|
57
59
|
# The values returned are 1 less than the value on the card.
|
58
60
|
# For example: 2's will be shown as 1.
|
@@ -94,14 +96,11 @@ class PokerHand
|
|
94
96
|
def four_of_a_kind?
|
95
97
|
if (md = (by_face =~ /(.). \1. \1. \1./))
|
96
98
|
# get kicker
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
arrange_hand(md)
|
101
|
-
]
|
102
|
-
else
|
103
|
-
false
|
99
|
+
result = [8, Card::face_value(md[1])]
|
100
|
+
result << Card::face_value($1) if (md.pre_match + md.post_match).match(/(\S)/)
|
101
|
+
return [result, arrange_hand(md)]
|
104
102
|
end
|
103
|
+
false
|
105
104
|
end
|
106
105
|
|
107
106
|
def full_house?
|
@@ -164,19 +163,17 @@ class PokerHand
|
|
164
163
|
if (md = (by_face =~ /(.). \1. \1./))
|
165
164
|
# get kicker
|
166
165
|
arranged_hand = arrange_hand(md)
|
167
|
-
arranged_hand.match(/(?:\S\S ){
|
168
|
-
|
169
|
-
[
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
]
|
175
|
-
|
176
|
-
]
|
177
|
-
else
|
178
|
-
false
|
166
|
+
matches = arranged_hand.match(/(?:\S\S ){2}(\S\S)/)
|
167
|
+
if matches
|
168
|
+
result = [4, Card::face_value(md[1])]
|
169
|
+
matches = arranged_hand.match(/(?:\S\S ){3}(\S)/)
|
170
|
+
result << Card::face_value($1) if matches
|
171
|
+
matches = arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
|
172
|
+
result << Card::face_value($2) if matches
|
173
|
+
return [result, arranged_hand]
|
174
|
+
end
|
179
175
|
end
|
176
|
+
false
|
180
177
|
end
|
181
178
|
|
182
179
|
def two_pair?
|
@@ -191,44 +188,52 @@ class PokerHand
|
|
191
188
|
# that were in-between, and the cards that came after.
|
192
189
|
arranged_hand = arrange_hand(md[0].sub(md[2], '') + ' ' +
|
193
190
|
md.pre_match + ' ' + md[2] + ' ' + md.post_match)
|
194
|
-
arranged_hand.match(/(?:\S\S ){
|
195
|
-
|
196
|
-
[
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
else
|
205
|
-
false
|
191
|
+
matches = arranged_hand.match(/(?:\S\S ){3}(\S\S)/)
|
192
|
+
if matches
|
193
|
+
result = []
|
194
|
+
result << 3
|
195
|
+
result << Card::face_value(md[1]) # face value of the first pair
|
196
|
+
result << Card::face_value(md[3]) # face value of the second pair
|
197
|
+
matches = arranged_hand.match(/(?:\S\S ){4}(\S)/)
|
198
|
+
result << Card::face_value($1) if matches # face value of the kicker
|
199
|
+
return [result, arranged_hand]
|
200
|
+
end
|
206
201
|
end
|
202
|
+
false
|
207
203
|
end
|
208
204
|
|
209
205
|
def pair?
|
210
206
|
if (md = (by_face =~ /(.). \1./))
|
211
|
-
|
212
|
-
arranged_hand =
|
213
|
-
|
214
|
-
[
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
]
|
222
|
-
|
223
|
-
]
|
207
|
+
arranged_hand_str = arrange_hand(md)
|
208
|
+
arranged_hand = PokerHand.new(arranged_hand_str)
|
209
|
+
|
210
|
+
if arranged_hand.hand[0].face == arranged_hand.hand[1].face &&
|
211
|
+
arranged_hand.hand[0].suit != arranged_hand.hand[1].suit
|
212
|
+
result = [2, arranged_hand.hand[0].face]
|
213
|
+
result << arranged_hand.hand[2].face if arranged_hand.size > 2
|
214
|
+
result << arranged_hand.hand[3].face if arranged_hand.size > 3
|
215
|
+
result << arranged_hand.hand[4].face if arranged_hand.size > 4
|
216
|
+
|
217
|
+
return [result, arranged_hand_str]
|
218
|
+
end
|
224
219
|
else
|
225
220
|
false
|
226
221
|
end
|
227
222
|
end
|
228
223
|
|
229
224
|
def highest_card?
|
230
|
-
|
231
|
-
|
225
|
+
if size > 0
|
226
|
+
result = by_face
|
227
|
+
[[1, *result.face_values[0..result.face_values.length]], result.hand.join(' ')]
|
228
|
+
else
|
229
|
+
false
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def empty_hand?
|
234
|
+
if size == 0
|
235
|
+
[[0]]
|
236
|
+
end
|
232
237
|
end
|
233
238
|
|
234
239
|
OPS = [
|
@@ -242,6 +247,7 @@ class PokerHand
|
|
242
247
|
['Two pair', :two_pair? ],
|
243
248
|
['Pair', :pair? ],
|
244
249
|
['Highest Card', :highest_card? ],
|
250
|
+
['Empty Hand', :empty_hand? ],
|
245
251
|
]
|
246
252
|
|
247
253
|
# Returns the verbose hand rating
|
@@ -252,18 +258,16 @@ class PokerHand
|
|
252
258
|
(method(op[1]).call()) ? op[0] : false
|
253
259
|
}.find { |v| v }
|
254
260
|
end
|
255
|
-
|
261
|
+
|
256
262
|
alias :rank :hand_rating
|
257
|
-
|
263
|
+
|
258
264
|
def score
|
259
|
-
# OPS.map returns an array containing the result of calling each OPS method
|
260
|
-
# the poker hand. The
|
265
|
+
# OPS.map returns an array containing the result of calling each OPS method against
|
266
|
+
# the poker hand. The truthy cell closest to the front of the array represents
|
261
267
|
# the highest ranking.
|
262
|
-
# find([0]) returns [0] instead of nil if the hand does not match any of the rankings
|
263
|
-
# which is not likely to occur since every hand should at least have a highest card
|
264
268
|
OPS.map { |op|
|
265
269
|
method(op[1]).call()
|
266
|
-
}.find
|
270
|
+
}.find { |score| score }
|
267
271
|
end
|
268
272
|
|
269
273
|
# Returns a string of the hand arranged based on its rank. Usually this will be the
|
@@ -271,11 +275,11 @@ class PokerHand
|
|
271
275
|
#
|
272
276
|
# ph = PokerHand.new("As 3s 5s 2s 4s")
|
273
277
|
# ph.sort_using_rank # => "5s 4s 3s 2s As"
|
274
|
-
# ph.by_face.just_cards # => "As 5s 4s 3s 2s"
|
278
|
+
# ph.by_face.just_cards # => "As 5s 4s 3s 2s"
|
275
279
|
def sort_using_rank
|
276
280
|
score[1]
|
277
281
|
end
|
278
|
-
|
282
|
+
|
279
283
|
# Returns string with a listing of the cards in the hand followed by the hand's rank.
|
280
284
|
#
|
281
285
|
# h = PokerHand.new("8c 8s")
|
@@ -283,20 +287,19 @@ class PokerHand
|
|
283
287
|
def to_s
|
284
288
|
just_cards + " (" + hand_rating + ")"
|
285
289
|
end
|
286
|
-
|
290
|
+
|
287
291
|
# Returns an array of `Card` objects that make up the `PokerHand`.
|
288
292
|
def to_a
|
289
293
|
@hand
|
290
294
|
end
|
291
|
-
|
292
295
|
alias :to_ary :to_a
|
293
|
-
|
296
|
+
|
294
297
|
def <=> other_hand
|
295
298
|
self.score[0].compact <=> other_hand.score[0].compact
|
296
299
|
end
|
297
|
-
|
300
|
+
|
298
301
|
# Add a card to the hand
|
299
|
-
#
|
302
|
+
#
|
300
303
|
# hand = PokerHand.new("5d")
|
301
304
|
# hand << "6s" # => Add a six of spades to the hand by passing a string
|
302
305
|
# hand << ["7h", "8d"] # => Add multiple cards to the hand using an array
|
@@ -304,16 +307,16 @@ class PokerHand
|
|
304
307
|
if new_cards.is_a?(Card) || new_cards.is_a?(String)
|
305
308
|
new_cards = [new_cards]
|
306
309
|
end
|
307
|
-
|
310
|
+
|
308
311
|
new_cards.each do |nc|
|
309
|
-
unless
|
312
|
+
unless allow_duplicates
|
310
313
|
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}/
|
311
314
|
end
|
312
|
-
|
315
|
+
|
313
316
|
@hand << Card.new(nc)
|
314
317
|
end
|
315
318
|
end
|
316
|
-
|
319
|
+
|
317
320
|
# Remove a card from the hand.
|
318
321
|
#
|
319
322
|
# hand = PokerHand.new("5d Jd")
|
@@ -322,14 +325,14 @@ class PokerHand
|
|
322
325
|
def delete card
|
323
326
|
@hand.delete(Card.new(card))
|
324
327
|
end
|
325
|
-
|
328
|
+
|
326
329
|
# Same concept as Array#uniq
|
327
330
|
def uniq
|
328
331
|
PokerHand.new(@hand.uniq)
|
329
332
|
end
|
330
|
-
|
333
|
+
|
331
334
|
# Resolving methods are just passed directly down to the @hand array
|
332
|
-
RESOLVING_METHODS = [:size,
|
335
|
+
RESOLVING_METHODS = [:each, :size, :-]
|
333
336
|
RESOLVING_METHODS.each do |method|
|
334
337
|
class_eval %{
|
335
338
|
def #{method}(*args, &block)
|
@@ -337,20 +340,106 @@ class PokerHand
|
|
337
340
|
end
|
338
341
|
}
|
339
342
|
end
|
340
|
-
|
343
|
+
|
344
|
+
def allow_duplicates
|
345
|
+
@@allow_duplicates
|
346
|
+
end
|
347
|
+
|
348
|
+
# Checks whether the hand matches usual expressions like AA, AK, AJ+, 66+, AQs, AQo...
|
349
|
+
#
|
350
|
+
# Valid expressions:
|
351
|
+
# * "AJ": Matches exact faces (in this case an Ace and a Jack), suited or not
|
352
|
+
# * "AJs": Same but suited only
|
353
|
+
# * "AJo": Same but offsuit only
|
354
|
+
# * "AJ+": Matches an Ace with any card >= Jack, suited or not
|
355
|
+
# * "AJs+": Same but suited only
|
356
|
+
# * "AJo+": Same but offsuit only
|
357
|
+
# * "JJ+": Matches any pair >= "JJ".
|
358
|
+
# * "8T+": Matches connectors (in this case with 1 gap : 8T, 9J, TQ, JK, QA)
|
359
|
+
# * "8Ts+": Same but suited only
|
360
|
+
# * "8To+": Same but offsuit only
|
361
|
+
#
|
362
|
+
# The order of the cards in the expression is important (8T+ is not the same as T8+), but the order of the cards in the hand is not ("AK" will match "Ad Kc" and "Kc Ad").
|
363
|
+
#
|
364
|
+
# The expression can be an array of expressions. In this case the method returns true if any expression matches.
|
365
|
+
#
|
366
|
+
# This method only works on hands with 2 cards.
|
367
|
+
#
|
368
|
+
# PokerHand.new('Ah Ad').match? 'AA' # => true
|
369
|
+
# PokerHand.new('Ah Kd').match? 'AQ+' # => true
|
370
|
+
# PokerHand.new('Jc Qc').match? '89s+' # => true
|
371
|
+
# PokerHand.new('Ah Jd').match? %w( 22+ A6s+ AJ+ ) # => true
|
372
|
+
# PokerHand.new('Ah Td').match? %w( 22+ A6s+ AJ+ ) # => false
|
373
|
+
#
|
374
|
+
def match? expression
|
375
|
+
raise "Hands with #{@hand.size} cards is not supported" unless @hand.size == 2
|
376
|
+
|
377
|
+
if expression.is_a? Array
|
378
|
+
return expression.any? { |e| match?(e) }
|
379
|
+
end
|
380
|
+
|
381
|
+
faces = @hand.map { |card| card.face }.sort.reverse
|
382
|
+
suited = @hand.map { |card| card.suit }.uniq.size == 1
|
383
|
+
if expression =~ /^(.)(.)(s|o|)(\+|)$/
|
384
|
+
face1 = Card.face_value($1)
|
385
|
+
face2 = Card.face_value($2)
|
386
|
+
raise ArgumentError, "Invalid expression: #{expression.inspect}" unless face1 and face2
|
387
|
+
suit_match = $3
|
388
|
+
plus = ($4 != "")
|
389
|
+
|
390
|
+
if plus
|
391
|
+
if face1 == face2
|
392
|
+
face_match = (faces.first == faces.last and faces.first >= face1)
|
393
|
+
elsif face1 > face2
|
394
|
+
face_match = (faces.first == face1 and faces.last >= face2)
|
395
|
+
else
|
396
|
+
face_match = ((faces.first - faces.last) == (face2 - face1) and faces.last >= face1)
|
397
|
+
end
|
398
|
+
else
|
399
|
+
expression_faces = [face1, face2].sort.reverse
|
400
|
+
face_match = (expression_faces == faces)
|
401
|
+
end
|
402
|
+
case suit_match
|
403
|
+
when ''
|
404
|
+
face_match
|
405
|
+
when 's'
|
406
|
+
face_match and suited
|
407
|
+
when 'o'
|
408
|
+
face_match and !suited
|
409
|
+
end
|
410
|
+
else
|
411
|
+
raise ArgumentError, "Invalid expression: #{expression.inspect}"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def +(other)
|
416
|
+
cards = @hand.map { |card| Card.new(card) }
|
417
|
+
case other
|
418
|
+
when String
|
419
|
+
cards << Card.new(other)
|
420
|
+
when Card
|
421
|
+
cards << other
|
422
|
+
when PokerHand
|
423
|
+
cards += other.hand
|
424
|
+
else
|
425
|
+
raise ArgumentError, "Invalid argument: #{other.inspect}"
|
426
|
+
end
|
427
|
+
PokerHand.new(cards)
|
428
|
+
end
|
429
|
+
|
341
430
|
private
|
342
|
-
|
431
|
+
|
343
432
|
def check_for_duplicates
|
344
|
-
if @hand.size != @hand.uniq.size &&
|
433
|
+
if @hand.size != @hand.uniq.size && !allow_duplicates
|
345
434
|
raise "Attempting to create a hand that contains duplicate cards. Set PokerHand.allow_duplicates to true if you do not want to ignore this error."
|
346
435
|
end
|
347
436
|
end
|
348
|
-
|
437
|
+
|
349
438
|
# if md is a string, arrange_hand will remove extra white space
|
350
439
|
# if md is a MatchData, arrange_hand returns the matched segment
|
351
440
|
# followed by the pre_match and the post_match
|
352
441
|
def arrange_hand(md)
|
353
|
-
hand = if
|
442
|
+
hand = if md.respond_to?(:to_str)
|
354
443
|
md
|
355
444
|
else
|
356
445
|
md[0] + ' ' + md.pre_match + md.post_match
|
@@ -358,12 +447,23 @@ class PokerHand
|
|
358
447
|
hand.strip.squeeze(" ") # remove extra whitespace
|
359
448
|
end
|
360
449
|
|
361
|
-
# delta transform
|
362
|
-
# between card values is in the string
|
363
|
-
# straight and/or straight flush
|
450
|
+
# delta transform returns a string representation of the cards where the
|
451
|
+
# delta between card values is in the string. This is necessary so a regexp
|
452
|
+
# can then match a straight and/or straight flush
|
453
|
+
#
|
454
|
+
# Examples
|
455
|
+
#
|
456
|
+
# PokerHand.new("As Qc Jh Ts 9d 8h")
|
457
|
+
# # => '0As 2Qc 1Jh 1Ts 19d 18h'
|
458
|
+
#
|
459
|
+
# PokerHand.new("Ah Qd Td 5d 4d")
|
460
|
+
# # => '0Ah 2Qd 2Td 55d 14d'
|
461
|
+
#
|
364
462
|
def delta_transform(use_suit = false)
|
463
|
+
# In order to check for both ace high and ace low straights we create low
|
464
|
+
# ace duplicates of all of the high aces.
|
365
465
|
aces = @hand.select { |c| c.face == Card::face_value('A') }
|
366
|
-
aces.map! { |c| Card.new(
|
466
|
+
aces.map! { |c| Card.new(0, c.suit) } # hack to give the appearance of a low ace
|
367
467
|
|
368
468
|
base = if (use_suit)
|
369
469
|
(@hand + aces).sort_by { |c| [c.suit, c.face] }.reverse
|
@@ -371,6 +471,7 @@ class PokerHand
|
|
371
471
|
(@hand + aces).sort_by { |c| [c.face, c.suit] }.reverse
|
372
472
|
end
|
373
473
|
|
474
|
+
# Insert delta in front of each card
|
374
475
|
result = base.inject(['',nil]) do |(delta_hand, prev_card), card|
|
375
476
|
if (prev_card)
|
376
477
|
delta = prev_card - card.face
|
@@ -403,5 +504,5 @@ class PokerHand
|
|
403
504
|
# careful to use gsub as gsub! can return nil here
|
404
505
|
arranged_hand.gsub(/\s+$/, '')
|
405
506
|
end
|
406
|
-
|
407
|
-
end
|
507
|
+
|
508
|
+
end
|