ruby-poker 0.3.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|