From aedd3c032ae7a16229d8480e02b3db2990a425a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Thu, 12 Mar 2026 19:50:43 +0100 Subject: [PATCH] extend GameManager game state with jury, bingo, leaderboard Co-Authored-By: Claude Opus 4.6 --- packages/server/src/games/game-manager.ts | 42 ++++++++++++++++++++-- packages/server/tests/game-manager.test.ts | 34 ++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/server/src/games/game-manager.ts b/packages/server/src/games/game-manager.ts index 8e61fa7..eed34ad 100644 --- a/packages/server/src/games/game-manager.ts +++ b/packages/server/src/games/game-manager.ts @@ -232,21 +232,59 @@ export class GameManager { return result } - getGameStateForPlayer(playerId: string, allPlayerIds: string[]): GameState { + getGameStateForPlayer( + playerId: string, + allPlayerIds: string[], + displayNames?: Record, + ): GameState { return { lineup, myPrediction: this.getPrediction(playerId), predictionsLocked: this.locked, predictionSubmitted: this.buildPredictionSubmitted(allPlayerIds), + currentJuryRound: this.getCurrentJuryRound(), + juryResults: this.juryResults, + myJuryVote: this.getPlayerJuryVote(playerId), + myBingoCard: this.getBingoCard(playerId), + bingoAnnouncements: this.bingoAnnouncements, + leaderboard: this.buildLeaderboard(allPlayerIds, displayNames ?? {}), } } - getGameStateForDisplay(allPlayerIds: string[]): GameState { + getGameStateForDisplay( + allPlayerIds: string[], + displayNames?: Record, + ): GameState { return { lineup, myPrediction: null, predictionsLocked: this.locked, predictionSubmitted: this.buildPredictionSubmitted(allPlayerIds), + currentJuryRound: this.getCurrentJuryRound(), + juryResults: this.juryResults, + myJuryVote: null, + myBingoCard: null, + bingoAnnouncements: this.bingoAnnouncements, + leaderboard: this.buildLeaderboard(allPlayerIds, displayNames ?? {}), } } + + private buildLeaderboard( + playerIds: string[], + displayNames: Record, + ): { playerId: string; displayName: string; juryPoints: number; bingoPoints: number; totalPoints: number }[] { + return playerIds + .map((id) => { + const juryPoints = this.getJuryScore(id) + const bingoPoints = this.getBingoScore(id) + return { + playerId: id, + displayName: displayNames[id] ?? "Unknown", + juryPoints, + bingoPoints, + totalPoints: juryPoints + bingoPoints, + } + }) + .sort((a, b) => b.totalPoints - a.totalPoints) + } } diff --git a/packages/server/tests/game-manager.test.ts b/packages/server/tests/game-manager.test.ts index a9627a7..f0c3890 100644 --- a/packages/server/tests/game-manager.test.ts +++ b/packages/server/tests/game-manager.test.ts @@ -305,6 +305,40 @@ describe("GameManager", () => { }) }) + describe("getGameStateForPlayer (with jury + bingo)", () => { + it("includes jury round state", () => { + gm.openJuryRound("SE", "Sweden", "πŸ‡ΈπŸ‡ͺ") + gm.submitJuryVote("p1", 8) + const state = gm.getGameStateForPlayer("p1", ["p1"]) + expect(state.currentJuryRound).not.toBeNull() + expect(state.currentJuryRound!.countryCode).toBe("SE") + expect(state.myJuryVote).toBe(8) + }) + + it("includes bingo card", () => { + gm.generateBingoCards(["p1"]) + const state = gm.getGameStateForPlayer("p1", ["p1"]) + expect(state.myBingoCard).not.toBeNull() + expect(state.myBingoCard!.squares).toHaveLength(16) + }) + + it("includes leaderboard with jury + bingo scores", () => { + gm.generateBingoCards(["p1"]) + gm.openJuryRound("SE", "Sweden", "πŸ‡ΈπŸ‡ͺ") + gm.submitJuryVote("p1", 8) + gm.closeJuryRound() + + const card = gm.getBingoCard("p1")! + gm.tapBingoSquare("p1", card.squares[0]!.tropeId) + + const state = gm.getGameStateForPlayer("p1", ["p1"], { p1: "Player 1" }) + expect(state.leaderboard).toHaveLength(1) + expect(state.leaderboard[0]!.juryPoints).toBe(5) // solo voter = exact match + expect(state.leaderboard[0]!.bingoPoints).toBe(2) // 1 tapped * 2 + expect(state.leaderboard[0]!.totalPoints).toBe(7) + }) + }) + describe("getGameStateForDisplay", () => { it("returns null myPrediction", () => { gm.submitPrediction("p1", "SE", "DE", "IT", "GB")