aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/blackjack.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/blackjack.go')
-rw-r--r--internal/casino/blackjack.go56
1 files changed, 33 insertions, 23 deletions
diff --git a/internal/casino/blackjack.go b/internal/casino/blackjack.go
index a047c71..3bc70b4 100644
--- a/internal/casino/blackjack.go
+++ b/internal/casino/blackjack.go
@@ -81,12 +81,11 @@ type blackjackTable struct {
}
// loneTurnGrace multiplies the action window into an absolute per-turn
-// ceiling. A lone connected player is deliberately never timed out by the
-// action timer (a solo player's think time must not end the hand), but
-// "never" cannot mean forever: a hung-but-connected client would hold the
-// hand, the seat, and the stake open with no automatic resolution, since
-// "leave" refuses mid-round. After this many ticks on one turn the table
-// stands the hand for the player and plays on.
+// ceiling. A lone connected player has no time limit at all (a solo player's
+// think time must not end the hand), but a multiplayer turn is still bounded:
+// a hung client must not hold the table, the other seats, and their stakes
+// open, since "leave" refuses mid-round. After this many ticks on one turn
+// the table stands the hand for the player and plays on.
const loneTurnGrace = 10
func newBlackjackTable(roomID int, cfg TableConfig, rng *rand.Rand) *blackjackTable {
@@ -212,6 +211,10 @@ func (t *blackjackTable) tick() []Event {
if t.insuranceSettled() {
return t.finishInsurance()
}
+ // A lone bettor is never timed out into declining insurance.
+ if t.bettorCount() <= 1 {
+ return nil
+ }
t.timer.tick()
if !t.timer.expired() {
var events []Event
@@ -224,7 +227,14 @@ func (t *blackjackTable) tick() []Event {
}
return events
}
- return t.finishInsurance()
+ // Announce the timeout itself, never silently declining the offer.
+ var events []Event
+ for _, p := range t.players.ordered() {
+ if p.HasBet && p.insurance == 0 && !p.declinedInsurance && p.Connected {
+ events = append(events, t.warning(p.Name, "Insurance timed out; continuing without it."))
+ }
+ }
+ return append(events, t.finishInsurance()...)
case blackjackPlaying:
name := t.activeName()
if name == "" {
@@ -236,24 +246,20 @@ func (t *blackjackTable) tick() []Event {
t.activePlayer++
return t.finishHand()
}
- // The absolute turn ceiling bounds every turn, including a lone
- // connected player's: the action timer below deliberately never
- // expires for a solo player, so without this a hung-but-connected
- // client would hold the stake and the phase open forever.
- t.ceiling.tick()
- if t.ceiling.expired() {
- active.hands[t.activeHand].stood = true
- events := []Event{t.warning(name, "You've been idle too long; standing.")}
- // Restart the action timer so the next hand gets a full window.
- t.timer.start(t.Rules.ActionTicks)
- return append(events, t.finishHand()...)
+ // A lone bettor has no time limit at all. With two or more bettors
+ // the absolute turn ceiling bounds a hung client; a disconnected
+ // active player is always timed out so the table can never stall.
+ if t.bettorCount() > 1 {
+ t.ceiling.tick()
+ if t.ceiling.expired() {
+ active.hands[t.activeHand].stood = true
+ events := []Event{t.warning(name, "You've been idle too long; standing.")}
+ // Restart the action timer so the next hand gets a full window.
+ t.timer.start(t.Rules.ActionTicks)
+ return append(events, t.finishHand()...)
+ }
}
if active.Connected && t.bettorCount() <= 1 {
- // A lone connected player is never timed out. A disconnected
- // active player always is, so the table can never stall.
- if t.ceiling.warn() {
- return []Event{t.warning(name, fmt.Sprintf("%d ticks remaining. No response means stand.", t.ceiling.remaining()))}
- }
return nil
}
t.timer.tick()
@@ -278,6 +284,10 @@ func (t *blackjackTable) tick() []Event {
func (t *blackjackTable) action(name, action string) []Event {
t.mu.Lock()
defer t.mu.Unlock()
+ if action == "unbet" {
+ events, _ := t.unbetLocked(name)
+ return events
+ }
if events, handled := t.controlLocked(name, action); handled {
if t.phase == blackjackBetting && allBet(t.players.participants()) {
return append(events, t.startRound()...)