add prediction scoring to GameManager with tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:14:28 +01:00
parent 44ea815f95
commit c7a11e80d3
2 changed files with 128 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ const countryCodes = new Set(lineup.entries.map((e) => e.country.code))
export class GameManager {
private predictions = new Map<string, Prediction>() // playerId → prediction
private locked = false
private actualResults: { winner: string; second: string; third: string; last: string } | null = null
getLineup(): Lineup {
return lineup
@@ -222,6 +223,29 @@ export class GameManager {
return score
}
// ─── Prediction Scoring ─────────────────────────────────────────
setActualResults(winner: string, second: string, third: string, last: string): void {
this.actualResults = { winner, second, third, last }
}
getActualResults(): { winner: string; second: string; third: string; last: string } | null {
return this.actualResults
}
getPredictionScore(playerId: string): number {
if (!this.actualResults) return 0
const prediction = this.predictions.get(playerId)
if (!prediction) return 0
let score = 0
if (prediction.first === this.actualResults.winner) score += scoringConfig.prediction_winner
if (prediction.second === this.actualResults.second) score += scoringConfig.prediction_top3
if (prediction.third === this.actualResults.third) score += scoringConfig.prediction_top3
if (prediction.last === this.actualResults.last) score += scoringConfig.prediction_nul_points
return score
}
// ─── State for client ───────────────────────────────────────────
private buildPredictionSubmitted(playerIds: string[]): Record<string, boolean> {
@@ -247,6 +271,7 @@ export class GameManager {
myJuryVote: this.getPlayerJuryVote(playerId),
myBingoCard: this.getBingoCard(playerId),
bingoAnnouncements: this.bingoAnnouncements,
actualResults: this.actualResults,
leaderboard: this.buildLeaderboard(allPlayerIds, displayNames ?? {}),
}
}
@@ -265,6 +290,7 @@ export class GameManager {
myJuryVote: null,
myBingoCard: null,
bingoAnnouncements: this.bingoAnnouncements,
actualResults: this.actualResults,
leaderboard: this.buildLeaderboard(allPlayerIds, displayNames ?? {}),
}
}
@@ -272,17 +298,19 @@ export class GameManager {
private buildLeaderboard(
playerIds: string[],
displayNames: Record<string, string>,
): { playerId: string; displayName: string; juryPoints: number; bingoPoints: number; totalPoints: number }[] {
): { playerId: string; displayName: string; juryPoints: number; bingoPoints: number; predictionPoints: number; totalPoints: number }[] {
return playerIds
.map((id) => {
const juryPoints = this.getJuryScore(id)
const bingoPoints = this.getBingoScore(id)
const predictionPoints = this.getPredictionScore(id)
return {
playerId: id,
displayName: displayNames[id] ?? "Unknown",
juryPoints,
bingoPoints,
totalPoints: juryPoints + bingoPoints,
predictionPoints,
totalPoints: juryPoints + bingoPoints + predictionPoints,
}
})
.sort((a, b) => b.totalPoints - a.totalPoints)

View File

@@ -339,6 +339,104 @@ describe("GameManager", () => {
})
})
describe("prediction scoring", () => {
it("returns 0 for all when no actual results set", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "SE", "IT", "FR", "GB")
expect(gm.getPredictionScore("p1")).toBe(0)
})
it("scores correct winner", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "SE", "IT", "FR", "GB")
gm.setActualResults("SE", "CH", "DE", "AL")
expect(gm.getPredictionScore("p1")).toBe(25)
})
it("scores correct second place", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "NO", "IT", "FR", "GB")
gm.setActualResults("SE", "IT", "DE", "AL")
expect(gm.getPredictionScore("p1")).toBe(10)
})
it("scores correct third place", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "NO", "DK", "FR", "GB")
gm.setActualResults("SE", "IT", "FR", "AL")
expect(gm.getPredictionScore("p1")).toBe(10)
})
it("scores correct last place", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "NO", "DK", "FI", "GB")
gm.setActualResults("SE", "IT", "FR", "GB")
expect(gm.getPredictionScore("p1")).toBe(15)
})
it("scores perfect prediction (all correct)", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "SE", "IT", "FR", "GB")
gm.setActualResults("SE", "IT", "FR", "GB")
expect(gm.getPredictionScore("p1")).toBe(60)
})
it("scores 0 for all wrong", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "NO", "DK", "FI", "EE")
gm.setActualResults("SE", "IT", "FR", "GB")
expect(gm.getPredictionScore("p1")).toBe(0)
})
it("returns 0 for player with no prediction", () => {
const gm = new GameManager()
gm.setActualResults("SE", "IT", "FR", "GB")
expect(gm.getPredictionScore("p1")).toBe(0)
})
it("getActualResults returns null before setting", () => {
const gm = new GameManager()
expect(gm.getActualResults()).toBeNull()
})
it("getActualResults returns results after setting", () => {
const gm = new GameManager()
gm.setActualResults("SE", "IT", "FR", "GB")
expect(gm.getActualResults()).toEqual({ winner: "SE", second: "IT", third: "FR", last: "GB" })
})
it("setActualResults overwrites previous results", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "SE", "IT", "FR", "GB")
gm.setActualResults("NO", "DK", "FI", "EE")
expect(gm.getPredictionScore("p1")).toBe(0)
gm.setActualResults("SE", "IT", "FR", "GB")
expect(gm.getPredictionScore("p1")).toBe(60)
})
it("prediction points appear in leaderboard", () => {
const gm = new GameManager()
gm.submitPrediction("p1", "SE", "IT", "FR", "GB")
gm.setActualResults("SE", "IT", "FR", "GB")
const state = gm.getGameStateForPlayer("p1", ["p1"], { p1: "Alice" })
expect(state.leaderboard[0]!.predictionPoints).toBe(60)
expect(state.leaderboard[0]!.totalPoints).toBe(60)
})
it("actualResults included in game state", () => {
const gm = new GameManager()
gm.setActualResults("SE", "IT", "FR", "GB")
const state = gm.getGameStateForPlayer("p1", ["p1"], { p1: "Alice" })
expect(state.actualResults).toEqual({ winner: "SE", second: "IT", third: "FR", last: "GB" })
})
it("actualResults null in game state when not set", () => {
const gm = new GameManager()
const state = gm.getGameStateForPlayer("p1", ["p1"], { p1: "Alice" })
expect(state.actualResults).toBeNull()
})
})
describe("getGameStateForDisplay", () => {
it("returns null myPrediction", () => {
gm.submitPrediction("p1", "SE", "DE", "IT", "GB")