feat: add viewer voting on user answers with leaderboard scoring
Viewers can now vote for their favourite audience answers during the 30-second voting window. Votes are persisted to the DB at round end and aggregated as SUM(votes) in the JUGADORES leaderboard. - db.ts: add persistUserAnswerVotes(); switch getPlayerScores() to SUM(votes) - game.ts: add userAnswerVotes to RoundState; persist votes before saveRound - server.ts: add userAnswerVoters map + /api/vote/respuesta endpoint - frontend.tsx: add userAnswerVotes type; vote state/handler in App; ▲ buttons in Arena - frontend.css: flex layout for user-answer rows; user-vote-btn styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
24
db.ts
24
db.ts
@@ -95,16 +95,28 @@ export function markQuestionUsed(id: number): void {
|
||||
db.prepare("UPDATE questions SET status = 'used' WHERE id = $id").run({ $id: id });
|
||||
}
|
||||
|
||||
/** Top 7 players by number of answers submitted, excluding anonymous. */
|
||||
/** Top 7 players by total votes received on their answers, excluding anonymous. */
|
||||
export function getPlayerScores(): Record<string, number> {
|
||||
const rows = db
|
||||
.query(
|
||||
"SELECT username, COUNT(*) as score FROM user_answers WHERE username != '' GROUP BY username ORDER BY score DESC LIMIT 7"
|
||||
"SELECT username, SUM(votes) as score FROM user_answers WHERE username != '' GROUP BY username ORDER BY score DESC LIMIT 7"
|
||||
)
|
||||
.all() as { username: string; score: number }[];
|
||||
return Object.fromEntries(rows.map(r => [r.username, r.score]));
|
||||
}
|
||||
|
||||
/** Persist accumulated vote counts for user answers in a given round. */
|
||||
export function persistUserAnswerVotes(roundNum: number, votes: Record<string, number>): void {
|
||||
const stmt = db.prepare(
|
||||
"UPDATE user_answers SET votes = $votes WHERE round_num = $roundNum AND username = $username"
|
||||
);
|
||||
db.transaction(() => {
|
||||
for (const [username, voteCount] of Object.entries(votes)) {
|
||||
stmt.run({ $votes: voteCount, $roundNum: roundNum, $username: username });
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// ── User answers (submitted during live rounds) ──────────────────────────────
|
||||
|
||||
db.exec(`
|
||||
@@ -114,10 +126,18 @@ db.exec(`
|
||||
text TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
votes INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// Migration: add votes column to pre-existing user_answers tables
|
||||
try {
|
||||
db.exec("ALTER TABLE user_answers ADD COLUMN votes INTEGER NOT NULL DEFAULT 0");
|
||||
} catch {
|
||||
// Column already exists — no-op
|
||||
}
|
||||
|
||||
// ── Credits (answer-count-based access) ──────────────────────────────────────
|
||||
|
||||
db.exec(`
|
||||
|
||||
Reference in New Issue
Block a user