diff options
Diffstat (limited to 'internal/casino/craps_clock.go')
| -rw-r--r-- | internal/casino/craps_clock.go | 92 |
1 files changed, 47 insertions, 45 deletions
diff --git a/internal/casino/craps_clock.go b/internal/casino/craps_clock.go index ab28329..5357c93 100644 --- a/internal/casino/craps_clock.go +++ b/internal/casino/craps_clock.go @@ -1,30 +1,26 @@ package casino -import ( - "fmt" - "time" -) +import "fmt" +// Every craps deadline is a tick count; the engine tick length decides the +// wall-clock duration. const ( - crapsBetDelay = 10 * time.Second - crapsDealerDelay = 60 * time.Second - crapsAutoDelay = 3 * time.Second + crapsBetTicks = 10 + crapsDealerTicks = 60 + crapsAutoTicks = 3 // Simplified craps has no manual roll: one shared automatic countdown // starts with the first live bet and resolves every wager. - crapsSimplifiedDelay = 3 * time.Second + crapsSimplifiedTicks = 3 // A requested manual roll first shows the shake, then the outcome chart, - // then resolves the dice five ticks (three seconds) later. - crapsManualRollDelay = 3 * time.Second + // then resolves the dice three ticks later. + crapsManualRollTicks = 3 + + // Last-call warning fires this many ticks before the shooter may roll. + crapsLastCallTicks = 3 ) -func crapsSeconds(d time.Duration) int { - if d <= 0 { - return 0 - } - return int((d + time.Second - 1) / time.Second) -} func (t *crapsTable) shooter() *crapsPlayer { if len(t.queue) == 0 { return nil @@ -53,13 +49,18 @@ func (t *crapsTable) activePlayers() int { // multiplayer or starting a new round opens a window; adding bets never // extends it. A solo player has no forced deadline at all. func (t *crapsTable) syncRoundClock() []Event { + // Cancel immediately, not on the next tick: a new bet must not inherit + // a roll requested for wagers that have since been taken down. + if !t.hasLiveBets() { + t.pendingRollTicks = 0 + } multi := t.activePlayers() > 1 if t.Config.Craps.variant() == "simplified" { // Simplified craps never opens a manual shooting window; the first // live bet starts one shared automatic countdown for everyone. if !t.hasLiveBets() { t.phase = phaseWaiting - t.roundElapsed = 0 + t.roundTicks = 0 t.countdown = 0 t.lastCall = false t.multiplayer = multi @@ -68,18 +69,18 @@ func (t *crapsTable) syncRoundClock() []Event { fresh := t.phase == phaseWaiting t.multiplayer = multi if fresh { - t.roundElapsed = 0 + t.roundTicks = 0 t.countdown = 0 } t.phase = phasePlaying if fresh && multi { - return []Event{t.event("{casino_emphasis}Bets open{/} — the dice roll in 3 seconds.", true)} + return []Event{t.event("{casino_emphasis}Bets open{/} — the dice roll in 3 ticks.", true)} } return nil } if !t.hasLiveBets() { t.phase = phaseWaiting - t.roundElapsed = 0 + t.roundTicks = 0 t.lastCall = false t.multiplayer = multi return nil @@ -87,54 +88,54 @@ func (t *crapsTable) syncRoundClock() []Event { fresh := t.phase == phaseWaiting || (multi && !t.multiplayer) t.multiplayer = multi if fresh { - t.roundElapsed = 0 + t.roundTicks = 0 t.lastCall = false } t.phase = phasePlaying - if multi && t.roundElapsed < crapsBetDelay { + if multi && t.roundTicks < crapsBetTicks { t.phase = phaseBetting } if fresh && multi { - return []Event{t.event("{casino_emphasis}Bets open{/} — 10 seconds before the shooter can roll. Betting stays open until the dice roll.", true)} + return []Event{t.event("{casino_emphasis}Bets open{/} — 10 ticks before the shooter can roll. Betting stays open until the dice roll.", true)} } return nil } -func (t *crapsTable) tickElapsed(elapsed time.Duration) []Event { +func (t *crapsTable) advance(ticks int) []Event { t.mu.Lock() defer t.mu.Unlock() events := t.syncRoundClock() + if ticks <= 0 { + return events + } if t.Config.Craps.variant() == "simplified" { - if elapsed <= 0 || !t.hasLiveBets() { + if !t.hasLiveBets() { return events } - t.roundElapsed += elapsed + t.roundTicks += ticks shooter := t.shooter() if shooter == nil { return events } - for t.countdown < len(crapsCountdownFlavor) && t.roundElapsed >= time.Duration(t.countdown+1)*time.Second { + for t.countdown < len(crapsCountdownFlavor) && t.roundTicks >= t.countdown+1 { events = append(events, crapsCountdownEvent(t, shooter.Name, t.countdown)) t.countdown++ } - if t.roundElapsed >= crapsSimplifiedDelay { + if t.roundTicks >= crapsSimplifiedTicks { events = append(events, t.roll()...) } return events } - if elapsed <= 0 { - return events - } - if t.pendingRoll > 0 { + if t.pendingRollTicks > 0 { // A manual roll is in the air: no other deadline may fire first. Only // the shake is announced now; the result follows when the dice land. if !t.hasLiveBets() { - t.pendingRoll = 0 + t.pendingRollTicks = 0 return events } - t.pendingRoll -= elapsed - if t.pendingRoll <= 0 { - t.pendingRoll = 0 + t.pendingRollTicks -= ticks + if t.pendingRollTicks <= 0 { + t.pendingRollTicks = 0 return append(events, t.roll()...) } return events @@ -142,32 +143,33 @@ func (t *crapsTable) tickElapsed(elapsed time.Duration) []Event { if !t.hasLiveBets() { return events } - t.roundElapsed += elapsed + t.roundTicks += ticks p := t.shooter() if p == nil { return events } if p.autoRoll && p.HasBet { - p.autoElapsed += elapsed + p.autoTicks += ticks } - if t.multiplayer && !t.lastCall && t.roundElapsed >= crapsBetDelay-3*time.Second { + if t.multiplayer && !t.lastCall && t.roundTicks >= crapsBetTicks-crapsLastCallTicks { t.lastCall = true - events = append(events, t.event(fmt.Sprintf("{casino_warning}Last call for bets! 3 seconds until %s can shoot!{/}", p.Name), true)) + remaining := max(1, crapsBetTicks-t.roundTicks) + events = append(events, t.event(fmt.Sprintf("{casino_warning}Last call for bets! %d ticks until %s can shoot!{/}", remaining, p.Name), true)) } - if t.multiplayer && t.roundElapsed < crapsBetDelay { + if t.multiplayer && t.roundTicks < crapsBetTicks { return events } if t.phase == phaseBetting { - events = append(events, t.event(fmt.Sprintf("{casino_emphasis}%s can shoot!{/} Bets stay open until the roll. Dealer rolls in 60 seconds if needed.", p.Name), true), t.reminder(p.Name)) + events = append(events, t.event(fmt.Sprintf("{casino_emphasis}%s can shoot!{/} Bets stay open until the roll. Dealer rolls in %d ticks if needed.", p.Name, crapsDealerTicks), true), t.reminder(p.Name)) } t.phase = phasePlaying - if (p.autoRoll && p.HasBet && p.autoElapsed >= crapsAutoDelay) || (t.multiplayer && t.roundElapsed >= crapsBetDelay+crapsDealerDelay) { + if (p.autoRoll && p.HasBet && p.autoTicks >= crapsAutoTicks) || (t.multiplayer && t.roundTicks >= crapsBetTicks+crapsDealerTicks) { return append(events, t.roll()...) } return events } -// crapsCountdownFlavor broadcasts one line per second before the automatic +// crapsCountdownFlavor broadcasts one line per tick before the automatic // simplified roll. Each step carries a few variants: the public line names // the shooter, the private line speaks to them. var crapsCountdownFlavor = [][3][2]string{ @@ -207,6 +209,6 @@ func (t *Table) StopAutoRoll(name string) ([]Event, bool) { return nil, false } p.autoRoll = false - p.autoElapsed = 0 + p.autoTicks = 0 return []Event{game.private(name, "{casino_emphasis}Autoroll OFF{/} — manual play resumed. Your bets stay on the table."), game.reminder(name)}, true } |
