From 7c80e9b9c0ec19b3de979b74f3027b95409460c0 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:57:20 +1100 Subject: [PATCH] added win streaks, Maria will be happy :) --- broadcast.ts | 16 +++++++++++++--- frontend.css | 17 +++++++++++++++++ frontend.tsx | 23 ++++++++++++++++++++--- game.ts | 8 ++++++++ quipslop.tsx | 11 ++++++----- server.ts | 19 +++++++++++++++---- 6 files changed, 79 insertions(+), 15 deletions(-) diff --git a/broadcast.ts b/broadcast.ts index 9fedb94..9222cac 100644 --- a/broadcast.ts +++ b/broadcast.ts @@ -29,6 +29,7 @@ type GameState = { lastCompleted: RoundState | null; active: RoundState | null; scores: Record; + streaks: Record; done: boolean; isPaused: boolean; generation: number; @@ -291,9 +292,9 @@ function drawHeader() { } -function drawScoreboard(scores: Record) { +function drawScoreboard(scores: Record, streaks: Record) { const entries = Object.entries(scores).sort((a, b) => b[1] - a[1]); - + roundRect(WIDTH - 380, 0, 380, HEIGHT, 0, "#111"); ctx.fillStyle = "#1c1c1c"; ctx.fillRect(WIDTH - 380, 0, 1, HEIGHT); @@ -335,6 +336,15 @@ function drawScoreboard(scores: Record) { const scoreText = String(score); const scoreWidth = ctx.measureText(scoreText).width; ctx.fillText(scoreText, WIDTH - 48 - scoreWidth, y + 24); + + const streak = streaks[name] || 0; + if (streak >= 2) { + ctx.font = '600 16px "Inter", sans-serif'; + ctx.fillStyle = "#f59e0b"; + const streakText = `🔥${streak}`; + const streakWidth = ctx.measureText(streakText).width; + ctx.fillText(streakText, WIDTH - 48 - scoreWidth - streakWidth - 8, y + 24); + } }); } @@ -564,7 +574,7 @@ function draw() { return; } - drawScoreboard(state.scores); + drawScoreboard(state.scores, state.streaks); const isNextPrompting = state.active?.phase === "prompting" && !state.active.prompt; const displayRound = isNextPrompting && state.lastCompleted ? state.lastCompleted : (state.active ?? state.lastCompleted ?? null); diff --git a/frontend.css b/frontend.css index ed778c3..6a111c0 100644 --- a/frontend.css +++ b/frontend.css @@ -220,6 +220,15 @@ body { flex-shrink: 0; } +.streak-badge { + font-family: var(--mono); + font-size: 11px; + font-weight: 700; + padding: 2px 6px; + color: #f59e0b; + margin-left: auto; +} + .win-tag { font-family: var(--mono); font-size: 10px; @@ -419,6 +428,14 @@ body { transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1); } +.standing__streak { + font-size: 11px; + font-weight: 700; + color: #f59e0b; + margin-left: auto; + flex-shrink: 0; +} + .standing__score { font-family: var(--mono); font-size: 12px; diff --git a/frontend.tsx b/frontend.tsx index 9357f83..066ab62 100644 --- a/frontend.tsx +++ b/frontend.tsx @@ -35,6 +35,7 @@ type GameState = { lastCompleted: RoundState | null; active: RoundState | null; scores: Record; + streaks: Record; done: boolean; isPaused: boolean; generation: number; @@ -155,6 +156,7 @@ function ContestantCard({ isWinner, showVotes, voters, + streak, }: { task: TaskInfo; voteCount: number; @@ -162,6 +164,7 @@ function ContestantCard({ isWinner: boolean; showVotes: boolean; voters: VoteInfo[]; + streak: number; }) { const color = getColor(task.model.name); const pct = totalVotes > 0 ? Math.round((voteCount / totalVotes) * 100) : 0; @@ -173,6 +176,11 @@ function ContestantCard({ >
+ {streak >= 2 && ( + + 🔥 {streak} + + )} {isWinner && WIN}
@@ -235,7 +243,7 @@ function ContestantCard({ // ── Arena ───────────────────────────────────────────────────────────────────── -function Arena({ round, total }: { round: RoundState; total: number | null }) { +function Arena({ round, total, streaks }: { round: RoundState; total: number | null; streaks: Record }) { const [contA, contB] = round.contestants; const showVotes = round.phase === "voting" || round.phase === "done"; const isDone = round.phase === "done"; @@ -280,6 +288,7 @@ function Arena({ round, total }: { round: RoundState; total: number | null }) { isWinner={isDone && votesA > votesB} showVotes={showVotes} voters={votersA} + streak={streaks[contA.name] || 0} /> votesA} showVotes={showVotes} voters={votersB} + streak={streaks[contB.name] || 0} /> )} @@ -329,9 +339,11 @@ function GameOver({ scores }: { scores: Record }) { function Standings({ scores, + streaks, activeRound, }: { scores: Record; + streaks: Record; activeRound: RoundState | null; }) { const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]); @@ -380,6 +392,11 @@ function Standings({ style={{ width: `${pct}%`, background: color }} /> + {(streaks[name] || 0) >= 2 && ( + + 🔥{streaks[name]} + + )} {score} ); @@ -484,7 +501,7 @@ function App() { {state.done ? ( ) : displayRound ? ( - + ) : (
Starting @@ -501,7 +518,7 @@ function App() { )} - +
); diff --git a/game.ts b/game.ts index 87d10b7..89153ff 100644 --- a/game.ts +++ b/game.ts @@ -70,6 +70,7 @@ export type GameState = { completed: RoundState[]; active: RoundState | null; scores: Record; + streaks: Record; done: boolean; isPaused: boolean; generation: number; @@ -453,8 +454,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; } rerender(); diff --git a/quipslop.tsx b/quipslop.tsx index 3d0fdc8..9bd57ad 100644 --- a/quipslop.tsx +++ b/quipslop.tsx @@ -223,11 +223,12 @@ function Game({ runs }: { runs: number }) { const stateRef = useRef({ completed: [], active: null, - scores: Object.fromEntries(MODELS.map((m) => [m.name, 0])), - done: false, - isPaused: false, - generation: 0, - }); + scores: Object.fromEntries(MODELS.map((m) => [m.name, 0])), + streaks: {}, + done: false, + isPaused: false, + generation: 0, + }); const [, setTick] = useState(0); const rerender = useCallback(() => setTick((t) => t + 1), []); diff --git a/server.ts b/server.ts index 6a4f9fe..a87c820 100644 --- a/server.ts +++ b/server.ts @@ -30,17 +30,25 @@ if (!process.env.OPENROUTER_API_KEY) { const allRounds = getAllRounds(); const initialScores = Object.fromEntries(MODELS.map((m) => [m.name, 0])); +const initialStreaks: Record = {}; 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; } } } @@ -54,6 +62,7 @@ const gameState: GameState = { completed: initialCompleted, active: null, scores: initialScores, + streaks: initialStreaks, done: false, isPaused: false, generation: 0, @@ -224,6 +233,7 @@ function getClientState() { active: gameState.active, lastCompleted: gameState.completed.at(-1) ?? null, scores: gameState.scores, + streaks: gameState.streaks, done: gameState.done, isPaused: gameState.isPaused, generation: gameState.generation, @@ -434,6 +444,7 @@ const server = Bun.serve({ gameState.completed = []; gameState.active = null; gameState.scores = Object.fromEntries(MODELS.map((m) => [m.name, 0])); + gameState.streaks = {}; gameState.done = false; gameState.isPaused = true; gameState.generation += 1;