texas_holdem 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,5 @@
1
+ begin
2
+ require 'redgreen/autotest'
3
+ rescue
4
+ puts "gem install redgreen for colorized output"
5
+ end
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/Gemfile ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Keith McDonnell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
File without changes
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = texas_holdem
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Keith McDonnell. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "texas_holdem"
8
+ gem.summary = %Q{Ruby classes modelling a game Texas Holdem poker}
9
+ gem.description = %Q{Ruby classes modelling a game Texas Holdem poker}
10
+ gem.email = "keith@dancingtext.com"
11
+ gem.homepage = "http://github.com/kmcd/texas_holdem"
12
+ gem.authors = ["Keith McDonnell"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ task :test => :check_dependencies
28
+
29
+ task :default => :test
30
+
31
+ require 'rake/rdoctask'
32
+ Rake::RDocTask.new do |rdoc|
33
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
34
+
35
+ rdoc.rdoc_dir = 'rdoc'
36
+ rdoc.title = "texas_holdem #{version}"
37
+ rdoc.rdoc_files.include('README*')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ end
40
+
41
+ desc 'Create class & unit test'
42
+ task :class, :name do |task,args|
43
+ class_name = args.name.capitalize
44
+ file_name = args.name.downcase
45
+
46
+ write_to "lib/#{file_name}.rb", %Q{class TexasHoldem::#{class_name}
47
+ end}
48
+
49
+ write_to "test/test_#{file_name}.rb", %Q{require 'helper'
50
+ class #{class_name}Test < Test::Unit::TestCase
51
+ def setup
52
+ end
53
+
54
+ test "should " do
55
+ flunk
56
+ end
57
+ end}
58
+ end
59
+
60
+ def write_to(local_filename,text)
61
+ File.open(local_filename, 'w') {|f| f.write(text) }
62
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,83 @@
1
+ require 'timeout'
2
+
3
+ class BettingRound
4
+ attr_accessor :max_wait_time_for_bet
5
+
6
+ def initialize(hand)
7
+ @hand = hand
8
+ @betting_circle = players.enum_for
9
+ players.each {|player| player.add_observer self }
10
+ end
11
+
12
+ def next_player
13
+ @current_player = @betting_circle.next
14
+ rescue StopIteration
15
+ @betting_circle.rewind and retry
16
+ end
17
+
18
+ def max_wait_time_for_bet(seconds)
19
+ Timeout::timeout(seconds) { yield }
20
+ rescue Timeout::Error
21
+ fold @current_player and next_player
22
+ end
23
+
24
+ def current_player
25
+ @current_player || next_player
26
+ end
27
+
28
+ def players
29
+ @players ||= @hand.players
30
+ end
31
+
32
+ def bets
33
+ @bets ||= Hash.new(0)
34
+ end
35
+
36
+ def minimum_bet
37
+ bets.values.empty? ? @hand.minimum_bet : amount_to_match_pot
38
+ end
39
+
40
+ def minimum_raise
41
+ minimum_bet + @hand.minimum_bet
42
+ end
43
+
44
+ def amount_bet
45
+ bets.values.compact.reduce(:+) || 0
46
+ end
47
+
48
+ def finished?
49
+ players.size == 1 || everyone_bet_equal_amount?
50
+ end
51
+
52
+ def update(args)
53
+ if args[:fold]
54
+ fold @current_player
55
+ else
56
+ bet, player = args[:bet][:amount], args[:bet][:player]
57
+ return unless valid? bet, player
58
+ bets[player] += bet
59
+ @hand.pot += bet
60
+ end
61
+ next_player
62
+ end
63
+
64
+ private
65
+
66
+ def valid?(bet,player)
67
+ return unless player.eql? current_player
68
+ bet == minimum_bet || bet >= minimum_raise || bet == 0
69
+ end
70
+
71
+ def everyone_bet_equal_amount?
72
+ bets.values.uniq.size == 1
73
+ end
74
+
75
+ def amount_to_match_pot
76
+ bets.values.max - bets[current_player]
77
+ end
78
+
79
+ def fold(player)
80
+ players.delete player
81
+ @hand.fold player
82
+ end
83
+ end
data/lib/deck.rb ADDED
@@ -0,0 +1,35 @@
1
+ class TexasHoldem::Deck
2
+ FACES, SUITS = "AKQJT98765432", "cdhs"
3
+
4
+ attr_reader :cards
5
+
6
+ def initialize
7
+ build
8
+ shuffle
9
+ end
10
+
11
+ def next_card
12
+ @cards.pop
13
+ end
14
+
15
+ private
16
+
17
+ def build
18
+ @cards = []
19
+
20
+ FACES.each_byte do |f|
21
+ SUITS.each_byte {|s| @cards.push(f.chr + s.chr) }
22
+ end
23
+ end
24
+
25
+ def shuffle
26
+ 3.times do
27
+ shuf = []
28
+ @cards.each do |c|
29
+ loc = rand(shuf.size + 1)
30
+ shuf.insert(loc, c)
31
+ end
32
+ @cards = shuf.reverse
33
+ end
34
+ end
35
+ end
data/lib/game.rb ADDED
@@ -0,0 +1,20 @@
1
+ class TexasHoldem::Game
2
+ SMALL_BLIND_PERCENTAGE = 0.0125
3
+ attr_reader :players, :entrance_fee
4
+
5
+ def initialize(players,entrance_fee)
6
+ @players, @entrance_fee = players, entrance_fee
7
+ end
8
+
9
+ def finished?
10
+ players.size == 1
11
+ end
12
+
13
+ def winner
14
+ players.first if finished?
15
+ end
16
+
17
+ def small_blind
18
+ @entrance_fee * SMALL_BLIND_PERCENTAGE
19
+ end
20
+ end
data/lib/hand.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'enumerated_attribute'
2
+
3
+ class TexasHoldem::Hand
4
+ enum_attr :round, %w( ^pocket flop turn river showdown )
5
+ attr_reader :players, :community_cards
6
+ attr_accessor :pot
7
+
8
+ def initialize(players, small_blind_amount=1)
9
+ @players = players
10
+ @deck = TexasHoldem::Deck.new
11
+ @community_cards = []
12
+ @small_blind_amount = small_blind_amount
13
+ @pot = 0 # Should this be an Observer?
14
+ end
15
+
16
+ def dealer
17
+ @players.first
18
+ end
19
+
20
+ def minimum_bet
21
+ @small_blind_amount * 2
22
+ end
23
+
24
+ def small_blind
25
+ return dealer if players_remaining? 2
26
+ @players[2]
27
+ end
28
+
29
+ def big_blind
30
+ return @players.last if players_remaining? 2
31
+ @players[1]
32
+ end
33
+
34
+ def winner
35
+ if finished?
36
+ @players.first.take_winnings @pot
37
+ @players.first
38
+ end
39
+ end
40
+
41
+ def finished?
42
+ players_remaining? 1
43
+ end
44
+
45
+ def fold(player)
46
+ @players.delete player
47
+ end
48
+
49
+ def deal
50
+ case round
51
+ when :pocket : deal_pocket_cards && deduct_blinds # Move to BettingRound?
52
+ when :flop : deal_community_cards 3
53
+ when :turn : deal_community_cards 1
54
+ when :river : deal_community_cards 1
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def players_remaining?(number)
61
+ @players.size == number
62
+ end
63
+
64
+ def deal_pocket_cards
65
+ @players.each do |player|
66
+ 2.times { player.cards << @deck.next_card }
67
+ end
68
+ end
69
+
70
+ def deal_community_cards(number)
71
+ number.times { @community_cards << @deck.next_card }
72
+ end
73
+
74
+ def deduct_blinds
75
+ @pot += small_blind.bet(@small_blind_amount)
76
+ @pot += big_blind.bet(@small_blind_amount * 2)
77
+ end
78
+ end
data/lib/player.rb ADDED
@@ -0,0 +1,42 @@
1
+ class NotEnoughCashError < StandardError
2
+ end
3
+
4
+ require 'observer'
5
+
6
+ class TexasHoldem::Player
7
+ attr_reader :name, :cards, :cash
8
+ include Observable
9
+
10
+ def initialize(name,cash)
11
+ @name, @cash = name, cash
12
+ @cards = []
13
+ end
14
+
15
+ def bet(amount)
16
+ # TODO: add remaining cash instead of raising an error
17
+ raise NotEnoughCashError unless enough_cash_for?(amount)
18
+ @cash -= amount
19
+ changed true
20
+ notify_observers :bet => { :player => self, :amount => amount }
21
+ amount
22
+ end
23
+
24
+ def check
25
+ bet 0
26
+ end
27
+
28
+ def fold
29
+ changed true
30
+ notify_observers :fold => true
31
+ end
32
+
33
+ def take_winnings(amount)
34
+ @cash += amount
35
+ end
36
+
37
+ private
38
+
39
+ def enough_cash_for?(amount)
40
+ @cash > amount
41
+ end
42
+ end
@@ -0,0 +1,132 @@
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
@@ -0,0 +1,8 @@
1
+ module TexasHoldem
2
+ require 'game'
3
+ require 'hand'
4
+ require 'player'
5
+ require 'deck'
6
+ require 'betting_round'
7
+ require 'players_hand'
8
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support/testing/declarative'
4
+ require 'mocha'
5
+
6
+ # TODO: create a rake task to load irb environment
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+ require 'texas_holdem'
10
+
11
+ class Test::Unit::TestCase
12
+ extend ActiveSupport::Testing::Declarative
13
+
14
+ def assert_cards(number,players)
15
+ assert players.all? {|player| player.cards.size == number }
16
+ end
17
+ end
18
+
19
+ class String
20
+ include Test::Unit::Assertions
21
+
22
+ def hand_name(name)
23
+ assert_equal name, TexasHoldem::PlayersHand.new(self).name
24
+ end
25
+
26
+ def beats(loser)
27
+ winner, loser = TexasHoldem::PlayersHand.new(self), TexasHoldem::PlayersHand.new(loser)
28
+ assert( winner > loser )
29
+ end
30
+ end
31
+
32
+ class TexasHoldem::Hand
33
+ def self.factory(small_blind=1.25)
34
+ players = %w( Amy Bill Carl ).map {|name| TexasHoldem::Player.factory name }
35
+ new players, small_blind
36
+ end
37
+
38
+ def advance_to_round(number)
39
+ (number - 1).times do
40
+ deal
41
+ round_next
42
+ end
43
+ end
44
+ end
45
+
46
+ class TexasHoldem::Player
47
+ def self.factory(name, cash=100)
48
+ new name, cash
49
+ end
50
+ end
51
+
52
+ module HandTestHelper
53
+ def setup
54
+ @hand = TexasHoldem::Hand.factory
55
+ @amy, @bill, @carl = *@hand.players
56
+ end
57
+ end
@@ -0,0 +1,140 @@
1
+ require 'helper'
2
+
3
+ class BettingRoundTest < Test::Unit::TestCase
4
+ include HandTestHelper
5
+
6
+ def setup
7
+ super
8
+ @betting_round = BettingRound.new @hand
9
+ @hand.deal
10
+ end
11
+
12
+ def assert_current_player(player)
13
+ assert_equal player, @betting_round.current_player
14
+ end
15
+
16
+ test "player to the left of big blind is first to bet" do
17
+ assert_current_player @amy
18
+ end
19
+
20
+ test "play continues clockwise around the table" do
21
+ assert_current_player @amy
22
+ @betting_round.next_player
23
+ assert_current_player @bill
24
+ @betting_round.next_player
25
+ assert_current_player @carl
26
+ @betting_round.next_player
27
+ assert_current_player @amy
28
+ end
29
+
30
+ test "should track amount bet" do
31
+ @amy.bet 20
32
+ assert_equal 20, @betting_round.amount_bet
33
+ @bill.bet 20
34
+ assert_equal 40, @betting_round.amount_bet
35
+ @carl.bet 20
36
+ assert_equal 60, @betting_round.amount_bet
37
+ end
38
+
39
+ test "should only accept bet from current player" do
40
+ assert_current_player @amy
41
+ @carl.bet 20
42
+ assert_equal 0, @betting_round.amount_bet
43
+ assert_current_player @amy
44
+ end
45
+
46
+ test "should finish if all players check" do
47
+ @betting_round.players.each {|player| player.check }
48
+ assert @betting_round.finished?
49
+ end
50
+
51
+ test "should finish if all players check or fold" do
52
+ @amy.check
53
+ @bill.check
54
+ @carl.fold
55
+ assert @betting_round.finished?
56
+ end
57
+
58
+ test "should remove player from hand if they fold" do
59
+ player = @betting_round.current_player
60
+ player.fold
61
+ assert_nil @hand.players.find {|p| p == player }
62
+ end
63
+
64
+ test "should finish when all but one fold" do
65
+ @amy.fold
66
+ @carl.fold
67
+ assert @betting_round.finished?
68
+ end
69
+
70
+ test "should have minimum raises equal to big blind" do
71
+ @amy.bet @hand.minimum_bet - 1
72
+ assert_equal 0, @betting_round.amount_bet
73
+ assert_current_player @amy
74
+ end
75
+
76
+ test "betting finishes when everyone puts in same amount" do
77
+ @betting_round.players.each {|player| player.bet 10 }
78
+ assert @betting_round.finished?
79
+ end
80
+
81
+ test "should calculate minimum bet for current player" do
82
+ @amy.bet 20
83
+
84
+ assert_current_player @bill
85
+ assert_equal @betting_round.minimum_bet, 20
86
+ @bill.bet 30
87
+
88
+ assert_current_player @carl
89
+ assert_equal @betting_round.minimum_bet, 30
90
+ @carl.bet 40
91
+
92
+ assert_current_player @amy
93
+ assert_equal @betting_round.minimum_bet, 20
94
+ @amy.bet 20
95
+
96
+ assert_current_player @bill
97
+ assert_equal @betting_round.minimum_bet, 10
98
+ @bill.bet 10
99
+ end
100
+
101
+ test "betting finishes when everyone matches amount previously put in" do
102
+ @amy.bet 20
103
+ @bill.bet 30
104
+ @carl.bet 40
105
+ assert !@betting_round.finished?
106
+ @amy.bet 20
107
+ assert !@betting_round.finished?
108
+ @bill.bet 10
109
+ assert @betting_round.finished?
110
+ end
111
+
112
+ test "should enforce (big blind) minumim bet raise" do
113
+ @amy.bet 10
114
+ assert_equal 10, @betting_round.minimum_bet
115
+ assert_equal 12.5, @betting_round.minimum_raise
116
+
117
+ @bill.bet 12
118
+ assert_current_player @bill
119
+
120
+ @bill.bet 12.5
121
+ assert_current_player @carl
122
+ end
123
+
124
+ test "should add bets to the pot" do
125
+ @amy.bet 20
126
+ @bill.bet 20
127
+ @carl.bet 20
128
+ blinds = 3.75
129
+ assert_equal 60 + blinds, @hand.pot
130
+ end
131
+
132
+ test "should kick player out of the hand if they take too long to bet" do
133
+ @betting_round.max_wait_time_for_bet(0.09) do
134
+ assert_current_player @amy
135
+ sleep 0.1
136
+ assert !@betting_round.players.include?(@amy)
137
+ assert_current_player @bill
138
+ end
139
+ end
140
+ end
data/test/test_deck.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'helper'
2
+
3
+ SORTED_FULL_DECK = %w[ 2c 2d 2h 2s 3c 3d 3h 3s 4c 4d 4h 4s 5c 5d 5h 5s 6c 6d 6h 6s
4
+ 7c 7d 7h 7s 8c 8d 8h 8s 9c 9d 9h 9s Ac Ad Ah As Jc Jd Jh Js Kc Kd Kh Ks Qc
5
+ Qd Qh Qs Tc Td Th Ts]
6
+
7
+ class NewDeckTest < Test::Unit::TestCase
8
+ def setup
9
+ @deck = TexasHoldem::Deck.new
10
+ end
11
+
12
+ test "should have 52 cards initially" do
13
+ assert_equal 52, @deck.cards.size
14
+ end
15
+
16
+ test "should have 52 unique cards" do
17
+ assert_equal 52, @deck.cards.uniq.size
18
+ end
19
+
20
+ test "should have 2 - Ace of Clubs, Spades, Diamonds & Hearts" do
21
+ assert_equal SORTED_FULL_DECK, @deck.cards.sort
22
+ end
23
+ end
24
+
25
+ class DeckDealTest < Test::Unit::TestCase
26
+ def setup
27
+ @deck = TexasHoldem::Deck.new
28
+ end
29
+
30
+ test "should deal the next card" do
31
+ delt_card = @deck.next_card
32
+
33
+ assert_equal 51, @deck.cards.size
34
+ assert SORTED_FULL_DECK.include?(delt_card)
35
+ assert_equal SORTED_FULL_DECK - [delt_card], @deck.cards.sort
36
+ end
37
+ end
data/test/test_game.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ class GameTest < Test::Unit::TestCase
4
+ def setup
5
+ @game = TexasHoldem::Game.new [], 100
6
+ end
7
+
8
+ test "should not have a winner at game start" do
9
+ assert_nil @game.winner
10
+ end
11
+
12
+ test "should have players" do
13
+ assert_equal [], @game.players
14
+ end
15
+
16
+ test "should have small blind as 1.25% of entrance fee" do
17
+ assert_equal 1.25, @game.small_blind
18
+ end
19
+
20
+ test "should know when finshed" do
21
+ @game.stubs(:players).returns [TexasHoldem::Player.new('Foo', 100)]
22
+ assert_equal true, @game.finished?
23
+ end
24
+
25
+ test "should have a winner when finished" do
26
+ winner = TexasHoldem::Player.new 'Foo', 100
27
+ @game.stubs(:players).returns [winner]
28
+ assert_equal winner, @game.winner
29
+ end
30
+ end
data/test/test_hand.rb ADDED
@@ -0,0 +1,158 @@
1
+ require 'helper'
2
+
3
+ class HandTest < Test::Unit::TestCase
4
+ include HandTestHelper
5
+
6
+ test "should not have a winner initially" do
7
+ assert_nil @hand.winner
8
+ end
9
+
10
+ test "should have a winner if everyone else folds" do
11
+ @hand.deal
12
+ @hand.fold @bill
13
+ @hand.fold @carl
14
+
15
+ assert_equal @hand.winner, @amy
16
+ end
17
+
18
+ test "automatically deduct blinds after dealing pocket cards" do
19
+ assert_equal 100, @hand.small_blind.cash
20
+ assert_equal 100, @hand.big_blind.cash
21
+
22
+ @hand.deal
23
+
24
+ assert @hand.pocket?
25
+ assert_equal 98.75, @hand.small_blind.cash
26
+ assert_equal 97.5, @hand.big_blind.cash
27
+ assert_equal 2.5 + 1.25, @hand.pot
28
+ end
29
+
30
+ test "should know when finished" do
31
+ @hand.deal
32
+ @hand.fold @bill
33
+ @hand.fold @carl
34
+
35
+ assert_equal true, @hand.finished?
36
+ end
37
+
38
+ test "pay out winnings when only one player remains" do
39
+ @hand.deal
40
+ @hand.fold @hand.small_blind
41
+ @hand.fold @hand.big_blind
42
+
43
+ assert_equal 103.75, @hand.winner.cash
44
+ end
45
+
46
+ test "minimum bet is double the small blind" do
47
+ assert_equal 2.5, @hand.minimum_bet
48
+ end
49
+ end
50
+
51
+ class PocketHandTest < Test::Unit::TestCase
52
+ include HandTestHelper
53
+
54
+ test "first round should be pocket/hole cards" do
55
+ assert @hand.pocket?
56
+ end
57
+
58
+ test "first round should deal two cards to all players" do
59
+ @hand.deal
60
+ assert_cards 2, @hand.players
61
+ end
62
+
63
+ test "should have a dealer" do
64
+ assert_equal @amy, @hand.dealer
65
+ end
66
+
67
+ test "should have a big blind player" do
68
+ assert_equal @bill, @hand.big_blind
69
+ end
70
+
71
+ test "should have a small blind player" do
72
+ assert_equal @carl, @hand.small_blind
73
+ end
74
+ end
75
+
76
+ class FlopTest < Test::Unit::TestCase
77
+ include HandTestHelper
78
+
79
+ def setup
80
+ super
81
+ @hand.advance_to_round 2
82
+ @hand.deal
83
+ end
84
+
85
+ test "second round should be the flop" do
86
+ assert @hand.flop?
87
+ end
88
+
89
+ test "second round should deal no new cards to any players" do
90
+ assert_cards 2, @hand.players
91
+ end
92
+
93
+ test "second round should deal three community cards" do
94
+ assert_equal 3, @hand.community_cards.size
95
+ end
96
+ end
97
+
98
+ class TurnTest < Test::Unit::TestCase
99
+ include HandTestHelper
100
+
101
+ def setup
102
+ super
103
+ @hand.advance_to_round 3
104
+ @hand.deal
105
+ end
106
+
107
+ test "third round should be the turn" do
108
+ assert @hand.turn?
109
+ end
110
+
111
+ test "third round should deal no new cards to any players" do
112
+ assert_cards 2, @hand.players
113
+ end
114
+
115
+ test "second round should deal one extra community card" do
116
+ assert_equal 4, @hand.community_cards.size
117
+ end
118
+ end
119
+
120
+ class RiverTest < Test::Unit::TestCase
121
+ include HandTestHelper
122
+
123
+ def setup
124
+ super
125
+ @hand.advance_to_round 4
126
+ @hand.deal
127
+ end
128
+
129
+ test "third round should be the river" do
130
+ assert @hand.river?
131
+ end
132
+
133
+ test "third round should deal no new cards to any players" do
134
+ assert_cards 2, @hand.players
135
+ end
136
+
137
+ test "second round should deal one extra community card" do
138
+ assert_equal 5, @hand.community_cards.size
139
+ end
140
+ end
141
+
142
+ class TwoPlayerHandTest < Test::Unit::TestCase
143
+ include HandTestHelper
144
+
145
+ def setup
146
+ super
147
+ @hand = TexasHoldem::Hand.new [@bill, @carl]
148
+ end
149
+
150
+ test "should have the dealer as the small blind" do
151
+ assert_equal @hand.dealer, @bill
152
+ assert_equal @hand.small_blind, @bill
153
+ end
154
+
155
+ test "should have second player as big blind" do
156
+ assert_equal @hand.big_blind, @carl
157
+ end
158
+ end
@@ -0,0 +1,32 @@
1
+ require 'helper'
2
+
3
+ class PlayerTest < Test::Unit::TestCase
4
+ def setup
5
+ @entrance_fee = 100
6
+ @player = TexasHoldem::Player.new 'Carl', @entrance_fee
7
+ end
8
+
9
+ test "should have a name" do
10
+ assert_equal 'Carl', @player.name
11
+ end
12
+
13
+ test "should have no cards initially" do
14
+ assert @player.cards.empty?
15
+ end
16
+
17
+ test "should have cash" do
18
+ assert_equal @entrance_fee, @player.cash
19
+ end
20
+
21
+ test "should place a bet" do
22
+ @player.bet 99
23
+ assert_equal 1, @player.cash
24
+ end
25
+
26
+ test "should NOT bet more than available cash" do
27
+ # FIXME: treat as all in insead of raising an error
28
+ assert_raises(NotEnoughCashError) do
29
+ @player.bet(101)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,117 @@
1
+ require 'helper'
2
+
3
+ class HandTest < Test::Unit::TestCase
4
+ test "should always have cards sorted in ascending order by face value" do
5
+ hand = TexasHoldem::PlayersHand.new('Ad As 5c 6c Jd')
6
+ assert_equal "5c 6c 11d 14s 14d", hand.cards
7
+ end
8
+ end
9
+
10
+ class HandIdentificationTest < Test::Unit::TestCase
11
+ # TODO: add more non-sequential matches
12
+
13
+ test "should recognise a high card" do
14
+ '2d 3s 5c 6c Ad'.hand_name 'high card'
15
+ end
16
+
17
+ test "should recognise a pair" do
18
+ '2d 2s 5c 6c Ad'.hand_name 'one pair'
19
+ end
20
+
21
+ test "should recognise two pair" do
22
+ '2d 2s 5c 5d Ad'.hand_name 'two pair'
23
+ end
24
+
25
+ test "should recognise three of a kind" do
26
+ '2d 2s 2h 5d Ad'.hand_name 'three of a kind'
27
+ end
28
+
29
+ test "should recognise a straight" do
30
+ '2d 3s 4h 5d 6d'.hand_name 'straight'
31
+ '10c Jd Qs Kd As'.hand_name 'straight'
32
+ end
33
+
34
+ test "should recognise a flush" do
35
+ '2h 3h 4h 5h 7h'.hand_name 'flush'
36
+ end
37
+
38
+ test "should recognise a full house" do
39
+ '2d 2s 2h 4d 4c'.hand_name 'full house'
40
+ '2d 2s 4h 4d 4c'.hand_name 'full house'
41
+ '2d 4s 2h 4d 4c'.hand_name 'full house'
42
+ end
43
+
44
+ test "should recognise four of a kind" do
45
+ '2d 2c 2s 2h 3c'.hand_name 'four of a kind'
46
+ end
47
+ end
48
+
49
+ class WinningHandsTest < Test::Unit::TestCase
50
+ test "highest card should win if no scoring poker hands" do
51
+ '2d 3s 5c 6c Ad'.beats '2c 3d 5s 6h Kd'
52
+ end
53
+
54
+ test "one pair beats no matches" do
55
+ '2d 2s 5c 6c Ad'.beats '2c 3d 5s 6h Kd'
56
+ end
57
+
58
+ test "high pair beats low pair" do
59
+ 'Ad As 5c 6c Jd'.beats 'Kc Kd 5s 6h Qd'
60
+ end
61
+
62
+ test "highest card wins when pairs are equal" do
63
+ 'Ad As 5c 6c Kd'.beats 'Ac Ad 5s 6h Qd'
64
+ end
65
+
66
+ test "two pair beats a pair" do
67
+ 'Ad As 5c 5d Kd'.beats 'Ac Ad 5s 6h Qd'
68
+ end
69
+
70
+ test "highest pair wins if both two pairs" do
71
+ '2d 2s 4c 4d Kd'.beats '2c 2d 3s 3h Qd'
72
+ 'Ad As 4c 4d Kd'.beats 'Kc Kd Qs Qh Jd'
73
+ end
74
+
75
+ test "highest card wins if both hands have identincal two pairs" do
76
+ '2d 2s 4c 4d 6d'.beats '2d 2s 4c 4d 5d'
77
+ end
78
+
79
+ test "three of a kind beats two pair" do
80
+ 'Ad As Ac 5d Kd'.beats 'Kc Kd 5s 5h Qd'
81
+ end
82
+
83
+ test "stright beats three of a kind" do
84
+ '2d 3c 4h 5s 6d'.beats 'Ad As Ac 5d Kd'
85
+ end
86
+
87
+ test "higher stright beats lower straight" do
88
+ '3c 4h 5s 6d 7d'.beats '2d 3c 4h 5s 6d'
89
+ '10c Jd Qs Kd As'.beats '9h 10c Jd Qs Kd'
90
+ end
91
+
92
+ test "flush beats a straight" do
93
+ '2h 3h 4h 5h 7h'.beats '9h 10c Jd Qs Kd'
94
+ end
95
+
96
+ test "high card determines winner of flushes" do
97
+ '2h 3h 4h 5h 8h'.beats '2d 3d 4d 5d 7d'
98
+ end
99
+
100
+ test "full house beats a flush" do
101
+ '2d 2s 2h 4d 4c'.beats '2d 3d 4d 5d 7d'
102
+ end
103
+
104
+ test "higher three of a kind determines full house winner" do
105
+ '3d 3s 3h 4d 4c'.beats '2s 2h 2c Ad Ac'
106
+ 'Ad As Ah 4d 4c'.beats 'Ks Kh Kc Ad Ac'
107
+ end
108
+
109
+ test "four of a kind beats a full house" do
110
+ '2d 2c 2s 2h 3c'.beats '3d 3s 3h 4d 4c'
111
+ end
112
+
113
+ test "higher card determines four of a kind winner" do
114
+ '3d 3c 3s 3h 4c'.beats '2d 2c 2s 2h 5c'
115
+ 'Ad Ac As Ah 4c'.beats 'Kd Kc Ks Kh 5c'
116
+ end
117
+ end
@@ -0,0 +1,9 @@
1
+ require 'helper'
2
+
3
+ class TexasHoldemTest < Test::Unit::TestCase
4
+ def setup
5
+ end
6
+
7
+ test "should have end to end tests here" do
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: texas_holdem
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Keith McDonnell
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-29 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Ruby classes modelling a game Texas Holdem poker
23
+ email: keith@dancingtext.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - LICENSE
30
+ - README
31
+ - README.rdoc
32
+ files:
33
+ - .autotest
34
+ - .document
35
+ - .gitignore
36
+ - Gemfile
37
+ - LICENSE
38
+ - README
39
+ - README.rdoc
40
+ - Rakefile
41
+ - VERSION
42
+ - lib/betting_round.rb
43
+ - lib/deck.rb
44
+ - lib/game.rb
45
+ - lib/hand.rb
46
+ - lib/player.rb
47
+ - lib/players_hand.rb
48
+ - lib/texas_holdem.rb
49
+ - test/helper.rb
50
+ - test/test_betting_round.rb
51
+ - test/test_deck.rb
52
+ - test/test_game.rb
53
+ - test/test_hand.rb
54
+ - test/test_player.rb
55
+ - test/test_players_hand.rb
56
+ - test/test_texas_holdem.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/kmcd/texas_holdem
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --charset=UTF-8
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.7
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Ruby classes modelling a game Texas Holdem poker
91
+ test_files:
92
+ - test/test_game.rb
93
+ - test/test_player.rb
94
+ - test/test_betting_round.rb
95
+ - test/test_hand.rb
96
+ - test/test_deck.rb
97
+ - test/test_players_hand.rb
98
+ - test/helper.rb
99
+ - test/test_texas_holdem.rb