rholdem 0.0.1

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/lib/hand.rb ADDED
@@ -0,0 +1,236 @@
1
+ require File.dirname(__FILE__) + "/deck"
2
+
3
+ module Holdem
4
+ class Hand
5
+ RANKINGS = [
6
+ :straight_flush,
7
+ :four_of_a_kind,
8
+ :full_house,
9
+ :flush,
10
+ :straight,
11
+ :three_of_a_kind,
12
+ :two_pair,
13
+ :pair,
14
+ :high_card
15
+ ]
16
+
17
+ attr_reader :cards, :sorted_cards, :ranking, :high_card
18
+
19
+ def initialize(*cards)
20
+ @cards = []
21
+ if(cards.length == 1 && cards[0].is_a?(String))
22
+ # a string to split up, Card class takes care of raising errors.
23
+ cards[0].split(' ').map { |c| @cards << Card.new(c) }
24
+ else
25
+ # an array of cards, need to verify class.
26
+ cards = cards[0] if cards.length == 1 && cards[0].is_a?(Array)
27
+ cards.map { |c| c.is_a?(Card) ? @cards << c :
28
+ raise("Cannot initialize Hand from #{c.class} objects") }
29
+ end
30
+
31
+ unless (2..7).include? @cards.length
32
+ raise "#{@cards.length} cards given (2 to 7 cards required)"
33
+ end
34
+
35
+ @cards.sort!
36
+
37
+ find_best(@cards)
38
+ @high_card = @sorted_cards[@sorted_cards.length-1]
39
+ end
40
+
41
+ def >(compare_to)
42
+ return true if (self <=> compare_to) == 1
43
+ false
44
+ end
45
+
46
+ def <(compare_to)
47
+ return true if (self <=> compare_to) == -1
48
+ false
49
+ end
50
+
51
+ def ==(compare_to)
52
+ return true if (self <=> compare_to) == 0
53
+ false
54
+ end
55
+
56
+ def <=>(compare_to)
57
+ # must have 5 sorted cards or be same length.
58
+ if(@sorted_cards.length != 5 && compare_to.sorted_cards.length != 5)
59
+ unless @sorted_cards.length == compare_to.sorted_cards.length
60
+ raise "Cannot compare mismatched, less then 5 card hand lengths"
61
+ end
62
+ end
63
+
64
+ my_rank = RANKINGS.index(@ranking)
65
+ comp_rank = RANKINGS.index(compare_to.ranking)
66
+
67
+ return 1 if my_rank < comp_rank
68
+ return -1 if my_rank > comp_rank
69
+
70
+ # otherwise must compare by cards.
71
+ i = @sorted_cards.length-1
72
+ while(i >= 0)
73
+ return 1 if @sorted_cards[i] > compare_to.sorted_cards[i]
74
+ return -1 if @sorted_cards[i] < compare_to.sorted_cards[i]
75
+ i -= 1
76
+ end
77
+
78
+ return 0
79
+ end
80
+
81
+ private
82
+
83
+ def find_best(cards)
84
+ case cards.length
85
+
86
+ when 7
87
+ (0..6).each do |i|
88
+ copy = cards.dup
89
+ copy.delete_at(i)
90
+ find_best(copy)
91
+ end
92
+
93
+ when 6
94
+ (0..5).each do |i|
95
+ copy = cards.dup
96
+ copy.delete_at(i)
97
+ find_best(copy)
98
+ end
99
+
100
+ else
101
+ # testing a hand of 5 or less
102
+ new_rank = find_ranking(cards)
103
+ cards = sort_by_ranking(cards, new_rank)
104
+
105
+ if(@ranking.nil? || RANKINGS.index(new_rank) < RANKINGS.index(@ranking))
106
+ update_ranking(new_rank, cards)
107
+ elsif(RANKINGS.index(new_rank) == RANKINGS.index(@ranking))
108
+ (0...cards.length).each do |i|
109
+ if(cards[i].rank > @sorted_cards[i].rank)
110
+ update_ranking(new_rank, cards)
111
+ break
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def find_ranking(cards)
119
+ RANKINGS.each do |r|
120
+ return r if method(r.to_s + '?').call(cards)
121
+ end
122
+ end
123
+
124
+ def sort_by_ranking(cards, ranking)
125
+ case ranking
126
+
127
+ when :straight_flush, :straight
128
+ if(cards[4].rank - cards[0].rank == 12)
129
+ card = cards.delete_at(4)
130
+ cards.insert(0, card)
131
+ end
132
+
133
+ when :four_of_a_kind, :full_house
134
+ over_rank = cards[2].rank
135
+ cards = cards.sort_by do |card|
136
+ card.rank == over_rank ? 1 : 0
137
+ end
138
+
139
+ when :three_of_a_kind
140
+ trips_rank = cards[2].rank
141
+ cards = cards.sort_by do |card|
142
+ card.rank == trips_rank ? 100 : card.rank
143
+ end
144
+
145
+ when :two_pair, :pair
146
+ rank_counts = Hash.new
147
+ cards.each do |c|
148
+ rank_counts[c.rank] ||= 0
149
+ rank_counts[c.rank] += 1
150
+ end
151
+
152
+ cards = cards.sort_by do |card|
153
+ rank_counts[card.rank] > 1 ? card.rank+100 : card.rank
154
+ end
155
+ end
156
+ cards
157
+ end
158
+
159
+ def update_ranking(ranking, cards)
160
+ @sorted_cards = cards
161
+ @ranking = ranking
162
+ end
163
+
164
+ ##
165
+ # Evaluators
166
+ #
167
+
168
+ def straight_flush?(cards)
169
+ return false if cards.length != 5
170
+ return true if (straight?(cards) && flush?(cards))
171
+ false
172
+ end
173
+
174
+ def four_of_a_kind?(cards)
175
+ return false unless (4..5).include?(cards.length)
176
+ return true if cards.join(' ') =~ /(.). \1. \1. \1./
177
+ false
178
+ end
179
+
180
+ def full_house?(cards)
181
+ return false if cards.length != 5
182
+ return true if cards.join(' ') =~ /(.). \1. \1. (.). \2./
183
+ return true if cards.join(' ') =~ /(.). \1. (.). \2. \2./
184
+
185
+ false
186
+ end
187
+
188
+ def flush?(cards)
189
+ return false if cards.length != 5
190
+ return true if cards.join(' ') =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/
191
+
192
+ false
193
+ end
194
+
195
+ def straight?(cards)
196
+ return false if cards.length != 5
197
+
198
+ ranks = cards.map { |card| card.rank }
199
+ return true if ranks == [0, 1, 2, 3, 12]
200
+
201
+ result = true
202
+ (0..3).each do |i|
203
+ if(ranks[i+1] - ranks[i] != 1)
204
+ result = false
205
+ break
206
+ end
207
+ end
208
+ result
209
+ end
210
+
211
+ def three_of_a_kind?(cards)
212
+ return false unless (3..5).include?(cards.length)
213
+ return true if cards.join(' ') =~ /(.). \1. \1./
214
+
215
+ false
216
+ end
217
+
218
+ def two_pair?(cards)
219
+ return false unless (4..5).include?(cards.length)
220
+ return true if cards.join(' ') =~ /(.). \1. (.). \2./
221
+
222
+ false
223
+ end
224
+
225
+ def pair?(cards)
226
+ return false unless (2..5).include?(cards.length)
227
+ return true if cards.join(' ') =~ /(.). \1./
228
+
229
+ false
230
+ end
231
+
232
+ def high_card?(cards)
233
+ true
234
+ end
235
+ end
236
+ end
data/lib/player.rb ADDED
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + "/hand"
2
+
3
+ module Holdem
4
+ class Player
5
+ MAX_ACTIONS = 300
6
+
7
+ attr_reader :hole_cards, :hand, :in_pot, :actions, :in_round, :folded, :delta
8
+
9
+ def initialize
10
+ @hole_cards = []
11
+ @actions = []
12
+ @in_pot = 0.0
13
+ @in_round = 0.0
14
+ @delta = 0.0
15
+ @folded = false
16
+ end
17
+
18
+ def deal(card)
19
+ raise "Player already has 2 hole cards" if @hole_cards.length >= 2
20
+ @hole_cards << card
21
+ end
22
+
23
+ def find_hand(board)
24
+ all_cards = (board + @hole_cards).flatten
25
+ @hand = Hand.new(all_cards)
26
+ end
27
+
28
+ def pay(amount)
29
+ @in_pot += amount
30
+ @in_round += amount
31
+ @delta -= amount
32
+ end
33
+
34
+ def award(amount)
35
+ @delta += amount
36
+ end
37
+
38
+ # override this for subclass Players
39
+ def act(game)
40
+ return :neutral
41
+ end
42
+
43
+ def record_action(action)
44
+ @actions << action
45
+ @actions.delete_at(0) if @actions.length > MAX_ACTIONS
46
+ end
47
+
48
+ def next_round
49
+ @in_round = 0.0
50
+ end
51
+
52
+ def fold
53
+ @folded = true
54
+ end
55
+ end
56
+ end
data/lib/rholdem.rb ADDED
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + "/card"
2
+ require File.dirname(__FILE__) + "/deck"
3
+ require File.dirname(__FILE__) + "/hand"
4
+ require File.dirname(__FILE__) + "/player"
5
+ require File.dirname(__FILE__) + "/game"
6
+
7
+ module Holdem
8
+ VERSION = '0.0.1'
9
+ end
data/spec/card_spec.rb ADDED
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + "/../lib/card"
2
+ include Holdem
3
+
4
+ describe Card do
5
+ before(:each) do
6
+ @c = Card.new('As')
7
+ end
8
+
9
+ it "should be creatable from a 2-char string" do
10
+ @c.should_not be_nil
11
+ end
12
+
13
+ it "should assign the rank when created" do
14
+ # RANKS = "23456789TJQKA"
15
+ @c.rank.should == 12
16
+ end
17
+
18
+ it "should assign the suit when created" do
19
+ # SUITS = "cdhs"
20
+ @c.suit.should == 3
21
+ end
22
+
23
+ it "should output the abbreviated card name" do
24
+ @c.to_s.should == 'As'
25
+ end
26
+
27
+ it "should show error for bad card initializers" do
28
+ lambda { Card.new('Za') }.should raise_error
29
+ lambda { Card.new('0') }.should raise_error
30
+ end
31
+
32
+ it "should evaluate two cards as equal if their ranks and suits match" do
33
+ c1 = Card.new('3c')
34
+ c2 = Card.new('3c')
35
+
36
+ c1.should == c2
37
+ c1.should eql?(c2)
38
+ end
39
+
40
+ it "should evaluate two cards as equal if their ranks match" do
41
+ c1 = Card.new('3d')
42
+ c2 = Card.new('3c')
43
+
44
+ c1.should == c2
45
+ c1.should eql?(c2)
46
+ end
47
+
48
+ it "should evaluate two cards as not equal if their ranks don't match" do
49
+ c1 = Card.new('3h')
50
+ c2 = Card.new('4h')
51
+ c1.should_not == c2
52
+ end
53
+
54
+ it "should evaluate two cards as === if their suits and ranks match" do
55
+ c1 = Card.new('3d')
56
+ c2 = Card.new('3d')
57
+ c1.should === c2
58
+ end
59
+
60
+ it "should evaluate two cards as not === if their suits don't match" do
61
+ c1 = Card.new('3d')
62
+ c2 = Card.new('3h')
63
+ c1.should_not === c2
64
+ end
65
+
66
+ it "should evaluate two cards as not === if their ranks don't match" do
67
+ c1 = Card.new('5d')
68
+ c2 = Card.new('3d')
69
+ c1.should_not === c2
70
+ end
71
+
72
+ it "should return -1 when comparing lower to higher card" do
73
+ c1 = Card.new('3c')
74
+ c2 = Card.new('4d')
75
+ (c1 <=> c2).should == -1
76
+ (c1 > c2).should == false
77
+ (c1 < c2).should == true
78
+ end
79
+
80
+ it "should return 1 when comparing higher to lower card" do
81
+ c1 = Card.new('8c')
82
+ c2 = Card.new('4d')
83
+ (c1 <=> c2).should == 1
84
+ (c1 > c2).should == true
85
+ (c1 < c2).should == false
86
+ end
87
+
88
+ it "should return 0 when cards are equal" do
89
+ c1 = Card.new('8c')
90
+ c2 = Card.new('8d')
91
+ (c1 <=> c2).should == 0
92
+ (c1 > c2).should == false
93
+ (c1 < c2).should == false
94
+ end
95
+ end
data/spec/deck_spec.rb ADDED
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + "/../lib/deck"
2
+ include Holdem
3
+
4
+ describe Deck do
5
+ before(:each) do
6
+ @d = Deck.new
7
+ end
8
+
9
+ it "should have all 52 cards" do
10
+ @d.cards.length.should == 52
11
+ end
12
+
13
+ it "should not have repeated cards" do
14
+ cards = Hash.new
15
+ @d.cards.each do |c|
16
+ cards[c].should be_nil
17
+ cards[c] = 1
18
+ end
19
+ end
20
+
21
+ it "should rearrange the cards after a shuffle" do
22
+ old_sort = @d.cards.dup
23
+ @d.shuffle
24
+ old_sort.should_not == @d.cards
25
+ end
26
+
27
+ it "should be shuffled when created" do
28
+ ordered_cards = []
29
+ Card::SUITS.each_byte do |suit|
30
+ Card::RANKS.each_byte do |rank|
31
+ ordered_cards << Card.new(rank.chr + suit.chr)
32
+ end
33
+ end
34
+
35
+ @d.cards.should_not == ordered_cards
36
+ end
37
+
38
+ it "should return a card from the deck when dealing" do
39
+ @d.deal.class.should == Card
40
+ end
41
+
42
+ it "should remove the card from the deck when dealing" do
43
+ c = @d.deal
44
+ @d.cards.each do |card|
45
+ card.should_not === c
46
+ end
47
+ end
48
+
49
+ it "should have 1 less card after dealing" do
50
+ @d.deal
51
+ @d.cards.length.should == 51
52
+ end
53
+ end