diff --git a/broadcast.ts b/broadcast.ts index a76f2c7..0c26cb2 100644 --- a/broadcast.ts +++ b/broadcast.ts @@ -32,6 +32,7 @@ type GameState = { lastCompleted: RoundState | null; active: RoundState | null; scores: Record; + streaks: Record; viewerScores: Record; done: boolean; isPaused: boolean; @@ -300,6 +301,7 @@ function drawScoreboardSection( label: string, startY: number, entryHeight: number, + streaks?: Record, ) { const maxScore = entries[0]?.[1] || 1; @@ -343,10 +345,19 @@ function drawScoreboardSection( const scoreText = String(score); const scoreWidth = ctx.measureText(scoreText).width; ctx.fillText(scoreText, WIDTH - 48 - scoreWidth, y + 18); + + const streak = streaks?.[name] || 0; + if (streak >= 2) { + ctx.font = '600 14px "Inter", sans-serif'; + ctx.fillStyle = "#f59e0b"; + const streakText = `🔥${streak}`; + const streakWidth = ctx.measureText(streakText).width; + ctx.fillText(streakText, WIDTH - 48 - scoreWidth - streakWidth - 8, y + 18); + } }); } -function drawScoreboard(scores: Record, viewerScores: Record) { +function drawScoreboard(scores: Record, viewerScores: Record, streaks: Record) { const modelEntries = Object.entries(scores).sort((a, b) => b[1] - a[1]) as [string, number][]; const viewerEntries = Object.entries(viewerScores).sort((a, b) => b[1] - a[1]) as [string, number][]; @@ -359,7 +370,7 @@ function drawScoreboard(scores: Record, viewerScores: Record; + streaks: Record; viewerScores: Record; done: boolean; isPaused: boolean; @@ -159,6 +160,7 @@ function ContestantCard({ isWinner, showVotes, voters, + streak, viewerVotes, totalViewerVotes, }: { @@ -168,6 +170,7 @@ function ContestantCard({ isWinner: boolean; showVotes: boolean; voters: VoteInfo[]; + streak: number; viewerVotes?: number; totalViewerVotes?: number; }) { @@ -185,6 +188,11 @@ function ContestantCard({ >
+ {streak >= 2 && ( + + 🔥 {streak} + + )} {isWinner && WIN}
@@ -269,10 +277,12 @@ function ContestantCard({ function Arena({ round, total, + streaks, viewerVotingSecondsLeft, }: { round: RoundState; total: number | null; + streaks: Record; viewerVotingSecondsLeft: number; }) { const [contA, contB] = round.contestants; @@ -332,6 +342,7 @@ function Arena({ isWinner={isDone && votesA > votesB} showVotes={showVotes} voters={votersA} + streak={streaks[contA.name] || 0} viewerVotes={round.viewerVotesA} totalViewerVotes={totalViewerVotes} /> @@ -342,6 +353,7 @@ function Arena({ isWinner={isDone && votesB > votesA} showVotes={showVotes} voters={votersB} + streak={streaks[contB.name] || 0} viewerVotes={round.viewerVotesB} totalViewerVotes={totalViewerVotes} /> @@ -386,10 +398,12 @@ function GameOver({ scores }: { scores: Record }) { function LeaderboardSection({ label, scores, + streaks, competing, }: { label: string; scores: Record; + streaks?: Record; competing: Set; }) { const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]); @@ -415,6 +429,9 @@ function LeaderboardSection({ {i === 0 && score > 0 ? "👑" : i + 1} + {(streaks?.[name] || 0) >= 2 && ( + 🔥{streaks![name]} + )} {score}
@@ -434,10 +451,12 @@ function LeaderboardSection({ function Standings({ scores, viewerScores, + streaks, activeRound, }: { scores: Record; viewerScores: Record; + streaks: Record; activeRound: RoundState | null; }) { const competing = activeRound @@ -466,6 +485,7 @@ function Standings({ ) : ( @@ -611,7 +632,7 @@ function App() { )} - +
); diff --git a/game.ts b/game.ts index 044e3f9..d7c6aed 100644 --- a/game.ts +++ b/game.ts @@ -73,6 +73,7 @@ export type GameState = { completed: RoundState[]; active: RoundState | null; scores: Record; + streaks: Record; viewerScores: Record; done: boolean; isPaused: boolean; @@ -469,8 +470,15 @@ export async function runGame( round.phase = "done"; if (votesA > votesB) { state.scores[contA.name] = (state.scores[contA.name] || 0) + 1; + state.streaks[contA.name] = (state.streaks[contA.name] || 0) + 1; + state.streaks[contB.name] = 0; } else if (votesB > votesA) { state.scores[contB.name] = (state.scores[contB.name] || 0) + 1; + state.streaks[contB.name] = (state.streaks[contB.name] || 0) + 1; + state.streaks[contA.name] = 0; + } else { + state.streaks[contA.name] = 0; + state.streaks[contB.name] = 0; } // Viewer vote scoring const vvA = round.viewerVotesA ?? 0; diff --git a/quipslop.tsx b/quipslop.tsx index 31008d8..8bcaaec 100644 --- a/quipslop.tsx +++ b/quipslop.tsx @@ -224,6 +224,7 @@ function Game({ runs }: { runs: number }) { completed: [], active: null, scores: Object.fromEntries(MODELS.map((m) => [m.name, 0])), + streaks: {}, viewerScores: Object.fromEntries(MODELS.map((m) => [m.name, 0])), done: false, isPaused: false, diff --git a/server.ts b/server.ts index 7f26f0f..bcc35fb 100644 --- a/server.ts +++ b/server.ts @@ -30,18 +30,26 @@ if (!process.env.OPENROUTER_API_KEY) { const allRounds = getAllRounds(); const initialScores = Object.fromEntries(MODELS.map((m) => [m.name, 0])); +const initialStreaks: Record = {}; const initialViewerScores = Object.fromEntries(MODELS.map((m) => [m.name, 0])); let initialCompleted: RoundState[] = []; if (allRounds.length > 0) { for (const round of allRounds) { if (round.scoreA !== undefined && round.scoreB !== undefined) { + const contAName = round.contestants[0].name; + const contBName = round.contestants[1].name; if (round.scoreA > round.scoreB) { - initialScores[round.contestants[0].name] = - (initialScores[round.contestants[0].name] || 0) + 1; + initialScores[contAName] = (initialScores[contAName] || 0) + 1; + initialStreaks[contAName] = (initialStreaks[contAName] || 0) + 1; + initialStreaks[contBName] = 0; } else if (round.scoreB > round.scoreA) { - initialScores[round.contestants[1].name] = - (initialScores[round.contestants[1].name] || 0) + 1; + initialScores[contBName] = (initialScores[contBName] || 0) + 1; + initialStreaks[contBName] = (initialStreaks[contBName] || 0) + 1; + initialStreaks[contAName] = 0; + } else { + initialStreaks[contAName] = 0; + initialStreaks[contBName] = 0; } } const vvA = round.viewerVotesA ?? 0; @@ -64,6 +72,7 @@ const gameState: GameState = { completed: initialCompleted, active: null, scores: initialScores, + streaks: initialStreaks, viewerScores: initialViewerScores, done: false, isPaused: false, @@ -360,6 +369,7 @@ function getClientState() { active: gameState.active, lastCompleted: gameState.completed.at(-1) ?? null, scores: gameState.scores, + streaks: gameState.streaks, viewerScores: gameState.viewerScores, done: gameState.done, isPaused: gameState.isPaused, @@ -647,6 +657,7 @@ const server = Bun.serve({ gameState.completed = []; gameState.active = null; gameState.scores = Object.fromEntries(MODELS.map((m) => [m.name, 0])); + gameState.streaks = {}; gameState.viewerScores = Object.fromEntries(MODELS.map((m) => [m.name, 0])); gameState.done = false; gameState.isPaused = true;