texas_holdem 0.1.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/.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