Class: LiarsDice::Engine

Inherits:
Object
  • Object
show all
Includes:
Watcher
Defined in:
lib/liars_dice/engine.rb

Instance Attribute Summary collapse

Attributes included from Watcher

#after_bid, #after_bs, #after_dice_rolled, #after_game, #after_invalid_bid, #after_roll, #after_round, #after_seats_assigned

Instance Method Summary collapse

Methods included from Watcher

#append_after_bid, #append_after_bs, #append_after_dice_rolled, #append_after_game, #append_after_invalid_bid, #append_after_round, #append_after_seats_assigned, #handle_event

Constructor Details

#initialize(player_classes, dice_per_player, watcher = nil) ⇒ Engine

Returns a new instance of Engine.



7
8
9
10
11
12
13
14
15
# File 'lib/liars_dice/engine.rb', line 7

def initialize(player_classes, dice_per_player, watcher=nil)
  self.seats = []
  player_classes.shuffle.each_with_index do |klass, i|
    player = klass.new(i, player_classes.count, dice_per_player)
    self.seats << Seat.new(i, player, dice_per_player)
  end
  self.seat_index = 0
  self.watcher = watcher || self
end

Instance Attribute Details

#bidsObject

Returns the value of attribute bids.



4
5
6
# File 'lib/liars_dice/engine.rb', line 4

def bids
  @bids
end

#loserObject

Returns the value of attribute loser.



5
6
7
# File 'lib/liars_dice/engine.rb', line 5

def loser
  @loser
end

#seat_indexObject

Returns the value of attribute seat_index.



5
6
7
# File 'lib/liars_dice/engine.rb', line 5

def seat_index
  @seat_index
end

#seatsObject

Returns the value of attribute seats.



4
5
6
# File 'lib/liars_dice/engine.rb', line 4

def seats
  @seats
end

#starting_seatObject

Returns the value of attribute starting_seat.



4
5
6
# File 'lib/liars_dice/engine.rb', line 4

def starting_seat
  @starting_seat
end

#watcherObject

Returns the value of attribute watcher.



4
5
6
# File 'lib/liars_dice/engine.rb', line 4

def watcher
  @watcher
end

Instance Method Details

#alive_seatsObject



192
193
194
# File 'lib/liars_dice/engine.rb', line 192

def alive_seats
  seats.select(&:alive?)
end

#bid_is_correct?(bid, use_wilds) ⇒ Boolean

Returns:

  • (Boolean)


48
49
50
51
52
# File 'lib/liars_dice/engine.rb', line 48

def bid_is_correct?(bid, use_wilds)
  total = total_dice_with_face_value(bid.face_value)
  total += total_dice_with_face_value(1) if use_wilds
  total >= bid.total
end

#get_bid(seat) ⇒ Object



27
28
29
# File 'lib/liars_dice/engine.rb', line 27

def get_bid(seat)
  seat.player.bid
end

#next_seatObject



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/liars_dice/engine.rb', line 31

def next_seat
  # If no seats are alive, we'd loop forever
  return nil if alive_seats.empty?

  seat = seats[seat_index]
  self.seat_index += 1

  # If the seat at seat_index is alive, return it
  # Otherwise, we've already updated seat_index (and wrapped it, if necessary)
  # so just call next_seat again
  seat.alive? ? seat : next_seat
end

#notify_bid(seat, bid) ⇒ Object

Notification Events ====



106
107
108
109
110
# File 'lib/liars_dice/engine.rb', line 106

def notify_bid(seat, bid)
  event = BidMadeEvent.new(seat.number, bid)
  notify_players(event)
  notify_watcher(event)
end

#notify_bs(seat) ⇒ Object



112
113
114
115
116
# File 'lib/liars_dice/engine.rb', line 112

def notify_bs(seat)
  event = BSCalledEvent.new(seat.number, previous_bid)
  notify_players(event)
  notify_watcher(event)
end

#notify_invalid_bid(seat) ⇒ Object



118
119
120
121
122
# File 'lib/liars_dice/engine.rb', line 118

def notify_invalid_bid(seat)
  event = InvalidBidEvent.new(seat.number)
  notify_players(event)
  notify_watcher(event)
end

#notify_loser(seat) ⇒ Object



124
125
126
127
128
129
# File 'lib/liars_dice/engine.rb', line 124

def notify_loser(seat)
  dice = seats.map(&:dice)
  event = LoserEvent.new(seat.number, dice)
  notify_players(event)
  notify_watcher(event)
end

#notify_players(event) ⇒ Object



148
149
150
# File 'lib/liars_dice/engine.rb', line 148

def notify_players(event)
  seats.each{|s| s.player.handle_event(event) }
end

#notify_rollObject



137
138
139
140
141
# File 'lib/liars_dice/engine.rb', line 137

def notify_roll
  dice = seats.map(&:dice)
  event = DiceRolledEvent.new(dice)
  notify_watcher(event)
end

#notify_seatsObject



143
144
145
146
# File 'lib/liars_dice/engine.rb', line 143

def notify_seats
  event = SeatsAssignedEvent.new(seats)
  notify_watcher(event)
end

#notify_watcher(event) ⇒ Object



152
153
154
# File 'lib/liars_dice/engine.rb', line 152

def notify_watcher(event)
  watcher.handle_event(event)
end

#notify_winnerObject



131
132
133
134
135
# File 'lib/liars_dice/engine.rb', line 131

def notify_winner
  event = WinnerEvent.new(winner.number)
  notify_players(event)
  notify_watcher(event)
end

#previous_bidObject



188
189
190
# File 'lib/liars_dice/engine.rb', line 188

def previous_bid
  bids[-1]
end

#roll_diceObject



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/liars_dice/engine.rb', line 90

def roll_dice
  die = (1..6).to_a
  alive_seats.each do |seat|
    dice = []
    seat.dice_left.times do
      dice << die.sample
    end

    seat.dice = dice
  end
  notify_roll
end

#runObject



17
18
19
20
21
22
23
24
25
# File 'lib/liars_dice/engine.rb', line 17

def run
  notify_seats
  until winner?
    roll_dice
    run_round
  end
  notify_winner
  nil
end

#run_roundObject

Raises:

  • (StandardError)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/liars_dice/engine.rb', line 54

def run_round
  self.bids = []

  previous_seat = nil
  aces_wild = true
  while true
    seat = next_seat
    bid = get_bid(seat)

    # An invalid bid ends the round and costs the bidder a die
    unless valid_bid?(bid)
      self.loser = seat
      notify_invalid_bid(seat)
      break
    end

    # If someone calls BS, figure out the loser and exit the loop
    if bid.bs_called?
      notify_bs(seat)
      self.loser = bid_is_correct?(previous_bid, aces_wild) ? seat : previous_seat
      break
    end

    # For a valid, non-BS bid, update wilds, record and notify the bid and prepare for the next bid
    aces_wild = false if bid.face_value == 1
    self.bids << bid
    notify_bid(seat, bid)
    previous_seat = seat
  end

  # It's an error if we get here without having set a loser
  raise StandardError.new("Unknown loser") unless loser
  notify_loser(loser)
  loser.lose_die
end

#total_dice_with_face_value(face_value) ⇒ Object



44
45
46
# File 'lib/liars_dice/engine.rb', line 44

def total_dice_with_face_value(face_value)
  seats.map{|seat| seat.dice.count(face_value) }.reduce(0, :+)
end

#valid_bid?(bid) ⇒ Boolean

Returns:

  • (Boolean)


164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/liars_dice/engine.rb', line 164

def valid_bid?(bid)
  return false unless bid

  if bid.bs_called?
    return valid_bs?(bid)
  end

  if bid.face_value < 1 || bid.face_value > 6
    # Can't bid a face_value that doesn't exist
    return false
  elsif bid.total < 1
    # Have to bid a positive total
    return false
  elsif previous_bid && bid.total < previous_bid.total
    # The total must be monotonically increasing
    return false
  elsif previous_bid && bid.total == previous_bid.total && bid.face_value <= previous_bid.face_value
    # If the total does not increase, the face_value must
    return false
  end

  true
end

#valid_bs?(bid) ⇒ Boolean

Validation Methods ====

Returns:

  • (Boolean)


159
160
161
162
# File 'lib/liars_dice/engine.rb', line 159

def valid_bs?(bid)
  # Cannot bid BS if there isn't a previous bid
  !!previous_bid
end

#winnerObject



200
201
202
203
# File 'lib/liars_dice/engine.rb', line 200

def winner
  return nil unless winner?
  alive_seats.first
end

#winner?Boolean

Returns:

  • (Boolean)


196
197
198
# File 'lib/liars_dice/engine.rb', line 196

def winner?
  alive_seats.count == 1
end