From 6e0a97df53d2b2526e68218861a730330a22e906 Mon Sep 17 00:00:00 2001 From: b3ni15 Date: Sat, 20 Dec 2025 23:33:55 +0100 Subject: [PATCH] Add turn timer functionality to Table class; update environment configuration --- .env.example | 1 + src/game/tables.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/.env.example b/.env.example index 04765d1..7408880 100644 --- a/.env.example +++ b/.env.example @@ -20,3 +20,4 @@ MIN_BET=10 MAX_BET=500 ROUND_START_DELAY_MS=3000 ROUND_RESET_DELAY_MS=5000 +TURN_TIME_MS=15000 diff --git a/src/game/tables.js b/src/game/tables.js index 49496a5..114de22 100644 --- a/src/game/tables.js +++ b/src/game/tables.js @@ -5,6 +5,7 @@ const MIN_BET = Number(process.env.MIN_BET || 10); const MAX_BET = Number(process.env.MAX_BET || 100); const ROUND_START_DELAY_MS = Number(process.env.ROUND_START_DELAY_MS || 3000); const ROUND_RESET_DELAY_MS = Number(process.env.ROUND_RESET_DELAY_MS || 5000); +const TURN_TIME_MS = Number(process.env.TURN_TIME_MS || 15000); class Table { constructor(id, seatCount) { @@ -26,6 +27,9 @@ class Table { this.roundId = 0; this.roundTimeout = null; this.resetTimeout = null; + this.turnTimeout = null; + this.turnEndsAt = null; + this.turnCounter = 0; this.clients = new Set(); } @@ -55,6 +59,7 @@ class Table { minBet: MIN_BET, maxBet: MAX_BET, roundId: this.roundId, + turnEndsAt: this.turnEndsAt, dealerHand, currentSeatIndex: this.currentSeatIndex, seats: this.seats.map((seat, index) => ({ @@ -103,6 +108,36 @@ class Table { } } + clearTurnTimer() { + if (this.turnTimeout) { + clearTimeout(this.turnTimeout); + } + this.turnTimeout = null; + this.turnEndsAt = null; + } + + startTurnTimer() { + this.clearTurnTimer(); + if (this.currentSeatIndex === null) { + return; + } + + this.turnEndsAt = Date.now() + TURN_TIME_MS; + const turnToken = ++this.turnCounter; + + this.turnTimeout = setTimeout(() => { + if (this.turnCounter !== turnToken) { + return; + } + const seat = this.seats[this.currentSeatIndex]; + if (!seat || seat.status !== 'playing') { + return; + } + seat.status = 'stood'; + void this.advanceTurn(); + }, TURN_TIME_MS); + } + findSeatIndexByUser(userId) { return this.seats.findIndex((seat) => seat.userId === userId); } @@ -251,6 +286,7 @@ class Table { return; } + this.clearTurnTimer(); this.phase = 'playing'; this.roundId += 1; this.deck = shuffle(createDeck()); @@ -272,6 +308,7 @@ class Table { return; } + this.startTurnTimer(); this.broadcastState(); } @@ -310,6 +347,8 @@ class Table { } else if (value === 21) { seat.status = 'stood'; await this.advanceTurn(); + } else { + this.startTurnTimer(); } } else if (action === 'stand') { seat.status = 'stood'; @@ -343,15 +382,18 @@ class Table { async advanceTurn() { const nextIndex = this.nextPlayableSeatIndex(this.currentSeatIndex ?? -1); if (nextIndex === null) { + this.clearTurnTimer(); await this.dealerTurn(); return; } this.currentSeatIndex = nextIndex; + this.startTurnTimer(); this.broadcastState(); } async dealerTurn() { + this.clearTurnTimer(); this.phase = 'dealer'; this.currentSeatIndex = null; @@ -423,6 +465,7 @@ class Table { } resetRound() { + this.clearTurnTimer(); for (const seat of this.seats) { if (!seat.userId) { continue;