From 3cd82dd96639f8c4a8bff7207ce4c33439fbfb47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:09:14 +0000 Subject: [PATCH 1/4] Initial plan From eb4192d8e126703cae5a8356db20bd1897dccafb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:13:34 +0000 Subject: [PATCH 2/4] Support superpowers: detect (+) marker, award 20 points Co-authored-by: geoffrey-wu <42471355+geoffrey-wu@users.noreply.github.com> --- client/play/TossupClient.js | 2 +- client/play/tossups/SoloTossupClient.js | 2 +- client/play/tossups/SoloTossupRoom.js | 5 ++++- quizbowl/TossupRoom.js | 16 ++++++++++++---- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/client/play/TossupClient.js b/client/play/TossupClient.js index f3e2311ed..bbe626a04 100644 --- a/client/play/TossupClient.js +++ b/client/play/TossupClient.js @@ -92,7 +92,7 @@ export const TossupClientMixin = (ClientClass) => class extends ClientClass { } updateQuestion ({ word }) { - if (word === '(*)' || word === '[*]') { return; } + if (word === '(*)' || word === '[*]' || word === '(+)') { return; } document.getElementById('question').innerHTML += word + ' '; } }; diff --git a/client/play/tossups/SoloTossupClient.js b/client/play/tossups/SoloTossupClient.js index 1f8ee763a..830b6f714 100644 --- a/client/play/tossups/SoloTossupClient.js +++ b/client/play/tossups/SoloTossupClient.js @@ -46,7 +46,7 @@ export default class SoloTossupClient extends TossupClient { super.endCurrentTossup({ starred, tossup }); if (!isSkip && this.room.previousTossup.userId === this.USER_ID && (this.room.mode !== MODE_ENUM.LOCAL)) { const previous = this.room.previousTossup; - const pointValue = previous.isCorrect ? (previous.inPower ? previous.powerValue : 10) : (previous.endOfQuestion ? 0 : previous.negValue); + const pointValue = previous.isCorrect ? (previous.inSuperpower ? previous.superpowerValue : (previous.inPower ? previous.powerValue : 10)) : (previous.endOfQuestion ? 0 : previous.negValue); questionStats.recordTossup({ _id: previous.tossup._id, celerity: previous.celerity, diff --git a/client/play/tossups/SoloTossupRoom.js b/client/play/tossups/SoloTossupRoom.js index 43c82d30e..0e871d21f 100644 --- a/client/play/tossups/SoloTossupRoom.js +++ b/client/play/tossups/SoloTossupRoom.js @@ -88,7 +88,10 @@ export default class SoloTossupRoom extends TossupRoom { this.previousTossup.isCorrect = correct; const multiplier = correct ? 1 : -1; - if (this.previousTossup.inPower) { + if (this.previousTossup.inSuperpower) { + this.players[userId].powers += multiplier * 1; + this.players[userId].points += multiplier * this.previousTossup.superpowerValue; + } else if (this.previousTossup.inPower) { this.players[userId].powers += multiplier * 1; this.players[userId].points += multiplier * this.previousTossup.powerValue; } else { diff --git a/quizbowl/TossupRoom.js b/quizbowl/TossupRoom.js index 25e012c23..e38ecbb45 100644 --- a/quizbowl/TossupRoom.js +++ b/quizbowl/TossupRoom.js @@ -38,8 +38,10 @@ export const TossupRoomMixin = (QuestionRoomClass) => class extends QuestionRoom endOfQuestion: false, isCorrect: true, inPower: false, + inSuperpower: false, negValue: -5, powerValue: 15, + superpowerValue: 20, tossup: {}, userId: null }; @@ -209,7 +211,7 @@ export const TossupRoomMixin = (QuestionRoomClass) => class extends QuestionRoom time += 2.5; } else if (word.endsWith(',') || word.slice(-2) === ',\u201d') { time += 1.5; - } else if (word === '(*)' || word === '[*]') { + } else if (word === '(*)' || word === '[*]' || word === '(+)') { time = 0; } @@ -235,22 +237,28 @@ export const TossupRoomMixin = (QuestionRoomClass) => class extends QuestionRoom scoreTossup ({ givenAnswer }) { const celerity = this.questionSplit.slice(this.wordIndex).join(' ').length / this.tossup.question.length; const endOfQuestion = (this.wordIndex === this.questionSplit.length); - const inPower = Math.max(this.questionSplit.indexOf('(*)'), this.questionSplit.indexOf('[*]')) >= this.wordIndex; + const superpowerIndex = this.questionSplit.indexOf('(+)'); + const powerIndex = Math.max(this.questionSplit.indexOf('(*)'), this.questionSplit.indexOf('[*]')); + const inSuperpower = superpowerIndex !== -1 && superpowerIndex >= this.wordIndex; + const inPower = !inSuperpower && powerIndex !== -1 && powerIndex >= this.wordIndex; const { directive, directedPrompt } = this.checkAnswer(this.tossup.answer, givenAnswer, this.settings.strictness); const isCorrect = directive === 'accept'; - const points = isCorrect ? (inPower ? this.previousTossup.powerValue : 10) : (endOfQuestion ? 0 : this.previousTossup.negValue); + const points = isCorrect + ? (inSuperpower ? this.previousTossup.superpowerValue : (inPower ? this.previousTossup.powerValue : 10)) + : (endOfQuestion ? 0 : this.previousTossup.negValue); this.previousTossup = { ...this.previousTossup, celerity, endOfQuestion, inPower, + inSuperpower, isCorrect, tossup: this.tossup, userId: this.buzzedIn }; - return { celerity, directive, directedPrompt, endOfQuestion, inPower, points }; + return { celerity, directive, directedPrompt, endOfQuestion, inPower, inSuperpower, points }; } setReadingSpeed (userId, { readingSpeed }) { From 401d77dfb3616a75d4675aa1b930f2718db9dd3f Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Mon, 9 Mar 2026 00:28:03 -0400 Subject: [PATCH 3/4] finish implementation of superpowers --- client/play/tossups/SoloTossupClient.js | 12 +++++++----- client/play/tossups/SoloTossupRoom.js | 5 +++-- client/play/upsert-player-item.js | 3 ++- quizbowl/Player.js | 11 ++++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/client/play/tossups/SoloTossupClient.js b/client/play/tossups/SoloTossupClient.js index 830b6f714..9a19fc058 100644 --- a/client/play/tossups/SoloTossupClient.js +++ b/client/play/tossups/SoloTossupClient.js @@ -46,7 +46,9 @@ export default class SoloTossupClient extends TossupClient { super.endCurrentTossup({ starred, tossup }); if (!isSkip && this.room.previousTossup.userId === this.USER_ID && (this.room.mode !== MODE_ENUM.LOCAL)) { const previous = this.room.previousTossup; - const pointValue = previous.isCorrect ? (previous.inSuperpower ? previous.superpowerValue : (previous.inPower ? previous.powerValue : 10)) : (previous.endOfQuestion ? 0 : previous.negValue); + const pointValue = previous.isCorrect + ? (previous.inSuperpower ? previous.superpowerValue : (previous.inPower ? previous.powerValue : 10)) + : (previous.endOfQuestion ? 0 : previous.negValue); questionStats.recordTossup({ _id: previous.tossup._id, celerity: previous.celerity, @@ -203,12 +205,12 @@ export default class SoloTossupClient extends TossupClient { } /** - * Updates the displayed stat line. - */ - updateStatDisplay ({ powers, tens, negs, tuh, points, celerity }) { + * Updates the displayed stat line. + */ + updateStatDisplay ({ superpowers, powers, tens, negs, tuh, points, celerity }) { const averageCelerity = celerity.correct.average.toFixed(3); const plural = (tuh === 1) ? '' : 's'; - document.getElementById('statline').innerHTML = `${powers}/${tens}/${negs} with ${tuh} tossup${plural} seen (${points} pts, celerity: ${averageCelerity})`; + document.getElementById('statline').innerHTML = `${superpowers}/${powers}/${tens}/${negs} with ${tuh} tossup${plural} seen (${points} pts, celerity: ${averageCelerity})`; // disable clear stats button if no stats document.getElementById('clear-stats').disabled = (tuh === 0); diff --git a/client/play/tossups/SoloTossupRoom.js b/client/play/tossups/SoloTossupRoom.js index 0e871d21f..9270b96cc 100644 --- a/client/play/tossups/SoloTossupRoom.js +++ b/client/play/tossups/SoloTossupRoom.js @@ -89,7 +89,7 @@ export default class SoloTossupRoom extends TossupRoom { const multiplier = correct ? 1 : -1; if (this.previousTossup.inSuperpower) { - this.players[userId].powers += multiplier * 1; + this.players[userId].superpowers += multiplier * 1; this.players[userId].points += multiplier * this.previousTossup.superpowerValue; } else if (this.previousTossup.inPower) { this.players[userId].powers += multiplier * 1; @@ -106,8 +106,9 @@ export default class SoloTossupRoom extends TossupRoom { this.players[userId].points += multiplier * -this.previousTossup.negValue; } + const correctBuzzes = this.players[userId].superpowers + this.players[userId].powers + this.players[userId].tens; this.players[userId].celerity.correct.total += multiplier * this.previousTossup.celerity; - this.players[userId].celerity.correct.average = this.players[userId].celerity.correct.total / (this.players[userId].powers + this.players[userId].tens); + this.players[userId].celerity.correct.average = this.players[userId].celerity.correct.total / correctBuzzes; this.emitMessage({ type: 'toggle-correct', correct, userId }); } diff --git a/client/play/upsert-player-item.js b/client/play/upsert-player-item.js index fa89df35a..2b645f679 100644 --- a/client/play/upsert-player-item.js +++ b/client/play/upsert-player-item.js @@ -32,7 +32,7 @@ export default function upsertPlayerItem (player, multiplayerOptions = {}) { player.userId = escapeHTML(player.userId); player.username = escapeHTML(player.username); - const { userId, username, powers = 0, tens = 0, negs = 0, tuh = 0, points = 0, online } = player; + const { userId, username, superpowers = 0, powers = 0, tens = 0, negs = 0, tuh = 0, points = 0, online } = player; const celerity = player?.celerity?.correct?.average ?? player?.celerity ?? 0; const { bonusStats = { 0: 0, 10: 0, 20: 0, 30: 0 } } = team; @@ -75,6 +75,7 @@ export default function upsertPlayerItem (player, multiplayerOptions = {}) { playerItem.setAttribute('data-bs-title', username); playerItem.setAttribute('data-bs-content', `