From ab7ddfed4b68a30f208ca4b1f267a08a173bafe9 Mon Sep 17 00:00:00 2001 From: RandyJC Date: Tue, 9 Dec 2025 14:02:52 +0100 Subject: [PATCH] adding pause and resume in game state --- packages/common/src/types/game/socket.ts | 3 ++ packages/socket/src/index.ts | 4 +++ packages/socket/src/services/game.ts | 25 +++++++++++++ packages/web/src/app/(auth)/manager/page.tsx | 4 +++ .../web/src/components/game/GameWrapper.tsx | 13 +++++++ .../src/components/game/create/QuizEditor.tsx | 35 ++++++++++++------- 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/packages/common/src/types/game/socket.ts b/packages/common/src/types/game/socket.ts index e194d21..5a8c1ff 100644 --- a/packages/common/src/types/game/socket.ts +++ b/packages/common/src/types/game/socket.ts @@ -35,6 +35,7 @@ export interface ServerToClientEvents { "game:reset": (_message: string) => void "game:updateQuestion": (_data: { current: number; total: number }) => void "game:playerAnswer": (_count: number) => void + "game:break": (_active: boolean) => void // Player events "player:successReconnect": (_data: { @@ -66,6 +67,7 @@ export interface ServerToClientEvents { "manager:quizzLoaded": (_quizz: QuizzWithId) => void "manager:quizzSaved": (_quizz: QuizzWithId) => void "manager:quizzDeleted": (_id: string) => void + "manager:break": (_active: boolean) => void } export interface ClientToServerEvents { @@ -78,6 +80,7 @@ export interface ClientToServerEvents { "manager:abortQuiz": (_message: MessageGameId) => void "manager:pauseCooldown": (_message: MessageGameId) => void "manager:resumeCooldown": (_message: MessageGameId) => void + "manager:setBreak": (_message: { gameId?: string; active: boolean }) => void "manager:endGame": (_message: MessageGameId) => void "manager:skipQuestionIntro": (_message: MessageGameId) => void "manager:nextQuestion": (_message: MessageGameId) => void diff --git a/packages/socket/src/index.ts b/packages/socket/src/index.ts index 8499a1d..ae8d555 100644 --- a/packages/socket/src/index.ts +++ b/packages/socket/src/index.ts @@ -234,6 +234,10 @@ io.on("connection", (socket) => { withGame(gameId, socket, (game) => game.resumeCooldown(socket)) ) + socket.on("manager:setBreak", ({ gameId, active }) => + withGame(gameId, socket, (game) => game.setBreak(socket, active)) + ) + socket.on("manager:endGame", ({ gameId }) => withGame(gameId, socket, (game) => game.endGame(socket, registry)) ) diff --git a/packages/socket/src/services/game.ts b/packages/socket/src/services/game.ts index 12174a0..81db6b4 100644 --- a/packages/socket/src/services/game.ts +++ b/packages/socket/src/services/game.ts @@ -46,6 +46,7 @@ class Game { timer: NodeJS.Timeout | null resolve: (() => void) | null } + breakActive: boolean constructor(io: Server, socket: Socket, quizz: Quizz) { if (!io) { @@ -84,6 +85,7 @@ class Game { timer: null, resolve: null, } + this.breakActive = false const roomInvite = createInviteCode() this.inviteCode = roomInvite @@ -137,6 +139,7 @@ class Game { timer: null, resolve: null, } + game.breakActive = snapshot.breakActive || false if (game.cooldown.active && game.cooldown.remaining > 0 && !game.cooldown.paused) { game.startCooldown(game.cooldown.remaining) @@ -193,6 +196,7 @@ class Game { paused: this.cooldown.paused, remaining: this.cooldown.remaining, }, + breakActive: this.breakActive, } } @@ -453,6 +457,27 @@ class Game { this.persist() } + setBreak(socket: Socket, active: boolean) { + if (this.manager.id !== socket.id) { + return + } + + this.breakActive = active + + if (this.cooldown.active) { + if (active) { + this.cooldown.paused = true + } else { + this.cooldown.paused = false + } + this.io.to(this.gameId).emit("game:cooldownPause", this.cooldown.paused) + } + + this.io.to(this.gameId).emit("game:break", active) + this.io.to(this.manager.id).emit("manager:break", active) + this.persist() + } + skipQuestionIntro(socket: Socket) { if (this.manager.id !== socket.id) { return diff --git a/packages/web/src/app/(auth)/manager/page.tsx b/packages/web/src/app/(auth)/manager/page.tsx index 0c6392d..f756b95 100644 --- a/packages/web/src/app/(auth)/manager/page.tsx +++ b/packages/web/src/app/(auth)/manager/page.tsx @@ -41,6 +41,9 @@ const Manager = () => { const handleCreate = (quizzId: string) => { socket?.emit("game:create", quizzId) } + const handleBreakToggle = (active: boolean) => { + socket?.emit("manager:setBreak", { gameId: null, active }) + } if (!isAuth) { return @@ -52,6 +55,7 @@ const Manager = () => { quizzList={quizzList} onBack={() => setShowEditor(false)} onListUpdate={setQuizzList} + onBreakToggle={handleBreakToggle} /> ) } diff --git a/packages/web/src/components/game/GameWrapper.tsx b/packages/web/src/components/game/GameWrapper.tsx index ca71cbc..cddaf28 100644 --- a/packages/web/src/components/game/GameWrapper.tsx +++ b/packages/web/src/components/game/GameWrapper.tsx @@ -39,6 +39,7 @@ const GameWrapper = ({ const { questionStates, setQuestionStates } = useQuestionStore() const { backgroundUrl, setBackground, setBrandName } = useThemeStore() const [isDisabled, setIsDisabled] = useState(false) + const [onBreak, setOnBreak] = useState(false) const next = statusName ? MANAGER_SKIP_BTN[statusName] : null useEvent("game:updateQuestion", ({ current, total }) => { @@ -52,6 +53,9 @@ const GameWrapper = ({ setIsDisabled(false) }, [statusName]) + useEvent("game:break", (active) => setOnBreak(active)) + useEvent("manager:break", (active) => setOnBreak(active)) + useEffect(() => { const loadTheme = async () => { try { @@ -171,6 +175,15 @@ const GameWrapper = ({ )} + + {onBreak && ( +
+
+

Game paused for a break

+

We'll resume from the same spot.

+
+
+ )} )} diff --git a/packages/web/src/components/game/create/QuizEditor.tsx b/packages/web/src/components/game/create/QuizEditor.tsx index 4ad09ca..f8f6a3c 100644 --- a/packages/web/src/components/game/create/QuizEditor.tsx +++ b/packages/web/src/components/game/create/QuizEditor.tsx @@ -12,6 +12,7 @@ type Props = { quizzList: QuizzWithId[] onBack: () => void onListUpdate: (_quizz: QuizzWithId[]) => void + onBreakToggle?: (_active: boolean) => void } type EditableQuestion = QuizzWithId["questions"][number] @@ -55,7 +56,7 @@ const formatBytes = (bytes: number) => { return `${value.toFixed(value >= 10 || value % 1 === 0 ? 0 : 1)} ${units[i]}` } -const QuizEditor = ({ quizzList, onBack, onListUpdate }: Props) => { +const QuizEditor = ({ quizzList, onBack, onListUpdate, onBreakToggle }: Props) => { const { socket } = useSocket() const [selectedId, setSelectedId] = useState(null) const [draft, setDraft] = useState(null) @@ -462,19 +463,29 @@ const QuizEditor = ({ quizzList, onBack, onListUpdate }: Props) => { return (
-
- + + {selectedId && ( + - - {selectedId && ( - - )} -
+ + + )} +