From f393c2a7f76b25262860f6029e3787907794a582 Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Wed, 21 Oct 2026 00:07:26 -0400 Subject: [PATCH 1/2] Record a geoword buzz when check-answer gives a final verdict The server now saves the buzz itself on an accept or reject, with isCorrect from its own verdict, instead of trusting a separate record-buzz request. A prompt records nothing; the earlier prompts are sent with the final answer. The /record-buzz route is removed. Co-Authored-By: Claude Sonnet 5.5 --- client/play/geoword/game/index.js | 40 ++++++-------------- routes/api/geoword/paid/play/check-answer.js | 17 ++++++++- routes/api/geoword/paid/play/index.js | 6 +-- routes/api/geoword/paid/play/record-buzz.js | 27 ------------- 4 files changed, 30 insertions(+), 60 deletions(-) delete mode 100644 routes/api/geoword/paid/play/record-buzz.js diff --git a/client/play/geoword/game/index.js b/client/play/geoword/game/index.js index 1e5cbcd4f..829bb6229 100644 --- a/client/play/geoword/game/index.js +++ b/client/play/geoword/game/index.js @@ -50,26 +50,29 @@ fetch('/api/geoword/paid/play/get-progress?' + new URLSearchParams({ packetName }); }); -async function checkGeowordAnswer (givenAnswer, questionNumber) { +async function checkGeowordAnswer (givenAnswer, questionNumber, celerity, prompts) { return await fetch('/api/geoword/paid/play/check-answer?' + new URLSearchParams({ givenAnswer, packetName, questionNumber, - division + division, + celerity, + prompts })) .then(response => response.json()) .then(data => { - const { actualAnswer, directive, directedPrompt } = data; - return { actualAnswer, directive, directedPrompt }; + const { actualAnswer, directive, directedPrompt, saved } = data; + return { actualAnswer, directive, directedPrompt, saved }; }); } async function giveAnswer (givenAnswer) { - const { actualAnswer, directive, directedPrompt } = await checkGeowordAnswer(givenAnswer, questionNumber); + const celerity = Math.max(0, 1 - currentAudio.currentTime / currentAudio.duration); + const { actualAnswer, directive, directedPrompt, saved } = await checkGeowordAnswer(givenAnswer, questionNumber, celerity, prompts); switch (directive) { case 'accept': - updateScore(true, givenAnswer, actualAnswer, prompts); + updateScore(true, givenAnswer, actualAnswer, celerity, saved); prompts = []; break; case 'prompt': @@ -79,7 +82,7 @@ async function giveAnswer (givenAnswer) { prompts.push(givenAnswer); break; case 'reject': - updateScore(false, givenAnswer, actualAnswer, prompts); + updateScore(false, givenAnswer, actualAnswer, celerity, saved); prompts = []; break; } @@ -156,24 +159,7 @@ function recordProtest (packetName, questionNumber) { }); } -/** - * @returns {Promise} - */ -async function recordBuzz (packetName, questionNumber, celerity, isCorrect, givenAnswer, prompts = []) { - return await fetch('/api/geoword/paid/play/record-buzz?' + new URLSearchParams({ - packetName, - questionNumber, - celerity, - isCorrect, - givenAnswer, - prompts - })) - .then(response => response.ok) - .catch(() => false); -} - -async function updateScore (isCorrect, givenAnswer, actualAnswer, prompts = []) { - const celerity = Math.max(0, 1 - currentAudio.currentTime / currentAudio.duration); +function updateScore (isCorrect, givenAnswer, actualAnswer, celerity, saved) { const currentPoints = scoreBuzz(scoring, { celerity, isCorrect }); if (isCorrect) { @@ -184,8 +170,6 @@ async function updateScore (isCorrect, givenAnswer, actualAnswer, prompts = []) document.getElementById('protest-text').classList.remove('d-none'); } - const saved = recordBuzz(packetName, questionNumber, celerity, isCorrect, givenAnswer, prompts); - points += currentPoints; tossupsHeard++; totalCorrectCelerity += isCorrect ? celerity : 0; @@ -202,7 +186,7 @@ async function updateScore (isCorrect, givenAnswer, actualAnswer, prompts = []) document.getElementById('buzz').disabled = true; document.getElementById('question-info').classList.remove('d-none'); - if (await saved) { + if (saved) { document.getElementById('next').disabled = false; } else { document.getElementById('record-buzz-error').classList.remove('d-none'); diff --git a/routes/api/geoword/paid/play/check-answer.js b/routes/api/geoword/paid/play/check-answer.js index 86a5f0874..fee1eac48 100644 --- a/routes/api/geoword/paid/play/check-answer.js +++ b/routes/api/geoword/paid/play/check-answer.js @@ -1,6 +1,7 @@ import getUserId from '../../../../../database/account-info/get-user-id.js'; import getAnswer from '../../../../../database/geoword/paid/play/get-answer.js'; import logPlayEvent from '../../../../../database/geoword/paid/play/log-play-event.js'; +import recordBuzz from '../../../../../database/geoword/paid/play/record-buzz.js'; import { Router } from 'express'; import checkAnswer from 'qb-answer-checker'; @@ -15,7 +16,21 @@ router.get('/', async (req, res) => { const userId = await getUserId(req.session.username); logPlayEvent({ type: 'check-answer', packetName, questionNumber, userId, givenAnswer, directive }).catch(console.error); - res.json({ actualAnswer: answer, directive, directedPrompt }); + if (directive === 'prompt') { + return res.json({ actualAnswer: answer, directive, directedPrompt }); + } + + const celerity = Math.min(1, Math.max(0, parseFloat(req.query.celerity) || 0)); + const prompts = req.query.prompts ? req.query.prompts.split(',') : []; + + let saved = false; + try { + saved = Boolean(await recordBuzz({ celerity, division, givenAnswer, isCorrect: directive === 'accept', packetName, prompts, questionNumber, userId })); + } catch (error) { + console.error(error); + } + + res.json({ actualAnswer: answer, directive, directedPrompt, saved }); }); export default router; diff --git a/routes/api/geoword/paid/play/index.js b/routes/api/geoword/paid/play/index.js index fed9fc107..7fdb776ab 100644 --- a/routes/api/geoword/paid/play/index.js +++ b/routes/api/geoword/paid/play/index.js @@ -4,7 +4,6 @@ import divisionChoiceRouter from './division-choice.js'; import getInstructionsRouter from './get-instructions.js'; import getProgressRouter from './get-progress.js'; import getQuestionCountRouter from './get-question-count.js'; -import recordBuzzRouter from './record-buzz.js'; import recordDivisionRouter from './record-division.js'; import recordProtestRouter from './record-protest.js'; import requireCurrentQuestion from './require-current-question.js'; @@ -14,8 +13,8 @@ import { Router } from 'express'; const router = Router(); -router.use(['/audio/question.mp3', '/check-answer', '/get-instructions', '/record-buzz'], requireCurrentQuestion); -router.use(['/audio/question.mp3', '/check-answer', '/get-instructions', '/record-buzz'], useDivisionChoice); +router.use(['/audio/question.mp3', '/check-answer', '/get-instructions'], requireCurrentQuestion); +router.use(['/audio/question.mp3', '/check-answer', '/get-instructions'], useDivisionChoice); router.use('/audio', audioRouter); router.use('/check-answer', checkAnswerRouter); @@ -23,7 +22,6 @@ router.use('/division-choice', divisionChoiceRouter); router.use('/get-instructions', getInstructionsRouter); router.use('/get-progress', getProgressRouter); router.use('/get-question-count', getQuestionCountRouter); -router.use('/record-buzz', recordBuzzRouter); router.use('/record-division', recordDivisionRouter); router.use('/record-protest', recordProtestRouter); diff --git a/routes/api/geoword/paid/play/record-buzz.js b/routes/api/geoword/paid/play/record-buzz.js deleted file mode 100644 index 6b606d8e5..000000000 --- a/routes/api/geoword/paid/play/record-buzz.js +++ /dev/null @@ -1,27 +0,0 @@ -import getUserId from '../../../../../database/account-info/get-user-id.js'; -import recordBuzz from '../../../../../database/geoword/paid/play/record-buzz.js'; - -import { Router } from 'express'; - -const router = Router(); - -router.get('/', async (req, res) => { - const { username } = req.session; - - req.query.celerity = Math.min(1, Math.max(0, parseFloat(req.query.celerity) || 0)); - req.query.isCorrect = req.query.isCorrect === 'true'; - if (req.query.prompts) { - req.query.prompts = req.query.prompts.split(','); - } - - const userId = await getUserId(username); - const { celerity, division, givenAnswer, isCorrect, packetName, prompts, questionNumber } = req.query; - try { - const result = await recordBuzz({ celerity, division, givenAnswer, isCorrect, packetName, prompts, questionNumber, userId }); - res.sendStatus(result ? 200 : 404); - } catch (error) { - res.sendStatus(error.code === 11000 ? 409 : 500); - } -}); - -export default router; From f22b6bda83178417db384fb73bbfb80aa181e1a6 Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Wed, 21 Oct 2026 00:11:30 -0400 Subject: [PATCH 2/2] Split the recordBuzz call in check-answer for readability Co-Authored-By: Claude Sonnet 5.5 --- routes/api/geoword/paid/play/check-answer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/api/geoword/paid/play/check-answer.js b/routes/api/geoword/paid/play/check-answer.js index fee1eac48..fa4b4f17a 100644 --- a/routes/api/geoword/paid/play/check-answer.js +++ b/routes/api/geoword/paid/play/check-answer.js @@ -25,7 +25,8 @@ router.get('/', async (req, res) => { let saved = false; try { - saved = Boolean(await recordBuzz({ celerity, division, givenAnswer, isCorrect: directive === 'accept', packetName, prompts, questionNumber, userId })); + const isCorrect = directive === 'accept'; + saved = !!(await recordBuzz({ celerity, division, givenAnswer, isCorrect, packetName, prompts, questionNumber, userId })); } catch (error) { console.error(error); }