ruby_holdem 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29fbffb1efbe129bc60cc9b38ef8f048c2e18ab9
4
- data.tar.gz: 0b99f0a5ebf3996ae2fe11b169647cf1dd74384b
3
+ metadata.gz: 1dd48e6184eca19ac256511789c5effc59d11afb
4
+ data.tar.gz: 2a199bdde72d3903b13453f8743889fa83a5b0f7
5
5
  SHA512:
6
- metadata.gz: c4b072056f11a740fe63f29deceb02a692114c152b6532ccb83364757b83728f7cb4425f063f8baeee3373672a31eaf6944a748b42dc9ee35a3c155b8b4bd172
7
- data.tar.gz: 409610a9e35b4ec60cfb4730edea0ca4d7958f53682e20b039a502d2d2e3264f89c4d8e26327f577f023324facf3112340c77b4fa922d4bc046068cb76036e8e
6
+ metadata.gz: 77f78e0b36c92cd26b9dda7ef221044b6ad18bef2d0798870633d31a5617dc2136d4e5943cf829c78de1b0a97b7ee157b7da1ab277b4788b36902576c4f5c4f7
7
+ data.tar.gz: e3d0e79ae799214fbafbe318abdb276d3d95c247ebffd6efdc7093ca8f7a9d40d28014a92b37f02e913dd4b8ea8463436e852dc2f63137ec06670a33f02307b6
data/README.md CHANGED
@@ -3,8 +3,6 @@ RubyHoldem is a set of classes which track the game state of a texas holdem poke
3
3
 
4
4
  ### Installation
5
5
  ```
6
- git clone git@github.com:evanrolfe/ruby-holdem.git
7
- cd ruby-holdem/
8
6
  gem install ruby_holdem
9
7
  ```
10
8
 
@@ -15,11 +13,11 @@ require 'ruby_holdem'
15
13
  players = ["Jack", "Joe", "Jil"]
16
14
  poker_round = RubyHoldem::Round.new(players, 2, 4)
17
15
 
18
- poker_round.make_move('bet', 2) # Jack bets small blinds
19
- poker_round.make_move('bet', 4) # Joe bets big blinds
16
+ poker_round.make_move('raise', 2) # Jack raises small blinds
17
+ poker_round.make_move('raise', 4) # Joe raises big blinds
20
18
  poker_round.make_move('fold') # Jil folds
21
19
  poker_round.make_move('call') # Jack calls
22
- poker_round.make_move('call') # Joe calls
20
+ poker_round.make_move('check') # Joe calls
23
21
  poker_round.next_stage
24
22
 
25
23
  puts poker_round.community_cards.join(' ')
@@ -28,13 +26,19 @@ puts poker_round.community_cards.join(' ')
28
26
  puts poker_round.pot_amount
29
27
  # => 8
30
28
 
31
- poker_round.make_move('bet', 3) # Jack bets 3
29
+ poker_round.make_move('raise', 3) # Jack raises 3
32
30
  poker_round.make_move('fold') # Joe folds
33
31
 
34
32
  puts poker_round.winner
35
33
  # => Jack
36
34
  ```
37
35
 
36
+ ### TODO
37
+ - Break monolithic ```Round``` class into smaller classes, i.e. ```Round```, ```RoundState``` and ```RoundPlayerMove```
38
+ - Use rspec 3 style (subjects, expects etc.)
39
+ - Get rid of the stubbing of action_history instance var in round_spec.rb
40
+ - Have ```Game``` keep track of an entire poker game consisting of multiple rounds
41
+
38
42
  ### License
39
43
 
40
44
  RubyHoldem uses the MIT license. Please check the [LICENSE](https://github.com/evanrolfe/ruby-holdem/blob/master/LICENSE) file for more details.
data/lib/ruby_holdem.rb CHANGED
@@ -4,4 +4,10 @@ require 'ruby_holdem/errors'
4
4
 
5
5
  require 'ruby_holdem/dealer'
6
6
  require 'ruby_holdem/round'
7
- require 'ruby_holdem/round_player'
7
+ require 'ruby_holdem/round/move_factory'
8
+ require 'ruby_holdem/round/move_history'
9
+ require 'ruby_holdem/round/move_history_computations'
10
+ require 'ruby_holdem/round/move_validator'
11
+ require 'ruby_holdem/round/player'
12
+
13
+ require 'pry'
@@ -1,7 +1,7 @@
1
1
  module RubyHoldem
2
- class MinBetNotMeet < StandardError
2
+ class MinRaiseNotMeet < StandardError
3
3
  end
4
4
 
5
- class NotEnoughMoney < StandardError
5
+ class InsufficientFunds < StandardError
6
6
  end
7
7
  end
@@ -4,151 +4,57 @@ module RubyHoldem
4
4
  class Round
5
5
  extend Forwardable
6
6
 
7
- attr_reader :players,
8
- :small_blinds,
7
+ attr_reader :small_blinds,
9
8
  :big_blinds,
10
9
  :pot_amount,
11
- :current_stage,
12
- :action_history,
13
- :dealer,
14
- :turns_played
10
+ :players,
11
+ :state
15
12
 
16
13
  def_delegator :@dealer, :community_cards
17
14
 
15
+ def_delegators :@move_history_computations, :ready_for_next_stage?,
16
+ :has_winner?,
17
+ :winner,
18
+ :player_in_turn,
19
+ :players_still_in_round,
20
+ :highest_bet_placed
21
+
22
+ def_delegators :@move_history, :stage,
23
+ :last_move,
24
+ :turns_played,
25
+ :moves
26
+
18
27
  STAGES = %w(pre_flop flop turn river show_down)
19
28
 
20
29
  def initialize(players, small_blinds, big_blinds)
21
- @small_blinds, @big_blinds = small_blinds, big_blinds
22
- @current_stage = STAGES[0]
30
+ @small_blinds = small_blinds
31
+ @big_blinds = big_blinds
23
32
  @pot_amount = 0
24
- @action_history = []
25
33
 
26
- @players = players.map { |player| RoundPlayer.new(player) }
34
+ @players = players.map { |player| Player.new(player) }
35
+ @move_history = MoveHistory.new
36
+ @move_history_computations = MoveHistoryComputations.new(@players, @move_history)
37
+
27
38
  @dealer = Dealer.new
28
39
  @dealer.deal_hole_cards(@players)
29
40
  end
30
41
 
31
- # TODO: Extract the code relating to making a move into its own class to separate the logic
32
- # behind making a move and the game state methods
33
- def make_move(move, amount=nil)
34
- if turns_played == 0
35
- apply_bet(small_blinds)
36
- elsif turns_played == 1
37
- apply_bet(big_blinds)
38
- elsif move == 'bet'
39
- apply_bet(amount)
40
- elsif move == 'call'
41
- apply_call
42
- elsif move == 'fold'
43
- apply_fold
44
- end
45
- end
46
-
47
- def next_stage
48
- raise StandardError unless ready_for_next_stage? && @current_stage != 'show_down'
49
-
50
- @current_stage = STAGES[STAGES.index(@current_stage)+1]
51
- @dealer.deal_community_cards(@current_stage)
52
- end
53
-
54
- def ready_for_next_stage?
55
- return false unless every_player_has_called? && turns_played_in_stage > 0
56
-
57
- players_still_in_round.map { |player| (player.current_bet_amount == highest_bet_placed) }.all?
58
- end
59
-
60
- def has_winner?
61
- (current_stage == 'show_down' || players_still_in_round.count == 1)
62
- end
63
-
64
- def winner
65
- return players_still_in_round[0] if players_still_in_round.count == 1
66
- return players_still_in_round[2]
67
- end
68
-
69
- # TODO: Refactor this method to make it more readable
70
- def player_in_turn #The player whose turn it is to make a move
71
- return players[0] if action_history.length == 0
72
-
73
- last_player_index = players.index(action_history.last[:player])
74
- player_found = false
75
- increment=1
76
-
77
- until player_found
78
- next_player = players[(last_player_index + increment) % players.length] #Wrap around the array once end reached
79
- player_found = true if players_still_in_round.include?(next_player)
80
- increment += 1
81
- end
82
-
83
- next_player
84
- end
85
-
86
- def players_still_in_round
87
- players.select do |round_player|
88
- folds = action_history.select { |action| action[:move] == 'fold' && action[:player] == round_player }
89
- (folds.length == 0)
90
- end
91
- end
92
-
93
- def highest_bet_placed
94
- players_still_in_round.max_by(&:current_bet_amount).current_bet_amount
95
- end
96
-
97
- def last_move
98
- action_history.last
99
- end
42
+ def make_move(move_type, amount=nil)
43
+ move = MoveFactory.new(self, player_in_turn, move_type, amount).build
100
44
 
101
- private
45
+ MoveValidator.new(self, move).validate
102
46
 
103
- def turns_played
104
- action_history.length
105
- end
106
-
107
- def turns_played_in_stage
108
- action_history.select { |action| action[:stage] == @current_stage }.length
109
- end
110
-
111
- def every_player_has_called?
112
- players_num_calls = players_still_in_round.map do |round_player|
113
- calls = action_history.select { |action| action[:stage] == @current_stage && action[:move] == 'call' && action[:player] == round_player }
114
- calls.length
47
+ unless move[:amount].nil?
48
+ player_in_turn.current_bet_amount += move[:amount]
49
+ @pot_amount += move[:amount]
115
50
  end
116
51
 
117
- players_num_calls.map { |num_calls| num_calls >= 1 }.all?
52
+ @move_history.add_move(move)
118
53
  end
119
54
 
120
- def apply_bet(amount)
121
- raise MinBetNotMeet if amount < min_bet_amount_for_player(player_in_turn)
122
-
123
- #TODO: Go all in instead of raising an error
124
- raise NotEnoughMoney unless player_can_afford_bet?(player_in_turn, amount)
125
-
126
- @pot_amount += amount
127
- player_in_turn.current_bet_amount += amount
128
- action_history << { player: player_in_turn, stage: current_stage, move: 'bet', amount: amount}
129
- end
130
-
131
- def apply_call
132
- amount = min_bet_amount_for_player(player_in_turn)
133
- raise NotEnoughMoney unless player_can_afford_bet?(player_in_turn, amount)
134
-
135
- @pot_amount += amount
136
- player_in_turn.current_bet_amount += amount
137
-
138
- action_history << { player: player_in_turn, stage: current_stage, move: 'call', amount: amount}
139
- end
140
-
141
- def apply_fold
142
- action_history << { player: player_in_turn, stage: current_stage, move: 'fold', amount: 0}
143
- end
144
-
145
- def min_bet_amount_for_player(player)
146
- highest_bet_placed - player.current_bet_amount
147
- end
148
-
149
- # TODO:
150
- def player_can_afford_bet?(player, bet_amount)
151
- true
55
+ def next_stage
56
+ @move_history.next_stage
57
+ @dealer.deal_community_cards(@move_history.stage)
152
58
  end
153
59
  end
154
60
  end
@@ -0,0 +1,51 @@
1
+ module RubyHoldem
2
+ class Round
3
+ class MoveFactory
4
+ attr_reader :round, :player, :move_type, :amount
5
+
6
+ def initialize(round, player, move_type, amount)
7
+ @round = round
8
+ @player = player
9
+ @move_type = move_type
10
+ @amount = amount
11
+ end
12
+
13
+ def build
14
+ {
15
+ player: player,
16
+ stage: stage,
17
+ move_type: move_type,
18
+ amount: actual_amount
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def actual_amount
25
+ if move_type == "call"
26
+ highest_bet_placed - current_bet_amount
27
+ else
28
+ amount
29
+ end
30
+ end
31
+
32
+ #
33
+ # Dependencies on Round class
34
+ #
35
+ def stage
36
+ round.stage
37
+ end
38
+
39
+ def highest_bet_placed
40
+ @highest_bet_placed ||= round.highest_bet_placed
41
+ end
42
+
43
+ #
44
+ # Dependencies on Player class
45
+ #
46
+ def current_bet_amount
47
+ @current_bet_amount ||= player.current_bet_amount
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ module RubyHoldem
2
+ class Round
3
+ class MoveHistory
4
+ attr_reader :moves, :stage
5
+
6
+ def initialize
7
+ @moves = []
8
+ @stage = STAGES.first
9
+ end
10
+
11
+ def add_move(move)
12
+ moves << move
13
+ end
14
+
15
+ def last_move
16
+ moves.last
17
+ end
18
+
19
+ def turns_played
20
+ moves.count
21
+ end
22
+
23
+ def next_stage
24
+ @stage = STAGES[STAGES.index(stage)+1]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,105 @@
1
+ module RubyHoldem
2
+ class Round
3
+ class MoveHistoryComputations
4
+ attr_reader :players, :move_history
5
+
6
+ def initialize(players, move_history)
7
+ @players = players
8
+ @move_history = move_history
9
+ end
10
+
11
+ def highest_bet_placed
12
+ get_current_bet_amount_for_player(
13
+ players_still_in_round.max_by(&:current_bet_amount)
14
+ )
15
+ end
16
+
17
+ def ready_for_next_stage?
18
+ every_player_has_checked? && turns_played_in_stage > 0
19
+ end
20
+
21
+ def has_winner?
22
+ (stage == 'show_down' || players_still_in_round.count == 1)
23
+ end
24
+
25
+ # TODO: Compare the hands of the two players
26
+ def winner
27
+ return players_still_in_round[0] if players_still_in_round.count == 1
28
+ end
29
+
30
+ def turns_played_in_stage
31
+ moves.select { |move| move[:stage] == stage }.length
32
+ end
33
+
34
+ def player_in_turn #The player whose turn it is to make a move
35
+ return players[0] if moves.length == 0
36
+
37
+ last_player = moves.last[:player]
38
+ i = (players.index(last_player) + 1) % players.length
39
+
40
+ player_found = false
41
+ until player_found
42
+ at_end_of_array = (i == (players.length - 1))
43
+
44
+ if player_is_folded?(players[i]) && !at_end_of_array
45
+ i += 1
46
+ elsif player_is_folded?(players[i]) && at_end_of_array
47
+ i = 0
48
+ else
49
+ player_found = true
50
+ end
51
+ end
52
+ players[i]
53
+ end
54
+
55
+ def every_player_has_checked?
56
+ players_num_checks = players_still_in_round.map do |round_player|
57
+ checks = moves.select do |move|
58
+ move[:stage] == stage &&
59
+ move[:move_type] == 'check' &&
60
+ move[:player] == round_player
61
+ end
62
+ checks.length
63
+ end
64
+
65
+ players_num_checks.map { |num_checks| num_checks >= 1 }.all?
66
+ end
67
+
68
+ def players_still_in_round
69
+ players.select do |round_player|
70
+ folds = moves.select do |move|
71
+ move[:move_type] == 'fold' && move[:player] == round_player
72
+ end
73
+
74
+ folds.length == 0
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def player_is_folded?(player)
81
+ moves.select do |move|
82
+ move[:move_type] == 'fold' && move[:player] == player
83
+ end.any?
84
+ end
85
+
86
+ #
87
+ # Dependencies on MoveHistory class
88
+ #
89
+ def moves
90
+ move_history.moves
91
+ end
92
+
93
+ def stage
94
+ move_history.stage
95
+ end
96
+
97
+ #
98
+ # Dependencies on Player class
99
+ #
100
+ def get_current_bet_amount_for_player(player)
101
+ player.current_bet_amount
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,110 @@
1
+ module RubyHoldem
2
+ class Round
3
+ class MoveValidator
4
+ attr_reader :round, :move
5
+
6
+ def initialize(round, move)
7
+ @round = round
8
+ @move = move
9
+ end
10
+
11
+ def validate
12
+ if blinds_turn? && blinds_not_met?
13
+ raise MinRaiseNotMeet, "You must bet blinds."
14
+ end
15
+
16
+ send("validate_#{move_type}")
17
+ end
18
+
19
+ private
20
+
21
+ def validate_raise
22
+ if amount < min_raise_amount
23
+ raise MinRaiseNotMeet
24
+ end
25
+
26
+ if !player_can_afford_raise?
27
+ raise InsufficientFunds
28
+ end
29
+ end
30
+
31
+ def validate_call
32
+ if !player_can_afford_raise?
33
+ raise InsufficientFunds
34
+ end
35
+ end
36
+
37
+ def validate_check
38
+ if min_raise_amount > 0
39
+ raise MinRaiseNotMeet
40
+ end
41
+ end
42
+
43
+ def validate_fold
44
+ true # NOTE: You can always fold as long as its not a blinds turn
45
+ end
46
+
47
+ def min_raise_amount
48
+ @min_raise_amount ||= highest_bet_placed - current_bet_amount
49
+ end
50
+
51
+ # TODO:
52
+ def player_can_afford_raise?
53
+ true
54
+ end
55
+
56
+ def blinds_turn?
57
+ turns_played == 0 || turns_played == 1
58
+ end
59
+
60
+ def blinds_not_met?
61
+ if turns_played == 0
62
+ amount < small_blinds
63
+ elsif turns_played == 1
64
+ amount < big_blinds
65
+ end
66
+ end
67
+
68
+ #
69
+ # Dependencies on Round class
70
+ #
71
+ def highest_bet_placed
72
+ @highest_bet_placed ||= round.highest_bet_placed
73
+ end
74
+
75
+ def turns_played
76
+ @turns_played ||= round.turns_played
77
+ end
78
+
79
+ def small_blinds
80
+ @small_blinds ||= round.small_blinds
81
+ end
82
+
83
+ def big_blinds
84
+ @big_blinds ||= round.big_blinds
85
+ end
86
+
87
+ #
88
+ # Dependencies on Player class
89
+ #
90
+ def current_bet_amount
91
+ @current_bet_amount ||= player.current_bet_amount
92
+ end
93
+
94
+ #
95
+ # Dependencies on MoveFactory
96
+ #
97
+ def amount
98
+ move[:amount]
99
+ end
100
+
101
+ def move_type
102
+ move[:move_type]
103
+ end
104
+
105
+ def player
106
+ move[:player]
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,13 @@
1
+ module RubyHoldem
2
+ class Round
3
+ class Player < SimpleDelegator
4
+ attr_accessor :hole_cards, :current_bet_amount
5
+
6
+ def initialize(player)
7
+ @hole_cards = []
8
+ @current_bet_amount = 0
9
+ super(player)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module RubyHoldem
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe RubyHoldem::Round::MoveFactory do
5
+ describe "#build" do
6
+ let(:round) { double(RubyHoldem::Round) }
7
+ let(:stage) { RubyHoldem::Round::STAGES.first }
8
+ let(:player) { double(RubyHoldem::Round::Player) }
9
+
10
+ subject { move_factory.build }
11
+
12
+ context "with a move_type of call" do
13
+ let(:move_type) { "call" }
14
+ let(:amount) { nil }
15
+ let(:move_factory) { RubyHoldem::Round::MoveFactory.new(round, player, move_type, amount) }
16
+ let(:highest_bet_placed) { 100 }
17
+ let(:current_bet_amount) { 90 }
18
+ let(:expected_amount) { highest_bet_placed - current_bet_amount }
19
+
20
+ before do
21
+ allow(round).to receive(:stage).and_return(stage)
22
+ allow(round).to receive(:highest_bet_placed).and_return(highest_bet_placed)
23
+ allow(player).to receive(:current_bet_amount).and_return(current_bet_amount)
24
+ end
25
+
26
+ it do
27
+ is_expected.to eq(
28
+ player: player,
29
+ stage: stage,
30
+ move_type: move_type,
31
+ amount: expected_amount
32
+ )
33
+ end
34
+ end
35
+
36
+ {
37
+ "raise" => 10,
38
+ "check" => nil,
39
+ "fold" => nil
40
+ }.each do |move_type, amount|
41
+ context "with a move_type of #{move_type}" do
42
+ let(:move_factory) { RubyHoldem::Round::MoveFactory.new(round, player, move_type, amount) }
43
+
44
+ before do
45
+ allow(round).to receive(:stage).and_return(stage)
46
+ end
47
+
48
+ it do
49
+ is_expected.to eq(
50
+ player: player,
51
+ stage: stage,
52
+ move_type: move_type,
53
+ amount: amount
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,274 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyHoldem::Round::MoveHistoryComputations do
4
+ let(:player1) { RubyHoldem::Round::Player.new("Jack") }
5
+ let(:player2) { RubyHoldem::Round::Player.new("Joe") }
6
+ let(:player3) { RubyHoldem::Round::Player.new("Jil") }
7
+ let(:players) { [player1, player2, player3] }
8
+
9
+ let(:moves) { [] }
10
+ let(:stage) { RubyHoldem::Round::STAGES.first }
11
+ let(:move_history) { RubyHoldem::Round::MoveHistory.new }
12
+ let(:move_history_computations) do
13
+ RubyHoldem::Round::MoveHistoryComputations.new(players, move_history)
14
+ end
15
+
16
+ before do
17
+ allow(move_history).to receive(:moves).and_return(moves)
18
+ allow(move_history).to receive(:stage).and_return(stage)
19
+ end
20
+
21
+ describe "#highest_bet_placed" do
22
+ before do
23
+ allow(player1).to receive(:current_bet_amount).and_return(0)
24
+ allow(player2).to receive(:current_bet_amount).and_return(5)
25
+ allow(player3).to receive(:current_bet_amount).and_return(10)
26
+ end
27
+
28
+ subject { move_history_computations.highest_bet_placed }
29
+
30
+ it { is_expected.to eq(10) }
31
+ end
32
+
33
+ describe "#ready_for_next_stage?" do
34
+ context "not every player has checked yet" do
35
+ let(:moves) do
36
+ [
37
+ { stage: 'pre_flop', player: players[0], amount: 10, move_type: 'raise' },
38
+ { stage: 'pre_flop', player: players[1], amount: nil, move_type: 'fold' },
39
+ { stage: 'pre_flop', player: players[2], amount: nil, move_type: 'check' },
40
+ { stage: 'pre_flop', player: players[0], amount: nil, move_type: 'check' },
41
+ ]
42
+ end
43
+
44
+ subject { move_history_computations.ready_for_next_stage? }
45
+
46
+ it { is_expected.to be_truthy }
47
+ end
48
+
49
+ context "no turns have been played yet in this stage" do
50
+ let(:moves) { [] }
51
+
52
+ subject { move_history_computations.ready_for_next_stage? }
53
+
54
+ it { is_expected.to be_falsey }
55
+ end
56
+ end
57
+
58
+ describe "#has_winner?" do
59
+ context "round won after every other player folds" do
60
+ let(:moves) do
61
+ [
62
+ { stage: 'pre_flop', player: players[0], amount: 1, move_type: 'raise' },
63
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
64
+ { stage: 'pre_flop', player: players[2], amount: 0, move_type: 'fold'},
65
+ { stage: 'pre_flop', player: players[0], amount: 3, move_type: 'check' },
66
+ { stage: 'flop', player: players[1], amount: 0, move_type: 'check' },
67
+ { stage: 'flop', player: players[0], amount: 0, move_type: 'fold' }
68
+ ]
69
+ end
70
+
71
+ subject { move_history_computations.has_winner? }
72
+
73
+ it { is_expected.to be_truthy }
74
+ end
75
+
76
+ context "in stage showdown" do
77
+ let(:stage) { RubyHoldem::Round::STAGES.last }
78
+
79
+ subject { move_history_computations.has_winner? }
80
+
81
+ it { is_expected.to be_truthy }
82
+ end
83
+ end
84
+
85
+ describe "#winner" do
86
+ context "round won after every other player folds" do
87
+ let(:moves) do
88
+ [
89
+ { stage: 'pre_flop', player: players[0], amount: 1, move_type: 'raise' },
90
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
91
+ { stage: 'pre_flop', player: players[2], amount: 0, move_type: 'fold'},
92
+ { stage: 'pre_flop', player: players[0], amount: 3, move_type: 'check' },
93
+ { stage: 'flop', player: players[1], amount: 0, move_type: 'check' },
94
+ { stage: 'flop', player: players[0], amount: 0, move_type: 'fold' }
95
+ ]
96
+ end
97
+
98
+ subject { move_history_computations.winner }
99
+
100
+ it "returns the only player who hasn't folded" do
101
+ is_expected.to eq(players[1])
102
+ end
103
+ end
104
+
105
+ context "in stage showdown" do
106
+ let(:stage) { RubyHoldem::Round::STAGES.last }
107
+
108
+ subject { move_history_computations.winner }
109
+
110
+ xit "returns the player with the better hand" do
111
+
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#turns_played_in_stage" do
117
+ context "no turns played" do
118
+ let(:moves) { [] }
119
+
120
+ subject { move_history_computations.turns_played_in_stage }
121
+
122
+ it { is_expected.to eq(0) }
123
+ end
124
+
125
+ context "turns played in pre_flop" do
126
+ let(:moves) do
127
+ [
128
+ { stage: 'pre_flop', player: players[0], amount: 1, move_type: 'raise' },
129
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
130
+ { stage: 'pre_flop', player: players[2], amount: 0, move_type: 'fold'},
131
+ ]
132
+ end
133
+
134
+ subject { move_history_computations.turns_played_in_stage }
135
+
136
+ it { is_expected.to eq(3) }
137
+ end
138
+
139
+ context "turns played in pre_flop but no turns played in flop" do
140
+ let(:stage) { 'flop' }
141
+ let(:moves) do
142
+ [
143
+ { stage: 'pre_flop', player: players[0], amount: 1, move_type: 'raise' },
144
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
145
+ { stage: 'pre_flop', player: players[2], amount: 0, move_type: 'fold'},
146
+ { stage: 'pre_flop', player: players[0], amount: 3, move_type: 'check' }
147
+ ]
148
+ end
149
+
150
+ subject { move_history_computations.turns_played_in_stage }
151
+
152
+ it { is_expected.to eq(0) }
153
+ end
154
+
155
+ context "turns played in pre_flop and turns played in flop" do
156
+ let(:stage) { 'flop' }
157
+ let(:moves) do
158
+ [
159
+ { stage: 'pre_flop', player: players[0], amount: 1, move_type: 'raise' },
160
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
161
+ { stage: 'pre_flop', player: players[2], amount: 0, move_type: 'fold'},
162
+ { stage: 'pre_flop', player: players[0], amount: 3, move_type: 'check' },
163
+ { stage: 'flop', player: players[1], amount: 0, move_type: 'check' },
164
+ { stage: 'flop', player: players[0], amount: 0, move_type: 'fold' }
165
+ ]
166
+ end
167
+
168
+ subject { move_history_computations.turns_played_in_stage }
169
+
170
+ it { is_expected.to eq(2) }
171
+ end
172
+ end
173
+
174
+ describe "#player_in_turn" do
175
+ context "no turns played" do
176
+ let(:moves) { [] }
177
+
178
+ subject { move_history_computations.player_in_turn }
179
+
180
+ it { is_expected.to eq(player1) }
181
+ end
182
+
183
+ context "two turns played" do
184
+ let(:moves) do
185
+ [
186
+ { stage: 'pre_flop', player: players[0], amount: 2, move_type: 'raise' },
187
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
188
+ { stage: 'pre_flop', player: players[2], move_type: 'fold' }
189
+ ]
190
+ end
191
+
192
+ subject { move_history_computations.player_in_turn }
193
+
194
+ it { is_expected.to eq(player1) }
195
+ end
196
+
197
+ context "many turns played" do
198
+ let(:moves) do
199
+ [
200
+ { stage: 'pre_flop', player: players[0], amount: 2, move_type: 'raise' },
201
+ { stage: 'pre_flop', player: players[1], amount: 4, move_type: 'raise' },
202
+ { stage: 'pre_flop', player: players[2], move_type: 'fold' },
203
+ { stage: 'pre_flop', player: players[0], move_type: 'call' },
204
+ { stage: 'pre_flop', player: players[1], move_type: 'check' }
205
+ ]
206
+ end
207
+
208
+ subject { move_history_computations.player_in_turn }
209
+
210
+ it { is_expected.to eq(player1) }
211
+ end
212
+ end
213
+
214
+ describe "#every_player_has_checked?" do
215
+ context "every player has checked yet" do
216
+ let(:moves) do
217
+ [
218
+ { stage: 'pre_flop', player: players[0], amount: 10, move_type: 'raise' },
219
+ { stage: 'pre_flop', player: players[1], amount: nil, move_type: 'fold' },
220
+ { stage: 'pre_flop', player: players[2], amount: nil, move_type: 'check' },
221
+ { stage: 'pre_flop', player: players[0], amount: nil, move_type: 'check' },
222
+ ]
223
+ end
224
+
225
+ subject { move_history_computations.every_player_has_checked? }
226
+
227
+ it { is_expected.to be_truthy }
228
+ end
229
+
230
+ context "not every player has checked yet" do
231
+ let(:moves) do
232
+ [
233
+ { stage: 'pre_flop', player: players[0], amount: 10, move_type: 'raise' },
234
+ { stage: 'pre_flop', player: players[1], amount: nil, move_type: 'fold' },
235
+ { stage: 'pre_flop', player: players[2], amount: nil, move_type: 'check' }
236
+ ]
237
+ end
238
+
239
+ subject { move_history_computations.every_player_has_checked? }
240
+
241
+ it { is_expected.to be_falsey }
242
+ end
243
+ end
244
+
245
+ describe "#players_still_in_round" do
246
+ context "no players have folded" do
247
+ let(:moves) do
248
+ [
249
+ { stage: 'pre_flop', player: players[0], amount: 10, move_type: 'raise' },
250
+ { stage: 'pre_flop', player: players[1], amount: 10, move_type: 'raise' },
251
+ { stage: 'pre_flop', player: players[2], amount: 10, move_type: 'raise' }
252
+ ]
253
+ end
254
+
255
+ subject { move_history_computations.players_still_in_round }
256
+
257
+ it { is_expected.to eq([player1, player2, player3]) }
258
+ end
259
+
260
+ context "a player has folded" do
261
+ let(:moves) do
262
+ [
263
+ { stage: 'pre_flop', player: players[0], amount: 10, move_type: 'raise' },
264
+ { stage: 'pre_flop', player: players[1], amount: nil, move_type: 'fold' },
265
+ { stage: 'pre_flop', player: players[2], amount: nil, move_type: 'check' }
266
+ ]
267
+ end
268
+
269
+ subject { move_history_computations.players_still_in_round }
270
+
271
+ it { is_expected.to eq([player1, player3]) }
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyHoldem::Round::MoveHistory do
4
+ let(:player1) { RubyHoldem::Round::Player.new("Jack") }
5
+ let(:player2) { RubyHoldem::Round::Player.new("Joe") }
6
+ let(:player3) { RubyHoldem::Round::Player.new("Jil") }
7
+ let(:players) { [player1, player2, player3] }
8
+
9
+ let(:move_history) { RubyHoldem::Round::MoveHistory.new }
10
+
11
+ describe "#add_move" do
12
+ let(:move) { double }
13
+
14
+ subject! { move_history.add_move(move) }
15
+
16
+ it { expect(move_history.moves.last).to eq(move) }
17
+ end
18
+
19
+ describe "#last_move" do
20
+ let(:move) { double }
21
+
22
+ before do
23
+ move_history.add_move(move)
24
+ end
25
+
26
+ subject { move_history.last_move }
27
+
28
+ it { is_expected.to eq(move) }
29
+ end
30
+
31
+ describe "#turns_played" do
32
+ subject { move_history.turns_played }
33
+
34
+ it { is_expected.to eq(0) }
35
+ end
36
+
37
+ describe "#next_stage" do
38
+ subject! { move_history.next_stage }
39
+
40
+ it { expect(move_history.stage).to eq(RubyHoldem::Round::STAGES[1]) }
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe RubyHoldem::Round::MoveValidator do
5
+ describe "#validate" do
6
+ let(:round) { double(RubyHoldem::Round) }
7
+ let(:player) { double(RubyHoldem::Round::Player) }
8
+
9
+ context "with a move_type of raise" do
10
+ pending
11
+ end
12
+
13
+ context "with a move_type of call" do
14
+ pending
15
+ end
16
+
17
+ context "with a move_type of check" do
18
+ pending
19
+ end
20
+
21
+ context "with a move_type of fold" do
22
+ pending
23
+ end
24
+ end
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_holdem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Rolfe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-28 00:00:00.000000000 Z
11
+ date: 2016-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-poker
@@ -101,7 +101,6 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
- - ".rspec"
105
104
  - Gemfile
106
105
  - Gemfile.lock
107
106
  - LICENSE
@@ -114,11 +113,18 @@ files:
114
113
  - lib/ruby_holdem/errors.rb
115
114
  - lib/ruby_holdem/game.rb
116
115
  - lib/ruby_holdem/round.rb
117
- - lib/ruby_holdem/round_player.rb
116
+ - lib/ruby_holdem/round/move_factory.rb
117
+ - lib/ruby_holdem/round/move_history.rb
118
+ - lib/ruby_holdem/round/move_history_computations.rb
119
+ - lib/ruby_holdem/round/move_validator.rb
120
+ - lib/ruby_holdem/round/player.rb
118
121
  - lib/ruby_holdem/version.rb
119
122
  - ruby_holdem.gemspec
120
123
  - spec/dealer_spec.rb
121
- - spec/round_spec.rb
124
+ - spec/round/move_factory_spec.rb
125
+ - spec/round/move_history_computations_spec.rb
126
+ - spec/round/move_history_spec.rb
127
+ - spec/round/move_validator_spec.rb
122
128
  - spec/spec_helper.rb
123
129
  homepage: https://github.com/evanrolfe/ruby-holdem
124
130
  licenses:
@@ -146,5 +152,8 @@ specification_version: 4
146
152
  summary: A gem for playing texas-holdem poker
147
153
  test_files:
148
154
  - spec/dealer_spec.rb
149
- - spec/round_spec.rb
155
+ - spec/round/move_factory_spec.rb
156
+ - spec/round/move_history_computations_spec.rb
157
+ - spec/round/move_history_spec.rb
158
+ - spec/round/move_validator_spec.rb
150
159
  - spec/spec_helper.rb
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --require spec_helper
@@ -1,11 +0,0 @@
1
- module RubyHoldem
2
- class RoundPlayer < SimpleDelegator
3
- attr_accessor :hole_cards, :current_bet_amount
4
-
5
- def initialize(player)
6
- @hole_cards = []
7
- @current_bet_amount = 0
8
- super(player)
9
- end
10
- end
11
- end
data/spec/round_spec.rb DELETED
@@ -1,249 +0,0 @@
1
- require 'spec_helper'
2
- require 'ostruct'
3
-
4
- describe RubyHoldem::Round do
5
- let(:player1) { OpenStruct.new(name: "Player #1") }
6
- let(:player2) { OpenStruct.new(name: "Player #2") }
7
- let(:player3) { OpenStruct.new(name: "Player #3") }
8
- let(:players) { [player1, player2, player3] }
9
-
10
- let(:round) { RubyHoldem::Round.new(players, 1, 2) }
11
-
12
- describe '#initialize' do
13
- subject { round }
14
-
15
- # TODO: Convert all instances of "should" to "is_expected"
16
- its(:small_blinds) { should eq(1) }
17
- its(:big_blinds) { should eq(2) }
18
- its(:current_stage) { should eq('pre_flop') }
19
- its(:action_history) { should eq([]) }
20
- end
21
-
22
- describe '#next_stage' do
23
- before do
24
- allow(round).to receive(:action_history).and_return(action_history)
25
- end
26
-
27
- context 'on the pre_flop' do
28
- # TODO: Convert the stubbing of the action_history to a sequence of make_move calls in a
29
- # before block
30
- let(:action_history) do
31
- [
32
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
33
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' },
34
- { stage: 'pre_flop', player: round.players[2], amount: 1, move: 'call' },
35
- { stage: 'pre_flop', player: round.players[0], amount: 0, move: 'call' }
36
- ]
37
- end
38
-
39
- before do
40
- round.next_stage
41
- end
42
-
43
- it { expect(round.current_stage).to eq('flop') }
44
- end
45
- end
46
-
47
- describe '#ready_for_next_stage?' do
48
- before do
49
- allow(round).to receive(:action_history).and_return(action_history)
50
- end
51
-
52
- subject { round.ready_for_next_stage? }
53
-
54
- context 'on game start' do
55
- let(:action_history) { [] }
56
-
57
- it { should eq(false) }
58
- end
59
-
60
- context 'on the pre_flop' do
61
-
62
- context 'situation 1' do
63
- let(:action_history) do
64
- [
65
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
66
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' }
67
- ]
68
- end
69
- it { should eq(false) }
70
- end
71
-
72
- context 'situation 2' do
73
- let(:action_history) do
74
- [
75
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
76
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' },
77
- { stage: 'pre_flop', player: round.players[2], amount: 1, move: 'call' },
78
- { stage: 'pre_flop', player: round.players[0], amount: 0, move: 'call' }
79
- ]
80
- end
81
-
82
- it { should eq(true) }
83
- end
84
-
85
- end
86
-
87
- context 'on the flop' do
88
-
89
- context 'situation 1' do
90
- let(:action_history) do
91
- [
92
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
93
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' },
94
- { stage: 'pre_flop', player: round.players[2], amount: 1, move: 'call' },
95
- { stage: 'flop', player: round.players[0], amount: 1, move: 'bet' }
96
- ]
97
- end
98
-
99
- before do
100
- allow(round).to receive(:current_stage).and_return('flop')
101
- end
102
-
103
- it { should eq(false) }
104
- end
105
-
106
- context 'situation 2' do
107
- let(:action_history) do
108
- [
109
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
110
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' },
111
- { stage: 'pre_flop', player: round.players[2], amount: 1, move: 'call' },
112
- { stage: 'pre_flop', player: round.players[0], amount: 0, move: 'call' },
113
- { stage: 'flop', player: round.players[0], amount: 3, move: 'bet' },
114
- { stage: 'flop', player: round.players[2], amount: 3, move: 'call' },
115
- { stage: 'flop', player: round.players[0], amount: 0, move: 'call' }
116
- ]
117
- end
118
-
119
- before do
120
- allow(round).to receive(:current_stage).and_return('flop')
121
- end
122
-
123
- it { should eq(true) }
124
- end
125
-
126
- end
127
- end
128
-
129
- describe '#player_in_turn' do
130
- let(:action_history) do
131
- [
132
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
133
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' }
134
- ]
135
- end
136
-
137
- before do
138
- allow(round).to receive(:action_history).and_return(action_history)
139
- end
140
-
141
- subject { round.send(:player_in_turn) }
142
-
143
- it { should == round.players[2] }
144
- end
145
-
146
- describe '#players_still_in_round' do
147
- let(:action_history) do
148
- [
149
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
150
- { stage: 'pre_flop', player: round.players[1], amount: 0, move: 'fold' }
151
- ]
152
- end
153
-
154
- before do
155
- allow(round).to receive(:action_history).and_return(action_history)
156
- end
157
-
158
- subject { round.send(:players_still_in_round) }
159
-
160
- its([0]) { should eq(round.players[0]) }
161
- its([1]) { should eq(round.players[2]) }
162
- end
163
-
164
- describe '#highest_bet_placed' do
165
- before do
166
- round.make_move('call')
167
- round.make_move('call')
168
- round.make_move('bet', 4)
169
- end
170
-
171
- subject { round.send(:highest_bet_placed) }
172
-
173
- it { should eq(4) }
174
- end
175
-
176
- describe '#has_winner?' do
177
- subject { round.has_winner? }
178
-
179
- context 'round won after folds' do
180
- let(:action_history) do
181
- [
182
- { stage: 'pre_flop', player: round.players[0], amount: 1, move: 'bet' },
183
- { stage: 'pre_flop', player: round.players[1], amount: 4, move: 'bet' },
184
- { stage: 'pre_flop', player: round.players[2], amount: 0, move: 'fold'},
185
- { stage: 'pre_flop', player: round.players[0], amount: 3, move: 'call' },
186
- { stage: 'flop', player: round.players[1], amount: 0, move: 'call' },
187
- { stage: 'flop', player: round.players[0], amount: 0, move: 'fold' }
188
- ]
189
- end
190
-
191
- before do
192
- allow(round).to receive(:action_history).and_return(action_history)
193
- end
194
-
195
- it { should eq(true) }
196
- end
197
-
198
- context 'showdown between remaining players' do
199
- before do
200
- allow(round).to receive(:current_stage).and_return('show_down')
201
- end
202
-
203
- it { should eq(true) }
204
- end
205
- end
206
-
207
- describe '#winner' do
208
-
209
- end
210
-
211
- describe '#apply_bet' do
212
- context 'at the start of the round' do
213
- before do
214
- round.send(:apply_bet, 1)
215
- end
216
-
217
- subject { round }
218
-
219
- its(:action_history) { should eq([{ player: round.players[0], stage: 'pre_flop', move: 'bet', amount: 1 }]) }
220
- its(:pot_amount) { should eq(1) }
221
- end
222
- end
223
-
224
- describe '#apply_call' do
225
- context 'at the start of the round' do
226
- before do
227
- round.send(:apply_call)
228
- end
229
-
230
- subject { round }
231
-
232
- its(:action_history) { should eq([{ player: round.players[0], stage: 'pre_flop', move: 'call', amount: 0 }]) }
233
- its(:pot_amount) { should eq(0) }
234
- end
235
- end
236
-
237
- describe '#apply_fold' do
238
- context 'at the start of the round' do
239
- before do
240
- round.send(:apply_fold)
241
- end
242
-
243
- subject { round }
244
-
245
- its(:action_history) { should eq([{ player: round.players[0], stage: 'pre_flop', move: 'fold', amount: 0 }]) }
246
- its(:pot_amount) { should eq(0) }
247
- end
248
- end
249
- end