ruby_holdem 0.0.1 → 0.0.2

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.
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