texas_holdem 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/README.rdoc +1 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/deck.rb +4 -4
- data/lib/hand.rb +5 -5
- data/lib/player_hand.rb +220 -0
- data/lib/texas_holdem.rb +2 -6
- data/test/helper.rb +4 -3
- data/test/{test_players_hand.rb → test_player_hand.rb} +1 -3
- metadata +7 -9
- data/README +0 -0
- data/lib/players_hand.rb +0 -132
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/deck.rb
CHANGED
@@ -17,17 +17,17 @@ class TexasHoldem::Deck
|
|
17
17
|
def build
|
18
18
|
@cards = []
|
19
19
|
|
20
|
-
FACES.each_byte do |
|
21
|
-
SUITS.each_byte {|
|
20
|
+
FACES.each_byte do |face|
|
21
|
+
SUITS.each_byte {|suit| @cards.push(face.chr + suit.chr) }
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def shuffle
|
26
26
|
3.times do
|
27
27
|
shuf = []
|
28
|
-
@cards.each do |
|
28
|
+
@cards.each do |card|
|
29
29
|
loc = rand(shuf.size + 1)
|
30
|
-
shuf.insert(loc,
|
30
|
+
shuf.insert(loc, card)
|
31
31
|
end
|
32
32
|
@cards = shuf.reverse
|
33
33
|
end
|
data/lib/hand.rb
CHANGED
@@ -32,10 +32,10 @@ class TexasHoldem::Hand
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def winner
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
return unless finished?
|
36
|
+
winning_player = @players.first
|
37
|
+
winning_player.take_winnings @pot
|
38
|
+
winning_player
|
39
39
|
end
|
40
40
|
|
41
41
|
def finished?
|
@@ -48,7 +48,7 @@ class TexasHoldem::Hand
|
|
48
48
|
|
49
49
|
def deal
|
50
50
|
case round
|
51
|
-
when :pocket : deal_pocket_cards && deduct_blinds
|
51
|
+
when :pocket : deal_pocket_cards && deduct_blinds
|
52
52
|
when :flop : deal_community_cards 3
|
53
53
|
when :turn : deal_community_cards 1
|
54
54
|
when :river : deal_community_cards 1
|
data/lib/player_hand.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
module TexasHoldem
|
2
|
+
class PlayerHand
|
3
|
+
attr_reader :cards
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
def self.inherited(subclass)
|
7
|
+
(@hand_types || @hand_types = []) << subclass
|
8
|
+
end
|
9
|
+
|
10
|
+
# TODO: investigate Builder / Factory pattern
|
11
|
+
def self.create(cards)
|
12
|
+
# cards could be both OnePair and a FullHouse, so return the highest raking hand
|
13
|
+
@hand_types.map {|hand| hand.create(cards) }.compact.sort_by(&:score).last
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(cards)
|
17
|
+
@cards = cards
|
18
|
+
@cards.gsub!(/J/, '11')
|
19
|
+
@cards.gsub!(/Q/, '12')
|
20
|
+
@cards.gsub!(/K/, '13')
|
21
|
+
@cards.gsub!(/A/, '14')
|
22
|
+
@cards = @cards.split.sort_by {|card| card.gsub(/\D/,'').to_i }.join ' '
|
23
|
+
end
|
24
|
+
|
25
|
+
def <=>(players_hand)
|
26
|
+
other_players_hand_score = players_hand.score
|
27
|
+
this_players_score = score
|
28
|
+
|
29
|
+
if this_players_score == other_players_hand_score
|
30
|
+
remaining_cards <=> players_hand.remaining_cards
|
31
|
+
else
|
32
|
+
this_players_score <=> other_players_hand_score
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def score
|
37
|
+
base_score * 1000 + relative_score
|
38
|
+
end
|
39
|
+
|
40
|
+
def remaining_cards
|
41
|
+
@cards.gsub /\D/, ''
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def face_values
|
47
|
+
@cards.gsub(/[scdh]/,'')
|
48
|
+
end
|
49
|
+
|
50
|
+
def face_values_array
|
51
|
+
face_values.to_a
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class FourOfAKind < PlayerHand
|
56
|
+
Pattern = /(\d{1,2})[scdh] (?:\s\1[scdh]){3}/x
|
57
|
+
|
58
|
+
def self.create(cards)
|
59
|
+
new cards if cards.match Pattern
|
60
|
+
end
|
61
|
+
|
62
|
+
def name
|
63
|
+
'four of a kind'
|
64
|
+
end
|
65
|
+
|
66
|
+
def base_score
|
67
|
+
7
|
68
|
+
end
|
69
|
+
|
70
|
+
def relative_score
|
71
|
+
@cards[Pattern,1].to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class FullHouse < PlayerHand
|
76
|
+
Pattern = /^(?:(\d) \1{2} (\d) \2|(\d) \3 (\d) \4{2})/x
|
77
|
+
|
78
|
+
def self.create(cards)
|
79
|
+
new(cards) if cards.gsub(/\D/,'').split(//).sort.to_s.match Pattern
|
80
|
+
end
|
81
|
+
|
82
|
+
def name
|
83
|
+
'full house'
|
84
|
+
end
|
85
|
+
|
86
|
+
def base_score
|
87
|
+
6
|
88
|
+
end
|
89
|
+
|
90
|
+
def relative_score
|
91
|
+
@cards[ TexasHoldem::ThreeOfAKind::Pattern, 1 ].to_i
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Flush < PlayerHand
|
96
|
+
Pattern = /\d{1,2}([csdh]) (?:\s\d{1,2}\1){4} /x
|
97
|
+
|
98
|
+
def self.create(cards)
|
99
|
+
new(cards) if cards.match Pattern
|
100
|
+
end
|
101
|
+
|
102
|
+
def name
|
103
|
+
'flush'
|
104
|
+
end
|
105
|
+
|
106
|
+
def base_score
|
107
|
+
5
|
108
|
+
end
|
109
|
+
|
110
|
+
def relative_score
|
111
|
+
face_values_array.sort.last.to_i
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Straight < PlayerHand
|
116
|
+
def self.create(cards)
|
117
|
+
straight = new cards
|
118
|
+
card_values = straight.cards.gsub(/[scdh]/,'').split.map {|card_value| card_value.to_i }
|
119
|
+
new(cards) if (card_values.first..card_values.last).to_a == card_values
|
120
|
+
end
|
121
|
+
|
122
|
+
def name
|
123
|
+
'straight'
|
124
|
+
end
|
125
|
+
|
126
|
+
def base_score
|
127
|
+
4
|
128
|
+
end
|
129
|
+
|
130
|
+
def relative_score
|
131
|
+
face_values_array.last.to_i
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class ThreeOfAKind < PlayerHand
|
136
|
+
Pattern = /(\d{1,2})[scdh] (?:\s\1[scdh]){2}/x
|
137
|
+
|
138
|
+
def self.create(cards)
|
139
|
+
new(cards) if cards.match Pattern
|
140
|
+
end
|
141
|
+
|
142
|
+
def name
|
143
|
+
'three of a kind'
|
144
|
+
end
|
145
|
+
|
146
|
+
def base_score
|
147
|
+
3
|
148
|
+
end
|
149
|
+
|
150
|
+
def relative_score
|
151
|
+
@cards[ Pattern, 1 ].to_i
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class TwoPair < PlayerHand
|
156
|
+
Pattern = /(\d{1,2})[scdh] \1[scdh].*(\d{1,2})[scdh] \2[scdh]/
|
157
|
+
|
158
|
+
def self.create(cards)
|
159
|
+
new(cards) if cards.match Pattern
|
160
|
+
end
|
161
|
+
|
162
|
+
def name
|
163
|
+
'two pair'
|
164
|
+
end
|
165
|
+
|
166
|
+
def base_score
|
167
|
+
2
|
168
|
+
end
|
169
|
+
|
170
|
+
def relative_score
|
171
|
+
@cards[ Pattern , 2 ].to_i * 2
|
172
|
+
end
|
173
|
+
|
174
|
+
def remaining_cards
|
175
|
+
@cards.gsub(Pattern, '').gsub /\D/, ''
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class OnePair < PlayerHand
|
180
|
+
Pattern = /(\d{1,2})[scdh] (?:\s\1[scdh]){1}/x
|
181
|
+
|
182
|
+
def self.create(cards)
|
183
|
+
new(cards) if cards.match Pattern
|
184
|
+
end
|
185
|
+
|
186
|
+
def name
|
187
|
+
'one pair'
|
188
|
+
end
|
189
|
+
|
190
|
+
def base_score
|
191
|
+
1
|
192
|
+
end
|
193
|
+
|
194
|
+
def relative_score
|
195
|
+
@cards[ Pattern , 1 ].to_i * 2
|
196
|
+
end
|
197
|
+
|
198
|
+
def remaining_cards
|
199
|
+
@cards.gsub(Pattern, '').gsub /\D/, ''
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class HighCard < PlayerHand
|
204
|
+
def self.create(cards)
|
205
|
+
new(cards)
|
206
|
+
end
|
207
|
+
|
208
|
+
def name
|
209
|
+
'high card'
|
210
|
+
end
|
211
|
+
|
212
|
+
def base_score
|
213
|
+
0
|
214
|
+
end
|
215
|
+
|
216
|
+
def relative_score
|
217
|
+
0
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/texas_holdem.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
1
|
module TexasHoldem
|
2
|
-
|
3
|
-
require '
|
4
|
-
require 'player'
|
5
|
-
require 'deck'
|
6
|
-
require 'betting_round'
|
7
|
-
require 'players_hand'
|
2
|
+
ClassFiles = Dir.entries( File.join( File.dirname(__FILE__), '..', 'lib' ) ).grep(/\.rb$/)
|
3
|
+
ClassFiles.each {|klass| require klass.gsub /\.rb/, ''}
|
8
4
|
end
|
data/test/helper.rb
CHANGED
@@ -20,12 +20,13 @@ class String
|
|
20
20
|
include Test::Unit::Assertions
|
21
21
|
|
22
22
|
def hand_name(name)
|
23
|
-
|
23
|
+
hand = TexasHoldem::PlayerHand.create(self)
|
24
|
+
assert_equal name, hand.name, "\n#{hand.cards} not recognised as a #{name}\n"
|
24
25
|
end
|
25
26
|
|
26
27
|
def beats(loser)
|
27
|
-
winner, loser = TexasHoldem::
|
28
|
-
assert
|
28
|
+
winner, loser = TexasHoldem::PlayerHand.create(self), TexasHoldem::PlayerHand.create(loser)
|
29
|
+
assert winner > loser, "\n #{winner.name} (#{winner.score}) should beat #{loser.name} (#{loser.score})\n"
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -2,14 +2,12 @@ require 'helper'
|
|
2
2
|
|
3
3
|
class HandTest < Test::Unit::TestCase
|
4
4
|
test "should always have cards sorted in ascending order by face value" do
|
5
|
-
hand = TexasHoldem::
|
5
|
+
hand = TexasHoldem::PlayerHand.new('Ad As 5c 6c Jd')
|
6
6
|
assert_equal "5c 6c 11d 14s 14d", hand.cards
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
10
|
class HandIdentificationTest < Test::Unit::TestCase
|
11
|
-
# TODO: add more non-sequential matches
|
12
|
-
|
13
11
|
test "should recognise a high card" do
|
14
12
|
'2d 3s 5c 6c Ad'.hand_name 'high card'
|
15
13
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: texas_holdem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Keith McDonnell
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-11-05 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -27,7 +27,6 @@ extensions: []
|
|
27
27
|
|
28
28
|
extra_rdoc_files:
|
29
29
|
- LICENSE
|
30
|
-
- README
|
31
30
|
- README.rdoc
|
32
31
|
files:
|
33
32
|
- .autotest
|
@@ -35,7 +34,6 @@ files:
|
|
35
34
|
- .gitignore
|
36
35
|
- Gemfile
|
37
36
|
- LICENSE
|
38
|
-
- README
|
39
37
|
- README.rdoc
|
40
38
|
- Rakefile
|
41
39
|
- VERSION
|
@@ -44,7 +42,7 @@ files:
|
|
44
42
|
- lib/game.rb
|
45
43
|
- lib/hand.rb
|
46
44
|
- lib/player.rb
|
47
|
-
- lib/
|
45
|
+
- lib/player_hand.rb
|
48
46
|
- lib/texas_holdem.rb
|
49
47
|
- test/helper.rb
|
50
48
|
- test/test_betting_round.rb
|
@@ -52,7 +50,7 @@ files:
|
|
52
50
|
- test/test_game.rb
|
53
51
|
- test/test_hand.rb
|
54
52
|
- test/test_player.rb
|
55
|
-
- test/
|
53
|
+
- test/test_player_hand.rb
|
56
54
|
- test/test_texas_holdem.rb
|
57
55
|
has_rdoc: true
|
58
56
|
homepage: http://github.com/kmcd/texas_holdem
|
@@ -91,9 +89,9 @@ summary: Ruby classes modelling a game Texas Holdem poker
|
|
91
89
|
test_files:
|
92
90
|
- test/test_game.rb
|
93
91
|
- test/test_player.rb
|
92
|
+
- test/test_player_hand.rb
|
94
93
|
- test/test_betting_round.rb
|
95
94
|
- test/test_hand.rb
|
96
95
|
- test/test_deck.rb
|
97
|
-
- test/test_players_hand.rb
|
98
96
|
- test/helper.rb
|
99
97
|
- test/test_texas_holdem.rb
|
data/README
DELETED
File without changes
|
data/lib/players_hand.rb
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
# TODO: rename to PlayerHand or Player::Hand
|
2
|
-
class TexasHoldem::PlayersHand
|
3
|
-
attr_reader :cards
|
4
|
-
|
5
|
-
include Comparable
|
6
|
-
|
7
|
-
# TODO: dry up [scdh] references
|
8
|
-
MATCHES = {
|
9
|
-
'one pair' => /(\d{1,2})[scdh] (?:\s\1[scdh]){1}/x,
|
10
|
-
'two pair' => /(\d{1,2})[scdh] \1[scdh].*(\d{1,2})[scdh] \2[scdh]/,
|
11
|
-
'three of a kind' => /(\d{1,2})[scdh] (?:\s\1[scdh]){2}/x,
|
12
|
-
'four of a kind' => /(\d{1,2})[scdh] (?:\s\1[scdh]){3}/x,
|
13
|
-
}
|
14
|
-
|
15
|
-
def initialize(cards)
|
16
|
-
@cards = cards
|
17
|
-
@cards.gsub!(/J/, '11')
|
18
|
-
@cards.gsub!(/Q/, '12')
|
19
|
-
@cards.gsub!(/K/, '13')
|
20
|
-
@cards.gsub!(/A/, '14')
|
21
|
-
@cards = @cards.split.sort_by {|c| c.gsub(/\D/,'').to_i }.join ' '
|
22
|
-
end
|
23
|
-
|
24
|
-
def <=>(players_hand)
|
25
|
-
if score == players_hand.score
|
26
|
-
remaining_cards <=> players_hand.remaining_cards
|
27
|
-
else
|
28
|
-
score <=> players_hand.score
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def name
|
33
|
-
# TODO: refactor to polymorphic type?
|
34
|
-
if four_of_a_kind?
|
35
|
-
'four of a kind'
|
36
|
-
elsif full_house?
|
37
|
-
'full house'
|
38
|
-
elsif flush?
|
39
|
-
'flush'
|
40
|
-
elsif straight?
|
41
|
-
'straight'
|
42
|
-
elsif three_of_a_kind?
|
43
|
-
'three of a kind'
|
44
|
-
elsif two_pair?
|
45
|
-
'two pair'
|
46
|
-
elsif one_pair?
|
47
|
-
'one pair'
|
48
|
-
else
|
49
|
-
'high card'
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def one_pair?
|
54
|
-
@cards.match MATCHES['one pair']
|
55
|
-
end
|
56
|
-
|
57
|
-
def two_pair?
|
58
|
-
@cards.match MATCHES['two pair']
|
59
|
-
end
|
60
|
-
|
61
|
-
def straight?
|
62
|
-
card_sequence = face_values.split.map {|d| d.to_i }
|
63
|
-
(card_sequence.first..card_sequence.last).to_a == card_sequence
|
64
|
-
end
|
65
|
-
|
66
|
-
def flush?
|
67
|
-
@cards.match /\d{1,2}([csdh]) (?:\s\d{1,2}\1){4} /x
|
68
|
-
end
|
69
|
-
|
70
|
-
def three_of_a_kind?
|
71
|
-
@cards.match MATCHES['three of a kind']
|
72
|
-
end
|
73
|
-
|
74
|
-
def full_house?
|
75
|
-
# TODO: change this to [csdh]
|
76
|
-
@cards.gsub(/\D/,'').match /^(?:(\d) \1{2} (\d) \2|(\d) \3 (\d) \4{2})/x
|
77
|
-
end
|
78
|
-
|
79
|
-
def four_of_a_kind?
|
80
|
-
@cards.match MATCHES['four of a kind']
|
81
|
-
end
|
82
|
-
|
83
|
-
protected
|
84
|
-
|
85
|
-
def score
|
86
|
-
base_score + relative_score
|
87
|
-
end
|
88
|
-
|
89
|
-
def base_score
|
90
|
-
case name
|
91
|
-
when /one pair/ : 1
|
92
|
-
when /two pair/ : 2
|
93
|
-
when /three/ : 3
|
94
|
-
when /straight/ : 4
|
95
|
-
when /flush/ : 5
|
96
|
-
when /full house/ : 6
|
97
|
-
when /four/ : 7
|
98
|
-
else
|
99
|
-
0
|
100
|
-
end * 1000
|
101
|
-
end
|
102
|
-
|
103
|
-
def relative_score
|
104
|
-
case name
|
105
|
-
when /one pair/ : @cards[MATCHES['one pair'],1].to_i * 2
|
106
|
-
when /two pair/ : @cards[MATCHES['two pair'],2].to_i * 2
|
107
|
-
when /three/ : @cards[MATCHES['three of a kind'],1].to_i * 3
|
108
|
-
when /straight/ : face_values.split.last.to_i
|
109
|
-
when /flush/ : face_values.split.sort.last.to_i
|
110
|
-
when /full house/ : @cards[MATCHES['three of a kind'],1].to_i
|
111
|
-
when /four/ : @cards[MATCHES['four of a kind'],1].to_i
|
112
|
-
else
|
113
|
-
0
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def remaining_cards
|
118
|
-
case name
|
119
|
-
when /high card/ : @cards
|
120
|
-
when /one pair/ : @cards.gsub(MATCHES['one pair'], '')
|
121
|
-
when /two pair/ : @cards.gsub(MATCHES['two pair'], '')
|
122
|
-
else
|
123
|
-
''
|
124
|
-
end.gsub /[a-z]/, ''
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def face_values
|
130
|
-
@cards.gsub(/[scdh]/,'')
|
131
|
-
end
|
132
|
-
end
|