liars_dice 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +4 -0
- data/lib/liars_dice/bid.rb +42 -0
- data/lib/liars_dice/command_line_watcher.rb +13 -0
- data/lib/liars_dice/engine.rb +194 -0
- data/lib/liars_dice/event.rb +57 -0
- data/lib/liars_dice/human_bot.rb +45 -0
- data/lib/liars_dice/random_bot.rb +42 -0
- data/lib/liars_dice/seat.rb +25 -0
- data/lib/liars_dice/watcher.rb +62 -0
- data/lib/liars_dice.rb +8 -0
- data/spec/bid_spec.rb +28 -0
- data/spec/engine_spec.rb +577 -0
- data/spec/seat_spec.rb +48 -0
- data/spec/spec_helper.rb +11 -0
- metadata +65 -0
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class Bid
|
3
|
+
attr_accessor :total, :face_value
|
4
|
+
|
5
|
+
def initialize(total, face_value)
|
6
|
+
self.total = total
|
7
|
+
self.face_value = face_value
|
8
|
+
end
|
9
|
+
|
10
|
+
def bs_called?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#{total} #{face_value}#{"s" if total > 1}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
if face_value < 1 || face_value > 6
|
20
|
+
# Can't bid a face_value that doesn't exist
|
21
|
+
return false
|
22
|
+
elsif total < 1
|
23
|
+
# Have to bid a positive total
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class BS < Bid
|
31
|
+
def initialize
|
32
|
+
end
|
33
|
+
|
34
|
+
def bs_called?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"BS"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class CommandLineWatcher
|
3
|
+
include Watcher
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
append_after_dice_rolled lambda{|dice| puts "Dice Rolled: #{dice.inspect.to_s}" }
|
7
|
+
append_after_bid lambda{|seat, bid| puts "Seat #{seat} bids #{bid}" }
|
8
|
+
append_after_bs lambda{|seat| puts "Seat #{seat} calls BS" }
|
9
|
+
append_after_game lambda{|winner| puts "Game over. Seat #{winner} wins." }
|
10
|
+
append_after_round lambda{|loser| puts "Seat #{loser} loses a die" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class Engine
|
3
|
+
include Watcher
|
4
|
+
attr_accessor :seats, :starting_seat, :bids, :watcher
|
5
|
+
attr_reader :loser, :seat_index
|
6
|
+
|
7
|
+
def initialize(player_classes, dice_per_player, watcher=nil)
|
8
|
+
self.seats = []
|
9
|
+
player_classes.shuffle.each_with_index do |klass, i|
|
10
|
+
player = klass.new(i, player_classes.count, dice_per_player)
|
11
|
+
self.seats << Seat.new(i, player, dice_per_player)
|
12
|
+
end
|
13
|
+
self.seat_index = 0
|
14
|
+
self.watcher = watcher || self
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
until winner?
|
19
|
+
roll_dice
|
20
|
+
run_round
|
21
|
+
end
|
22
|
+
notify_winner
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_bid(seat)
|
26
|
+
bid = seat.player.bid
|
27
|
+
unless valid_bid?(bid)
|
28
|
+
raise StandardError.new("Invalid Bid")
|
29
|
+
end
|
30
|
+
bid
|
31
|
+
end
|
32
|
+
|
33
|
+
def next_seat
|
34
|
+
# If no seats are alive, we'd loop forever
|
35
|
+
return nil if alive_seats.empty?
|
36
|
+
|
37
|
+
seat = seats[seat_index]
|
38
|
+
self.seat_index += 1
|
39
|
+
|
40
|
+
# If the seat at seat_index is alive, return it
|
41
|
+
# Otherwise, we've already updated seat_index (and wrapped it, if necessary)
|
42
|
+
# so just call next_seat again
|
43
|
+
seat.alive? ? seat : next_seat
|
44
|
+
end
|
45
|
+
|
46
|
+
def total_dice_with_face_value(face_value)
|
47
|
+
seats.map{|seat| seat.dice.count(face_value) }.reduce(0, :+)
|
48
|
+
end
|
49
|
+
|
50
|
+
def bid_is_correct?(bid, use_wilds)
|
51
|
+
total = total_dice_with_face_value(bid.face_value)
|
52
|
+
total += total_dice_with_face_value(1) if use_wilds
|
53
|
+
total >= bid.total
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_round
|
57
|
+
self.bids = []
|
58
|
+
|
59
|
+
previous_seat = nil
|
60
|
+
aces_wild = true
|
61
|
+
while true
|
62
|
+
seat = next_seat
|
63
|
+
bid = get_bid(seat)
|
64
|
+
aces_wild = false if bid.face_value == 1
|
65
|
+
|
66
|
+
if bid.bs_called?
|
67
|
+
notify_bs(seat)
|
68
|
+
self.loser = bid_is_correct?(previous_bid, aces_wild) ? seat : previous_seat
|
69
|
+
notify_loser(loser)
|
70
|
+
loser.lose_die
|
71
|
+
break
|
72
|
+
end
|
73
|
+
|
74
|
+
self.bids << bid
|
75
|
+
notify_bid(seat, bid)
|
76
|
+
previous_seat = seat
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def roll_dice
|
81
|
+
die = (1..6).to_a
|
82
|
+
alive_seats.each do |seat|
|
83
|
+
dice = []
|
84
|
+
seat.dice_left.times do
|
85
|
+
dice << die.sample
|
86
|
+
end
|
87
|
+
|
88
|
+
seat.dice = dice
|
89
|
+
end
|
90
|
+
notify_roll
|
91
|
+
end
|
92
|
+
|
93
|
+
# ===========================================
|
94
|
+
# ==== Notification Events ====
|
95
|
+
# ===========================================
|
96
|
+
def notify_bid(seat, bid)
|
97
|
+
event = BidMadeEvent.new(seat.number, bid)
|
98
|
+
notify_players(event)
|
99
|
+
notify_watcher(event)
|
100
|
+
end
|
101
|
+
|
102
|
+
def notify_bs(seat)
|
103
|
+
event = BSCalledEvent.new(seat.number, previous_bid)
|
104
|
+
notify_players(event)
|
105
|
+
notify_watcher(event)
|
106
|
+
end
|
107
|
+
|
108
|
+
def notify_loser(seat)
|
109
|
+
dice = seats.map(&:dice)
|
110
|
+
event = LoserEvent.new(seat.number, dice)
|
111
|
+
notify_players(event)
|
112
|
+
notify_watcher(event)
|
113
|
+
end
|
114
|
+
|
115
|
+
def notify_winner
|
116
|
+
event = WinnerEvent.new(winner.number)
|
117
|
+
notify_players(event)
|
118
|
+
notify_watcher(event)
|
119
|
+
end
|
120
|
+
|
121
|
+
def notify_roll
|
122
|
+
dice = seats.map(&:dice)
|
123
|
+
event = DiceRolledEvent.new(dice)
|
124
|
+
notify_watcher(event)
|
125
|
+
end
|
126
|
+
|
127
|
+
def notify_players(event)
|
128
|
+
seats.each{|s| s.player.handle_event(event) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def notify_watcher(event)
|
132
|
+
watcher.handle_event(event)
|
133
|
+
end
|
134
|
+
|
135
|
+
# ===========================================
|
136
|
+
# ==== Validation Methods ====
|
137
|
+
# ===========================================
|
138
|
+
def valid_bs?(bid)
|
139
|
+
# Cannot bid BS if there isn't a previous bid
|
140
|
+
!!previous_bid
|
141
|
+
end
|
142
|
+
|
143
|
+
def valid_bid?(bid)
|
144
|
+
return false unless bid
|
145
|
+
|
146
|
+
if bid.bs_called?
|
147
|
+
return valid_bs?(bid)
|
148
|
+
end
|
149
|
+
|
150
|
+
if bid.face_value < 1 || bid.face_value > 6
|
151
|
+
# Can't bid a face_value that doesn't exist
|
152
|
+
return false
|
153
|
+
elsif bid.total < 1
|
154
|
+
# Have to bid a positive total
|
155
|
+
return false
|
156
|
+
elsif previous_bid && bid.total < previous_bid.total
|
157
|
+
# The total must be monotonically increasing
|
158
|
+
return false
|
159
|
+
elsif previous_bid && bid.total == previous_bid.total && bid.face_value <= previous_bid.face_value
|
160
|
+
# If the total does not increase, the face_value must
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
def previous_bid
|
168
|
+
bids[-1]
|
169
|
+
end
|
170
|
+
|
171
|
+
def alive_seats
|
172
|
+
seats.select(&:alive?)
|
173
|
+
end
|
174
|
+
|
175
|
+
def winner?
|
176
|
+
alive_seats.count == 1
|
177
|
+
end
|
178
|
+
|
179
|
+
def winner
|
180
|
+
return nil unless winner?
|
181
|
+
alive_seats.first
|
182
|
+
end
|
183
|
+
|
184
|
+
def loser=(seat)
|
185
|
+
@loser = seat
|
186
|
+
self.seat_index = seat.number + 1
|
187
|
+
end
|
188
|
+
|
189
|
+
def seat_index=(index)
|
190
|
+
@seat_index = index
|
191
|
+
@seat_index = 0 if @seat_index == seats.count
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class Event
|
3
|
+
attr_accessor :message
|
4
|
+
|
5
|
+
def initialize(message)
|
6
|
+
self.message = message
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class BidMadeEvent < Event
|
11
|
+
attr_accessor :seat_number, :bid
|
12
|
+
|
13
|
+
def initialize(seat_number, bid)
|
14
|
+
self.seat_number = seat_number
|
15
|
+
self.bid = bid
|
16
|
+
super("Seat #{seat_number} bid #{bid.to_s}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class BSCalledEvent < Event
|
21
|
+
attr_accessor :seat_number, :previous_bid
|
22
|
+
|
23
|
+
def initialize(seat_number, previous_bid)
|
24
|
+
self.seat_number = seat_number
|
25
|
+
self.previous_bid = previous_bid
|
26
|
+
super("Seat #{seat_number} called BS")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DiceRolledEvent < Event
|
31
|
+
attr_accessor :dice
|
32
|
+
|
33
|
+
def initialize(dice)
|
34
|
+
self.dice = dice
|
35
|
+
super("Dice rolled")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class LoserEvent < Event
|
40
|
+
attr_accessor :seat_number, :dice
|
41
|
+
|
42
|
+
def initialize(seat_number, dice)
|
43
|
+
self.seat_number = seat_number
|
44
|
+
self.dice = dice
|
45
|
+
super("Seat #{seat_number} lost a die")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class WinnerEvent < Event
|
50
|
+
attr_accessor :seat_number
|
51
|
+
|
52
|
+
def initialize(seat_number)
|
53
|
+
self.seat_number = seat_number
|
54
|
+
super("Game is over. Seat #{seat_number} is the winner.")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class HumanBot
|
3
|
+
attr_accessor :prev_bid, :dice
|
4
|
+
|
5
|
+
def initialize(seat_number, number_of_players, number_of_dice)
|
6
|
+
puts "You're playing as HumanBot in seat #{seat_number}"
|
7
|
+
puts "When asked for a bid, either enter <TOTAL> <FACE_VALUE> or BS"
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def handle_event(event)
|
12
|
+
if event.is_a? LiarsDice::BidMadeEvent
|
13
|
+
self.prev_bid = event.bid
|
14
|
+
elsif event.is_a? LiarsDice::LoserEvent
|
15
|
+
self.prev_bid = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def dice=(dice)
|
20
|
+
@dice = dice
|
21
|
+
puts "Your dice are #{dice.inspect.to_s}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def bid
|
25
|
+
if prev_bid
|
26
|
+
puts "Previous bid was #{prev_bid}"
|
27
|
+
else
|
28
|
+
puts "You're bidding first"
|
29
|
+
end
|
30
|
+
|
31
|
+
print "What do you bid? "
|
32
|
+
bid_string = gets.chomp
|
33
|
+
if bid_string.downcase == "bs"
|
34
|
+
return BS.new
|
35
|
+
end
|
36
|
+
parts = bid_string.split(" ").map(&:to_i)
|
37
|
+
if parts.length == 2
|
38
|
+
ret = Bid.new(*parts)
|
39
|
+
return ret if ret.valid?
|
40
|
+
end
|
41
|
+
print "Invalid bid"
|
42
|
+
return bid
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class RandomBot
|
3
|
+
attr_accessor :prev_bid
|
4
|
+
|
5
|
+
def initialize(seat_number, number_of_players, number_of_dice); end
|
6
|
+
|
7
|
+
|
8
|
+
def handle_event(event)
|
9
|
+
if event.is_a? LiarsDice::BidMadeEvent
|
10
|
+
self.prev_bid = event.bid
|
11
|
+
elsif event.is_a? LiarsDice::LoserEvent
|
12
|
+
self.prev_bid = nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def dice=(dice); end
|
17
|
+
|
18
|
+
def bid
|
19
|
+
if prev_bid
|
20
|
+
choice = (0...10).to_a.sample
|
21
|
+
|
22
|
+
if choice == 0
|
23
|
+
# Call BS 10% of the time
|
24
|
+
LiarsDice::BS.new
|
25
|
+
elsif choice <= 6
|
26
|
+
# Up the total 50% of the time
|
27
|
+
LiarsDice::Bid.new(prev_bid.total + 1, prev_bid.face_value)
|
28
|
+
else
|
29
|
+
# Up the number 40% of the time
|
30
|
+
r = LiarsDice::Bid.new(prev_bid.total, prev_bid.face_value + 1)
|
31
|
+
if r.face_value > 6
|
32
|
+
r.total += 1
|
33
|
+
r.face_value = 2
|
34
|
+
end
|
35
|
+
r
|
36
|
+
end
|
37
|
+
else
|
38
|
+
LiarsDice::Bid.new(1, 2)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
class Seat
|
3
|
+
attr_accessor :number, :player
|
4
|
+
attr_reader :dice_left, :dice
|
5
|
+
|
6
|
+
def initialize(number, player, starting_dice)
|
7
|
+
self.number = number
|
8
|
+
@dice_left = starting_dice
|
9
|
+
self.player = player
|
10
|
+
end
|
11
|
+
|
12
|
+
def alive?
|
13
|
+
dice_left > 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def dice=(value)
|
17
|
+
@dice = value
|
18
|
+
player.dice = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def lose_die
|
22
|
+
@dice_left -= 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module LiarsDice
|
2
|
+
module Watcher
|
3
|
+
attr_reader :after_roll, :after_bid, :after_round, :after_bs, :after_game, :after_dice_rolled
|
4
|
+
|
5
|
+
def append_after_bid(callback)
|
6
|
+
append_callback(:after_bid, callback)
|
7
|
+
end
|
8
|
+
|
9
|
+
def append_after_round(callback)
|
10
|
+
append_callback(:after_round, callback)
|
11
|
+
end
|
12
|
+
|
13
|
+
def append_after_bs(callback)
|
14
|
+
append_callback(:after_bs, callback)
|
15
|
+
end
|
16
|
+
|
17
|
+
def append_after_game(callback)
|
18
|
+
append_callback(:after_game, callback)
|
19
|
+
end
|
20
|
+
|
21
|
+
def append_after_dice_rolled(callback)
|
22
|
+
append_callback(:after_dice_rolled, callback)
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_event(event)
|
26
|
+
if event.is_a? BidMadeEvent
|
27
|
+
fire(:after_bid, event.seat_number, event.bid)
|
28
|
+
elsif event.is_a? BSCalledEvent
|
29
|
+
fire(:after_bs, event.seat_number)
|
30
|
+
elsif event.is_a? LoserEvent
|
31
|
+
fire(:after_round, event.seat_number)
|
32
|
+
elsif event.is_a? WinnerEvent
|
33
|
+
fire(:after_game, event.seat_number)
|
34
|
+
elsif event.is_a? DiceRolledEvent
|
35
|
+
fire(:after_dice_rolled, event.dice)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def allowed_callbacks
|
41
|
+
[:after_bid, :after_bs, :after_dice_rolled, :after_game, :after_round]
|
42
|
+
end
|
43
|
+
|
44
|
+
def append_callback(callback_name, callback)
|
45
|
+
raise ArgumentError.new("Callback does not respond to call") unless callback.respond_to? :call
|
46
|
+
raise ArgumentError.new("Unsupported callback #{callback_name}") unless allowed_callbacks.include? callback_name
|
47
|
+
watcher_callbacks[callback_name] << callback
|
48
|
+
end
|
49
|
+
|
50
|
+
def fire(callback_name, *args)
|
51
|
+
raise ArgumentError.new("Unsupported callback #{callback_name}") unless allowed_callbacks.include? callback_name
|
52
|
+
watcher_callbacks[callback_name].each do |callback|
|
53
|
+
callback.call(*args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def watcher_callbacks
|
58
|
+
# Create a hash where any missing key returns an empty array
|
59
|
+
@watcher_callbacks ||= Hash.new {|hash, key| hash[key] = [] }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/liars_dice.rb
ADDED
data/spec/bid_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Bid" do
|
4
|
+
describe "#valid?" do
|
5
|
+
it "returns false if total is negative" do
|
6
|
+
bid = Bid.new(-1, 5)
|
7
|
+
bid.should_not be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns false if total is negative" do
|
11
|
+
bid = Bid.new(0, 5)
|
12
|
+
bid.should_not be_valid
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns false for invalid die face_values" do
|
16
|
+
bid = Bid.new(5, 0)
|
17
|
+
bid.should_not be_valid
|
18
|
+
|
19
|
+
bid = Bid.new(5, 10)
|
20
|
+
bid.should_not be_valid
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns true for valid bids" do
|
24
|
+
bid = Bid.new(5, 3)
|
25
|
+
bid.should be_valid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,577 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Engine" do
|
4
|
+
let(:engine) { Engine.new([], 5) }
|
5
|
+
let(:seat) { Seat.new(0, nil, 5) }
|
6
|
+
let(:bid) { Bid.new(1, 1) }
|
7
|
+
|
8
|
+
describe "run_round" do
|
9
|
+
let(:bid_one) { Bid.new(1, 1) }
|
10
|
+
let(:bid_two) { Bid.new(1, 2) }
|
11
|
+
let(:bs) { BS.new }
|
12
|
+
|
13
|
+
before do
|
14
|
+
engine.stub(:next_seat).and_return(seat)
|
15
|
+
engine.stub(:get_bid).and_return(bid_one, bid_two, bs)
|
16
|
+
engine.stub(:bid_is_correct).and_return(true)
|
17
|
+
engine.stub(:notify_loser)
|
18
|
+
seat.stub(:lose_die)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "properly sets #previous_bid" do
|
22
|
+
engine.run_round
|
23
|
+
engine.previous_bid.should == bid_two
|
24
|
+
end
|
25
|
+
|
26
|
+
context "tracking whether aces are wild" do
|
27
|
+
it "doesn't count aces wild after aces are bet" do
|
28
|
+
engine.should_receive(:bid_is_correct?).with(bid_two, false).and_return(true)
|
29
|
+
engine.run_round
|
30
|
+
end
|
31
|
+
|
32
|
+
it "counts aces wild if aces aren't bet" do
|
33
|
+
engine.stub(:get_bid).and_return(bid_two, bs)
|
34
|
+
engine.should_receive(:bid_is_correct?).with(bid_two, true).and_return(true)
|
35
|
+
engine.run_round
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "bid notification" do
|
40
|
+
it "notifies of each bid" do
|
41
|
+
engine.should_receive(:notify_bid).twice
|
42
|
+
engine.run_round
|
43
|
+
end
|
44
|
+
|
45
|
+
it "notifies of the seat and the bid" do
|
46
|
+
engine.stub(:get_bid).and_return(bid_two, bs)
|
47
|
+
engine.should_receive(:notify_bid).with(seat, bid_two)
|
48
|
+
engine.run_round
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "notifies of a bs" do
|
53
|
+
engine.should_receive(:notify_bs).with(seat)
|
54
|
+
engine.run_round
|
55
|
+
end
|
56
|
+
|
57
|
+
it "notifies of a loser" do
|
58
|
+
engine.should_receive(:notify_loser).with(seat)
|
59
|
+
engine.run_round
|
60
|
+
end
|
61
|
+
|
62
|
+
context "picking a loser" do
|
63
|
+
let (:seat1) { Seat.new(1, nil, 5) }
|
64
|
+
let (:seat2) { Seat.new(2, nil, 5) }
|
65
|
+
|
66
|
+
before do
|
67
|
+
engine.stub(:next_seat).and_return(seat1, seat2)
|
68
|
+
engine.stub(:get_bid).and_return(bid_two, bs)
|
69
|
+
engine.stub_chain(:loser, :lose_die)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "picks the bidder if the bid was incorrect" do
|
73
|
+
engine.stub(:bid_is_correct?).and_return(false)
|
74
|
+
engine.should_receive(:loser=).with(seat1)
|
75
|
+
engine.run_round
|
76
|
+
end
|
77
|
+
|
78
|
+
it "picks the caller if the bid was correct" do
|
79
|
+
engine.stub(:bid_is_correct?).and_return(true)
|
80
|
+
engine.should_receive(:loser=).with(seat2)
|
81
|
+
engine.run_round
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "removes a dice from the loser" do
|
86
|
+
seat1 = Seat.new(1, nil, 5)
|
87
|
+
seat2 = Seat.new(2, nil, 5)
|
88
|
+
|
89
|
+
engine.stub(:next_seat).and_return(seat1, seat2)
|
90
|
+
engine.stub(:get_bid).and_return(bid_two, bs)
|
91
|
+
engine.stub(:bid_is_correct?).and_return(false)
|
92
|
+
seat1.should_receive(:lose_die)
|
93
|
+
engine.run_round
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "run" do
|
98
|
+
before do
|
99
|
+
engine.stub(:roll_dice)
|
100
|
+
engine.stub(:run_round)
|
101
|
+
engine.stub(:notify_winner)
|
102
|
+
engine.stub(:winner?).and_return(false, true)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "goes until there's a winner" do
|
106
|
+
engine.should_receive(:winner?).exactly(4).times.and_return(false, false, false, true)
|
107
|
+
engine.run
|
108
|
+
end
|
109
|
+
|
110
|
+
it "rolls the dice" do
|
111
|
+
engine.should_receive(:roll_dice)
|
112
|
+
engine.run
|
113
|
+
end
|
114
|
+
|
115
|
+
it "runs a round" do
|
116
|
+
engine.should_receive(:run_round)
|
117
|
+
engine.run
|
118
|
+
end
|
119
|
+
|
120
|
+
it "notifies of a winner" do
|
121
|
+
engine.should_receive(:notify_winner)
|
122
|
+
engine.run
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "correctly advances seats after a round" do
|
127
|
+
bs = BS.new
|
128
|
+
seat0 = Seat.new(0, nil, 5)
|
129
|
+
seat1 = Seat.new(1, nil, 5)
|
130
|
+
seat2 = Seat.new(2, nil, 5)
|
131
|
+
seat3 = Seat.new(3, nil, 5)
|
132
|
+
seat4 = Seat.new(4, nil, 5)
|
133
|
+
seats = [seat0, seat1, seat2, seat3, seat4]
|
134
|
+
|
135
|
+
engine.stub(:winner?).and_return(false, false, false, true)
|
136
|
+
engine.stub(:roll_dice)
|
137
|
+
engine.stub(:notify_bid)
|
138
|
+
engine.stub(:notify_bs)
|
139
|
+
engine.stub(:notify_loser)
|
140
|
+
engine.stub(:notify_winner)
|
141
|
+
engine.stub(:bid_is_correct?).and_return(true, false, true)
|
142
|
+
|
143
|
+
engine.seat_index = 0
|
144
|
+
engine.stub(:seats).and_return(seats)
|
145
|
+
seats.each{|s| s.stub(:lose_die) }
|
146
|
+
|
147
|
+
# We're set up to run 3 rounds:
|
148
|
+
# R1: Seat0 bids, Seat1 calls, Seat1 loses
|
149
|
+
# R2: Seat2 bids, Seat3 calls, Seat2 loses
|
150
|
+
# R3: Seat3 bids, Seat4 calls, Seat4 loses
|
151
|
+
engine.should_receive(:get_bid).with(seat0).ordered.and_return(bid)
|
152
|
+
engine.should_receive(:get_bid).with(seat1).ordered.and_return(bs)
|
153
|
+
engine.should_receive(:get_bid).with(seat2).ordered.and_return(bid)
|
154
|
+
engine.should_receive(:get_bid).with(seat3).ordered.and_return(bs)
|
155
|
+
engine.should_receive(:get_bid).with(seat3).ordered.and_return(bid)
|
156
|
+
engine.should_receive(:get_bid).with(seat4).ordered.and_return(bs)
|
157
|
+
|
158
|
+
engine.run
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "#get_bid" do
|
162
|
+
before do
|
163
|
+
seat.stub_chain(:player, :bid).and_return("bid")
|
164
|
+
end
|
165
|
+
|
166
|
+
it "gets a bid from the seat's user" do
|
167
|
+
engine.stub(:valid_bid?).and_return(true)
|
168
|
+
engine.get_bid(seat).should == "bid"
|
169
|
+
end
|
170
|
+
|
171
|
+
it "raises an error if given an invalid bid" do
|
172
|
+
engine.stub(:valid_bid?).and_return(false)
|
173
|
+
expect { engine.get_bid(seat) }.to raise_error
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "winner?" do
|
178
|
+
let(:seat0) { OpenStruct.new(:alive? => true) }
|
179
|
+
let(:seat1) { OpenStruct.new(:alive? => true) }
|
180
|
+
let(:seat2) { OpenStruct.new(:alive? => false) }
|
181
|
+
let(:seat3) { OpenStruct.new(:alive? => false) }
|
182
|
+
|
183
|
+
it "returns true if there's one alive seat left" do
|
184
|
+
engine.stub(:seats).and_return([seat0, seat2, seat3])
|
185
|
+
engine.winner?.should be_true
|
186
|
+
end
|
187
|
+
|
188
|
+
it "returns false if there's more than one alive seat left" do
|
189
|
+
engine.stub(:seats).and_return([seat0, seat1, seat2, seat3])
|
190
|
+
engine.winner?.should be_false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "winner" do
|
195
|
+
let(:seat0) { OpenStruct.new(:alive? => true) }
|
196
|
+
let(:seat1) { OpenStruct.new(:alive? => true) }
|
197
|
+
let(:seat2) { OpenStruct.new(:alive? => false) }
|
198
|
+
let(:seat3) { OpenStruct.new(:alive? => false) }
|
199
|
+
|
200
|
+
it "returns nil if there isn't a winner" do
|
201
|
+
engine.stub(:seats).and_return([seat0, seat1, seat2, seat3])
|
202
|
+
engine.winner.should == nil
|
203
|
+
end
|
204
|
+
|
205
|
+
it "returns the sole alive seat" do
|
206
|
+
engine.stub(:seats).and_return([seat0, seat2, seat3])
|
207
|
+
engine.winner.should == seat0
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe "#total_dice_with_face_value" do
|
212
|
+
let(:seat0) { OpenStruct.new(dice: [2, 2, 3]) }
|
213
|
+
let(:seat1) { OpenStruct.new(dice: [4, 2, 3]) }
|
214
|
+
let(:seat2) { OpenStruct.new(dice: [2, 4, 5]) }
|
215
|
+
let(:seat3) { OpenStruct.new(dice: []) }
|
216
|
+
|
217
|
+
before do
|
218
|
+
engine.stub(:seats).and_return([seat0, seat1, seat2, seat3])
|
219
|
+
end
|
220
|
+
|
221
|
+
it "returns the correct counts" do
|
222
|
+
engine.total_dice_with_face_value(1).should == 0
|
223
|
+
engine.total_dice_with_face_value(2).should == 4
|
224
|
+
engine.total_dice_with_face_value(3).should == 2
|
225
|
+
engine.total_dice_with_face_value(4).should == 2
|
226
|
+
engine.total_dice_with_face_value(5).should == 1
|
227
|
+
engine.total_dice_with_face_value(6).should == 0
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "#bid_is_correct?" do
|
232
|
+
let(:bid) { Bid.new(3, 3) }
|
233
|
+
|
234
|
+
before do
|
235
|
+
engine.stub(:total_dice_with_face_value).with(1).and_return(1)
|
236
|
+
end
|
237
|
+
|
238
|
+
context "with wilds" do
|
239
|
+
it "returns false when none of the face_values were rolled" do
|
240
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(0)
|
241
|
+
engine.bid_is_correct?(bid, true).should == false
|
242
|
+
end
|
243
|
+
|
244
|
+
it "returns false when less than total of the face_values were rolled" do
|
245
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(1)
|
246
|
+
engine.bid_is_correct?(bid, true).should == false
|
247
|
+
end
|
248
|
+
|
249
|
+
it "returns true when exactly total of the face_values were rolled" do
|
250
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(2)
|
251
|
+
engine.bid_is_correct?(bid, true).should == true
|
252
|
+
end
|
253
|
+
|
254
|
+
it "returns true when more than total of the face_values were rolled" do
|
255
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(6)
|
256
|
+
engine.bid_is_correct?(bid, true).should == true
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
context "without wilds" do
|
261
|
+
it "returns false when none of the face_values were rolled" do
|
262
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(0)
|
263
|
+
engine.bid_is_correct?(bid, false).should == false
|
264
|
+
end
|
265
|
+
|
266
|
+
it "returns false when less than total of the face_values were rolled" do
|
267
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(1)
|
268
|
+
engine.bid_is_correct?(bid, false).should == false
|
269
|
+
end
|
270
|
+
|
271
|
+
it "returns true when exactly total of the face_values were rolled" do
|
272
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(3)
|
273
|
+
engine.bid_is_correct?(bid, false).should == true
|
274
|
+
end
|
275
|
+
|
276
|
+
it "returns true when more than total of the face_values were rolled" do
|
277
|
+
engine.stub(:total_dice_with_face_value).with(3).and_return(6)
|
278
|
+
engine.bid_is_correct?(bid, false).should == true
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#next_seat" do
|
284
|
+
let(:seat1) { Seat.new(0, nil, 1) }
|
285
|
+
let(:seat2) { Seat.new(0, nil, 1) }
|
286
|
+
let(:seat3) { Seat.new(0, nil, 1) }
|
287
|
+
|
288
|
+
it "only returns seats that are alive" do
|
289
|
+
seat1.stub(:alive?).and_return(true)
|
290
|
+
seat2.stub(:alive?).and_return(false)
|
291
|
+
seat3.stub(:alive?).and_return(true)
|
292
|
+
|
293
|
+
engine.stub(:seats).and_return([seat1, seat2, seat3])
|
294
|
+
returned_seats = []
|
295
|
+
3.times { returned_seats << engine.next_seat }
|
296
|
+
returned_seats.should include(seat1)
|
297
|
+
returned_seats.should_not include(seat2)
|
298
|
+
returned_seats.should include(seat3)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "wraps around if necessary" do
|
302
|
+
seat1.stub(:alive?).and_return(true)
|
303
|
+
seat2.stub(:alive?).and_return(false)
|
304
|
+
seat3.stub(:alive?).and_return(true)
|
305
|
+
|
306
|
+
engine.stub(:seats).and_return([seat1, seat2, seat3])
|
307
|
+
returned_seats = []
|
308
|
+
3.times { returned_seats << engine.next_seat }
|
309
|
+
returned_seats.should == [seat1, seat3, seat1]
|
310
|
+
end
|
311
|
+
|
312
|
+
it "returns seats in order" do
|
313
|
+
seat1.stub(:alive?).and_return(true)
|
314
|
+
seat2.stub(:alive?).and_return(true)
|
315
|
+
seat3.stub(:alive?).and_return(true)
|
316
|
+
|
317
|
+
engine.stub(:seats).and_return([seat1, seat2, seat3])
|
318
|
+
returned_seats = []
|
319
|
+
3.times { returned_seats << engine.next_seat }
|
320
|
+
returned_seats.should == [seat1, seat2, seat3]
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe "#roll_dice" do
|
325
|
+
context "randomness" do
|
326
|
+
before do
|
327
|
+
# Roll the dice a bunch of times and capture the result in a hash
|
328
|
+
# keyed by die face_value
|
329
|
+
engine.stub(:alive_seats).and_return([seat])
|
330
|
+
seat.stub(:dice_left).and_return(6000)
|
331
|
+
rolled_dice = []
|
332
|
+
seat.stub(:dice=) { |val| rolled_dice = val }
|
333
|
+
engine.roll_dice
|
334
|
+
@histogram = Hash.new(0)
|
335
|
+
rolled_dice.each{|die| @histogram[die] += 1 }
|
336
|
+
end
|
337
|
+
|
338
|
+
it "chooses valid face_values" do
|
339
|
+
@histogram.keys.sort.should == [1,2,3,4,5,6]
|
340
|
+
end
|
341
|
+
|
342
|
+
it "randomly distributes face_values" do
|
343
|
+
6.times do |i|
|
344
|
+
@histogram[i+1].should > 800
|
345
|
+
@histogram[i+1].should < 1200
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
it "only rolls dice for seats that are alive" do
|
351
|
+
s1 = Seat.new(0, {}, 5)
|
352
|
+
s1.stub(:alive?).and_return(false)
|
353
|
+
s2 = Seat.new(1, {}, 5)
|
354
|
+
s2.stub(:alive?).and_return(true)
|
355
|
+
engine.stub(:seats).and_return([s1, s2])
|
356
|
+
s1.should_not_receive(:dice=)
|
357
|
+
s2.should_receive(:dice=)
|
358
|
+
engine.roll_dice
|
359
|
+
end
|
360
|
+
|
361
|
+
it "rolls the correct number of dice for each seat" do
|
362
|
+
s1 = Seat.new(0, {}, 5)
|
363
|
+
s1.stub(:dice_left).and_return(3)
|
364
|
+
s1.stub(:dice=) { |val| val.count.should == 3 }
|
365
|
+
s2 = Seat.new(1, {}, 5)
|
366
|
+
s2.stub(:dice_left).and_return(5)
|
367
|
+
s2.stub(:dice=) { |val| val.count.should == 5 }
|
368
|
+
|
369
|
+
engine.stub(:alive_seats).and_return([s1, s2])
|
370
|
+
engine.roll_dice
|
371
|
+
end
|
372
|
+
|
373
|
+
it "notifies after rolling" do
|
374
|
+
engine.should_receive(:notify_roll)
|
375
|
+
engine.stub(:alive_seats).and_return([])
|
376
|
+
engine.roll_dice
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
describe "#valid_bid?" do
|
381
|
+
before do
|
382
|
+
engine.stub(:previous_bid).and_return(nil)
|
383
|
+
end
|
384
|
+
|
385
|
+
context "when given a BS object" do
|
386
|
+
it "calls valid_bs?" do
|
387
|
+
engine.should_receive(:valid_bs?)
|
388
|
+
engine.valid_bid?(BS.new)
|
389
|
+
end
|
390
|
+
|
391
|
+
it "returns the result of valid_bs?" do
|
392
|
+
engine.stub(:valid_bs?).and_return("foobar")
|
393
|
+
engine.valid_bid?(BS.new).should == "foobar"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
it "returns false if total is negative" do
|
398
|
+
bid = Bid.new(-1, 5)
|
399
|
+
engine.valid_bid?(bid).should == false
|
400
|
+
end
|
401
|
+
|
402
|
+
it "returns false for invalid die face_values" do
|
403
|
+
bid = Bid.new(5, 0)
|
404
|
+
engine.valid_bid?(bid).should == false
|
405
|
+
bid = Bid.new(5, 10)
|
406
|
+
engine.valid_bid?(bid).should == false
|
407
|
+
end
|
408
|
+
|
409
|
+
context "with a previous bid" do
|
410
|
+
before do
|
411
|
+
@prev = Bid.new(3, 3)
|
412
|
+
engine.stub(:previous_bid).and_return(@prev)
|
413
|
+
end
|
414
|
+
|
415
|
+
it "validates all the possible bids correctly" do
|
416
|
+
[[-1, -1, false],
|
417
|
+
[-1, 0, false],
|
418
|
+
[-1, 1, false],
|
419
|
+
[-1, -1, false],
|
420
|
+
[-1, 0, false],
|
421
|
+
[-1, 1, false],
|
422
|
+
[-1, -1, false],
|
423
|
+
[-1, 0, false],
|
424
|
+
[-1, 1, false]].each do |total_delta, face_value_delta, result|
|
425
|
+
bid = Bid.new(@prev.total + total_delta, @prev.face_value + face_value_delta)
|
426
|
+
engine.valid_bid?(bid).should == result
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
describe "#valid_bs?" do
|
433
|
+
it "returns true if there's a previous bid" do
|
434
|
+
prev = Bid.new(3, 3)
|
435
|
+
engine.stub(:previous_bid).and_return(prev)
|
436
|
+
engine.valid_bs?(BS.new).should == true
|
437
|
+
end
|
438
|
+
|
439
|
+
it "returns false if there's not a previous bid" do
|
440
|
+
engine.stub(:previous_bid).and_return(nil)
|
441
|
+
engine.valid_bs?(BS.new).should == false
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
describe "#alive_seats" do
|
446
|
+
it "doesn't return any seats that aren't alive" do
|
447
|
+
s1 = Seat.new(0, nil, 6)
|
448
|
+
s2 = Seat.new(0, nil, 6)
|
449
|
+
s1.stub(:alive?).and_return(true)
|
450
|
+
s2.stub(:alive?).and_return(false)
|
451
|
+
engine.stub(:seats).and_return([s1, s2])
|
452
|
+
engine.alive_seats.should == [s1]
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
describe "notify_bid" do
|
457
|
+
it "passes a BidMadeEvent to notify_players" do
|
458
|
+
engine.should_receive(:notify_players).with(an_instance_of(BidMadeEvent))
|
459
|
+
engine.notify_bid(seat, nil)
|
460
|
+
end
|
461
|
+
|
462
|
+
it "passes a BidMadeEvent to notify_watcher" do
|
463
|
+
engine.should_receive(:notify_watcher).with(an_instance_of(BidMadeEvent))
|
464
|
+
engine.notify_bid(seat, nil)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
describe "notify_bs" do
|
469
|
+
it "passes a BSCalledEvent to notify_players" do
|
470
|
+
engine.stub(:previous_bid).and_return(nil)
|
471
|
+
engine.should_receive(:notify_players).with(an_instance_of(BSCalledEvent))
|
472
|
+
engine.notify_bs(seat)
|
473
|
+
end
|
474
|
+
|
475
|
+
it "passes a BSCalledEvent to notify_watcher" do
|
476
|
+
engine.stub(:previous_bid).and_return(nil)
|
477
|
+
engine.should_receive(:notify_watcher).with(an_instance_of(BSCalledEvent))
|
478
|
+
engine.notify_bs(seat)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
describe "notify_loser" do
|
483
|
+
it "passes a LoserEvent to notify_players" do
|
484
|
+
engine.should_receive(:notify_players).with(an_instance_of(LoserEvent))
|
485
|
+
engine.stub(:seats).and_return([])
|
486
|
+
engine.notify_loser(seat)
|
487
|
+
end
|
488
|
+
|
489
|
+
it "passes a LoserEvent to notify_watcher" do
|
490
|
+
engine.should_receive(:notify_watcher).with(an_instance_of(LoserEvent))
|
491
|
+
engine.stub(:seats).and_return([])
|
492
|
+
engine.notify_loser(seat)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
describe "notify_winner" do
|
497
|
+
it "passes a WinnerEvent to notify_players" do
|
498
|
+
engine.should_receive(:notify_players).with(an_instance_of(WinnerEvent))
|
499
|
+
engine.stub(:winner).and_return(seat)
|
500
|
+
engine.notify_winner
|
501
|
+
end
|
502
|
+
|
503
|
+
it "passes a WinnerEvent to notify_watcher" do
|
504
|
+
engine.should_receive(:notify_watcher).with(an_instance_of(WinnerEvent))
|
505
|
+
engine.stub(:winner).and_return(seat)
|
506
|
+
engine.notify_winner
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
describe "notify_players" do
|
511
|
+
let (:player1) { {} }
|
512
|
+
let (:player2) { {} }
|
513
|
+
let (:player3) { {} }
|
514
|
+
let (:watcher) { {} }
|
515
|
+
|
516
|
+
before do
|
517
|
+
player1.stub(:handle_event)
|
518
|
+
player2.stub(:handle_event)
|
519
|
+
player3.stub(:handle_event)
|
520
|
+
|
521
|
+
s1 = Seat.new(0, player1, 5)
|
522
|
+
s2 = Seat.new(0, player2, 5)
|
523
|
+
s3 = Seat.new(0, player3, 5)
|
524
|
+
engine.stub(:seats).and_return([s1, s2, s3])
|
525
|
+
engine.stub(:watcher).and_return(watcher)
|
526
|
+
end
|
527
|
+
|
528
|
+
it "passes the event to all players" do
|
529
|
+
event = WinnerEvent.new(seat)
|
530
|
+
player1.should_receive(:handle_event).with(event)
|
531
|
+
player2.should_receive(:handle_event).with(event)
|
532
|
+
player3.should_receive(:handle_event).with(event)
|
533
|
+
|
534
|
+
engine.notify_players(event)
|
535
|
+
end
|
536
|
+
|
537
|
+
it "does not pass the event to the watcher" do
|
538
|
+
event = WinnerEvent.new(seat)
|
539
|
+
watcher.should_not_receive(:handle_event).with(event)
|
540
|
+
|
541
|
+
engine.notify_players(event)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
describe "notify_watcher" do
|
546
|
+
let (:player1) { {} }
|
547
|
+
let (:player2) { {} }
|
548
|
+
let (:player3) { {} }
|
549
|
+
let (:watcher) { {} }
|
550
|
+
|
551
|
+
before do
|
552
|
+
watcher.stub(:handle_event)
|
553
|
+
|
554
|
+
s1 = Seat.new(0, player1, 5)
|
555
|
+
s2 = Seat.new(0, player2, 5)
|
556
|
+
s3 = Seat.new(0, player3, 5)
|
557
|
+
engine.stub(:seats).and_return([s1, s2, s3])
|
558
|
+
engine.stub(:watcher).and_return(watcher)
|
559
|
+
end
|
560
|
+
|
561
|
+
it "does not pass the event to any player" do
|
562
|
+
event = WinnerEvent.new(seat)
|
563
|
+
player1.should_not_receive(:handle_event).with(event)
|
564
|
+
player2.should_not_receive(:handle_event).with(event)
|
565
|
+
player3.should_not_receive(:handle_event).with(event)
|
566
|
+
|
567
|
+
engine.notify_watcher(event)
|
568
|
+
end
|
569
|
+
|
570
|
+
it "passes the event to the watcher" do
|
571
|
+
event = WinnerEvent.new(seat)
|
572
|
+
watcher.should_receive(:handle_event).with(event)
|
573
|
+
|
574
|
+
engine.notify_watcher(event)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
data/spec/seat_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Seat" do
|
4
|
+
let (:player) { {} }
|
5
|
+
let (:seat) { Seat.new(0, player, 5) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
player.stub(:dice=)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#alive?" do
|
12
|
+
it "returns true when there are dice left" do
|
13
|
+
seat.stub(:dice_left).and_return(4)
|
14
|
+
seat.should be_alive
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns false when there are no dice left" do
|
18
|
+
seat.stub(:dice_left).and_return(0)
|
19
|
+
seat.should_not be_alive
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#dice=" do
|
24
|
+
it "passes the dice to the player" do
|
25
|
+
player.should_receive(:dice=).with([1, 2, 3])
|
26
|
+
seat.dice = [1,2,3]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "remembers the dice" do
|
30
|
+
seat.dice = [4, 5, 6]
|
31
|
+
seat.dice.should == [4, 5, 6]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#lose_die" do
|
36
|
+
it "adjusts the dice_left count" do
|
37
|
+
count = seat.dice_left
|
38
|
+
seat.lose_die
|
39
|
+
seat.dice_left.should == (count - 1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: liars_dice
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben Schmeckpeper
|
9
|
+
- Chris Doyle
|
10
|
+
- Max Page
|
11
|
+
- Molly Struve
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
date: 2013-06-28 00:00:00.000000000Z
|
16
|
+
dependencies: []
|
17
|
+
description: A liar's dice botting environment, developed by Aisle50
|
18
|
+
email: dev@aisle50.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- README.md
|
24
|
+
- lib/liars_dice.rb
|
25
|
+
- lib/liars_dice/bid.rb
|
26
|
+
- lib/liars_dice/command_line_watcher.rb
|
27
|
+
- lib/liars_dice/engine.rb
|
28
|
+
- lib/liars_dice/event.rb
|
29
|
+
- lib/liars_dice/human_bot.rb
|
30
|
+
- lib/liars_dice/random_bot.rb
|
31
|
+
- lib/liars_dice/seat.rb
|
32
|
+
- lib/liars_dice/watcher.rb
|
33
|
+
- spec/bid_spec.rb
|
34
|
+
- spec/engine_spec.rb
|
35
|
+
- spec/seat_spec.rb
|
36
|
+
- spec/spec_helper.rb
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.8.15
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Liar's Dice game
|
61
|
+
test_files:
|
62
|
+
- spec/bid_spec.rb
|
63
|
+
- spec/engine_spec.rb
|
64
|
+
- spec/seat_spec.rb
|
65
|
+
- spec/spec_helper.rb
|