aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_clock.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/craps_clock.go')
-rw-r--r--internal/casino/craps_clock.go92
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
}