From 6d6d0b059b26ba126b0a76e414f0a542ab589687 Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Thu, 21 Dec 2023 17:57:07 -0600 Subject: [PATCH] close #160 --- client/api-docs/random-bonus.html | 12 +++++ client/api-docs/random-tossup.html | 21 +++++++++ client/multiplayer/room.html | 4 ++ client/multiplayer/room.js | 23 +++++++++- client/singleplayer/bonuses.html | 4 ++ client/singleplayer/bonuses.js | 61 ++++++++++++++++--------- client/singleplayer/tossups.html | 4 ++ client/singleplayer/tossups.js | 56 +++++++++++++++-------- database/qbreader/get-random-bonuses.js | 6 +++ database/qbreader/get-random-tossups.js | 6 +++ routes/api/random-bonus.js | 2 + routes/api/random-tossup.js | 1 + server/TossupRoom.js | 13 ++++++ 13 files changed, 171 insertions(+), 42 deletions(-) diff --git a/client/api-docs/random-bonus.html b/client/api-docs/random-bonus.html index 586feabc..8e2b0f08 100644 --- a/client/api-docs/random-bonus.html +++ b/client/api-docs/random-bonus.html @@ -217,6 +217,18 @@

Parameters

Whether to only return bonuses with 3 parts. +
  • +
    + standardOnly: boolean + default: "false" +
    +
    + Whether or not to only return questions from standard quizbowl formats. + Broadly, non-standard formats include anything that's not designed for use at an academic quizbowl tournament. + Think trash packets, single-packet sets, and side events. + Single-subject full-length tournaments (think DECAF and Lederberg) still count as "standard-format". +
    +
  • diff --git a/client/api-docs/random-tossup.html b/client/api-docs/random-tossup.html index c1ab9a38..ed4aa02f 100644 --- a/client/api-docs/random-tossup.html +++ b/client/api-docs/random-tossup.html @@ -208,6 +208,27 @@

    Parameters

    The most recent year to search for. +
  • +
    + powermarkOnly: boolean + default: "false" +
    +
    + Whether or not to only return powermarked questions. +
    +
  • +
  • +
    + standardOnly: boolean + default: "false" +
    +
    + Whether or not to only return questions from standard quizbowl formats. + Broadly, non-standard formats include anything that's not designed for use at an academic quizbowl tournament. + Think trash packets, single-packet sets, and side events. + Single-subject full-length tournaments (think DECAF and Lederberg) still count as "standard-format". +
    +
  • diff --git a/client/multiplayer/room.html b/client/multiplayer/room.html index e2b1a759..e596c417 100644 --- a/client/multiplayer/room.html +++ b/client/multiplayer/room.html @@ -154,6 +154,10 @@

    DO YOUR GRAD SCHOOL APPS

    +
    + + +
    diff --git a/client/multiplayer/room.js b/client/multiplayer/room.js index 9b8ec24b..251fd39b 100644 --- a/client/multiplayer/room.js +++ b/client/multiplayer/room.js @@ -199,6 +199,12 @@ socket.onmessage = function (event) { document.getElementById('set-settings').classList.add('d-none'); } document.getElementById('toggle-powermark-only').disabled = data.selectBySetName; + document.getElementById('toggle-standard-only').disabled = data.selectBySetName; + break; + + case 'toggle-standard-only': + logEvent(data.username, `${data.standardOnly ? 'enabled' : 'disabled'} standard format only`); + document.getElementById('toggle-standard-only').checked = data.standardOnly; break; case 'toggle-timer': @@ -322,6 +328,12 @@ const socketOnConnectionAcknowledged = async (message) => { document.getElementById('set-settings').classList.add('d-none'); } + document.getElementById('toggle-powermark-only').disabled = message.selectBySetName; + document.getElementById('toggle-standard-only').disabled = message.selectBySetName; + + document.getElementById('toggle-powermark-only').checked = message.powermarkOnly; + document.getElementById('toggle-standard-only').checked = message.standardOnly; + switch (message.questionProgress) { case 0: document.getElementById('next').textContent = 'Start'; @@ -934,6 +946,12 @@ document.getElementById('toggle-lock').addEventListener('click', function () { }); +document.getElementById('toggle-powermark-only').addEventListener('click', function () { + this.blur(); + socket.send(JSON.stringify({ type: 'toggle-powermark-only', powermarkOnly: this.checked })); +}); + + document.getElementById('toggle-rebuzz').addEventListener('click', function () { this.blur(); socket.send(JSON.stringify({ type: 'toggle-rebuzz', rebuzz: this.checked })); @@ -955,9 +973,10 @@ document.getElementById('toggle-select-by-set-name').addEventListener('click', f })); }); -document.getElementById('toggle-powermark-only').addEventListener('click', function () { + +document.getElementById('toggle-standard-only').addEventListener('click', function () { this.blur(); - socket.send(JSON.stringify({ type: 'toggle-powermark-only', powermarkOnly: this.checked })); + socket.send(JSON.stringify({ type: 'toggle-standard-only', standardOnly: this.checked })); }); document.getElementById('toggle-timer').addEventListener('click', function () { diff --git a/client/singleplayer/bonuses.html b/client/singleplayer/bonuses.html index 380b19c5..b509be1a 100644 --- a/client/singleplayer/bonuses.html +++ b/client/singleplayer/bonuses.html @@ -130,6 +130,10 @@

    DO YOUR GRAD SCHOOL APPS

    +
    + + +
    diff --git a/client/singleplayer/bonuses.js b/client/singleplayer/bonuses.js index bcdb7992..acca48ba 100644 --- a/client/singleplayer/bonuses.js +++ b/client/singleplayer/bonuses.js @@ -12,22 +12,29 @@ let questions = [{}]; */ let randomQuestions = []; -// Room settings -const query = localStorage.getItem('singleplayer-bonus-query') - ? JSON.parse(localStorage.getItem('singleplayer-bonus-query')) - : { - categories: [], - difficulties: [], - minYear: 2010, - maxYear: 2023, - packetNumbers: [], - setName: '', - subcategories: [], - threePartBonuses: true, - }; +const defaults = { + categories: [], + difficulties: [], + minYear: 2010, + maxYear: 2023, + packetNumbers: [], + setName: '', + subcategories: [], + threePartBonuses: true, + version: '12-21-2023', +}; -query.subcategories = query.subcategories.filter(subcategory => subcategory !== 'Math'); -localStorage.setItem('singleplayer-tossup-query', JSON.stringify(query)); +// Room settings +let query; +if (!localStorage.getItem('singleplayer-bonus-query')) { + query = defaults; +} else { + query = JSON.parse(localStorage.getItem('singleplayer-bonus-query')); + if (query.version !== '12-21-2023') { + query = defaults; + localStorage.setItem('singleplayer-bonus-query', JSON.stringify(query)); + } +} const settings = localStorage.getItem('singleplayer-bonus-settings') ? JSON.parse(localStorage.getItem('singleplayer-bonus-settings')) @@ -47,6 +54,7 @@ if (settings.selectBySetName) { document.getElementById('difficulty-settings').classList.add('d-none'); document.getElementById('set-settings').classList.remove('d-none'); document.getElementById('toggle-select-by-set-name').checked = true; + document.getElementById('toggle-standard-only').disabled = true; document.getElementById('toggle-three-part-bonuses').disabled = true; } @@ -575,6 +583,7 @@ document.getElementById('start').addEventListener('click', async function () { document.getElementById('toggle-select-by-set-name').addEventListener('click', function () { this.blur(); settings.selectBySetName = this.checked; + document.getElementById('toggle-standard-only').disabled = this.checked; document.getElementById('toggle-three-part-bonuses').disabled = this.checked; if (this.checked) { @@ -588,12 +597,6 @@ document.getElementById('toggle-select-by-set-name').addEventListener('click', f localStorage.setItem('singleplayer-bonus-settings', JSON.stringify(settings)); }); -document.getElementById('toggle-three-part-bonuses').addEventListener('click', function () { - this.blur(); - query.threePartBonuses = this.checked; - loadRandomBonuses(query); - localStorage.setItem('singleplayer-bonus-query', JSON.stringify(query)); -}); document.getElementById('toggle-show-history').addEventListener('click', function () { this.blur(); @@ -609,6 +612,22 @@ document.getElementById('toggle-show-history').addEventListener('click', functio }); +document.getElementById('toggle-standard-only').addEventListener('click', function () { + this.blur(); + query.standardOnly = this.checked; + loadRandomBonuses(query); + localStorage.setItem('singleplayer-bonus-query', JSON.stringify(query)); +}); + + +document.getElementById('toggle-three-part-bonuses').addEventListener('click', function () { + this.blur(); + query.threePartBonuses = this.checked; + loadRandomBonuses(query); + localStorage.setItem('singleplayer-bonus-query', JSON.stringify(query)); +}); + + document.getElementById('type-to-answer').addEventListener('click', function () { this.blur(); settings.typeToAnswer = this.checked; diff --git a/client/singleplayer/tossups.html b/client/singleplayer/tossups.html index 760d4168..9df8fb90 100644 --- a/client/singleplayer/tossups.html +++ b/client/singleplayer/tossups.html @@ -132,6 +132,10 @@

    DO YOUR GRAD SCHOOL APPS

    +
    + + +
    diff --git a/client/singleplayer/tossups.js b/client/singleplayer/tossups.js index 1a4b4759..5b5611c3 100644 --- a/client/singleplayer/tossups.js +++ b/client/singleplayer/tossups.js @@ -16,21 +16,29 @@ const previous = { celerity: 0, }; -const query = localStorage.getItem('singleplayer-tossup-query') - ? JSON.parse(localStorage.getItem('singleplayer-tossup-query')) - : { - categories: [], - difficulties: [], - minYear: 2010, - maxYear: 2023, - packetNumbers: [], - powermarkOnly: false, - setName: '', - subcategories: [], - }; +const defaults = { + categories: [], + difficulties: [], + minYear: 2010, + maxYear: 2023, + packetNumbers: [], + powermarkOnly: false, + setName: '', + standardOnly: false, + subcategories: [], + version: '12-21-2023', +}; -query.subcategories = query.subcategories.filter(subcategory => subcategory !== 'Math'); -localStorage.setItem('singleplayer-tossup-query', JSON.stringify(query)); +let query; +if (!localStorage.getItem('singleplayer-tossup-query')) { + query = defaults; +} else { + query = JSON.parse(localStorage.getItem('singleplayer-tossup-query')); + if (query.version !== '12-21-2023') { + query = defaults; + localStorage.setItem('singleplayer-tossup-query', JSON.stringify(query)); + } +} const settings = localStorage.getItem('singleplayer-tossup-settings') ? JSON.parse(localStorage.getItem('singleplayer-tossup-settings')) @@ -62,6 +70,7 @@ if (settings.selectBySetName) { document.getElementById('set-settings').classList.remove('d-none'); document.getElementById('toggle-select-by-set-name').checked = true; document.getElementById('toggle-powermark-only').disabled = true; + document.getElementById('toggle-standard-only').disabled = true; } if (!settings.showHistory) { @@ -329,9 +338,9 @@ function isPace(setName) { return setName.includes('PACE'); } -async function loadRandomTossups({ categories, difficulties, minYear, maxYear, number = 1, powermarkOnly, subcategories }) { +async function loadRandomTossups({ categories, difficulties, minYear, maxYear, number = 1, powermarkOnly, subcategories, standardOnly }) { randomQuestions = []; - await fetch('/api/random-tossup?' + new URLSearchParams({ categories, difficulties, maxYear, minYear, number, powermarkOnly, subcategories })) + await fetch('/api/random-tossup?' + new URLSearchParams({ categories, difficulties, maxYear, minYear, number, powermarkOnly, subcategories, standardOnly })) .then(response => response.json()) .then(response => response.tossups) .then(questions => { @@ -349,16 +358,16 @@ async function loadRandomTossups({ categories, difficulties, minYear, maxYear, n * Get a random tossup. * @returns */ -async function getRandomTossup({ categories, difficulties, minYear, maxYear, powermarkOnly, subcategories }) { +async function getRandomTossup({ categories, difficulties, minYear, maxYear, powermarkOnly, subcategories, standardOnly }) { if (randomQuestions.length === 0) { - await loadRandomTossups({ categories, difficulties, maxYear, minYear, number: 20, powermarkOnly, subcategories }); + await loadRandomTossups({ categories, difficulties, maxYear, minYear, number: 20, powermarkOnly, subcategories, standardOnly }); } const randomQuestion = randomQuestions.pop(); // Begin loading the next batch of questions (asynchronously) if (randomQuestions.length === 0) { - loadRandomTossups({ categories, difficulties, maxYear, minYear, number: 20, powermarkOnly, subcategories }); + loadRandomTossups({ categories, difficulties, maxYear, minYear, number: 20, powermarkOnly, subcategories, standardOnly }); } return randomQuestion; @@ -747,6 +756,7 @@ document.getElementById('toggle-select-by-set-name').addEventListener('click', f this.blur(); settings.selectBySetName = this.checked; document.getElementById('toggle-powermark-only').disabled = this.checked; + document.getElementById('toggle-standard-only').disabled = this.checked; if (this.checked) { document.getElementById('difficulty-settings').classList.add('d-none'); @@ -775,6 +785,14 @@ document.getElementById('toggle-show-history').addEventListener('click', functio }); +document.getElementById('toggle-standard-only').addEventListener('click', function () { + this.blur(); + query.standardOnly = this.checked; + loadRandomTossups(query); + localStorage.setItem('singleplayer-tossup-query', JSON.stringify(query)); +}); + + document.getElementById('type-to-answer').addEventListener('click', function () { this.blur(); settings.typeToAnswer = this.checked; diff --git a/database/qbreader/get-random-bonuses.js b/database/qbreader/get-random-bonuses.js index f2fb93b7..0738de84 100644 --- a/database/qbreader/get-random-bonuses.js +++ b/database/qbreader/get-random-bonuses.js @@ -16,6 +16,7 @@ import * as types from '../../types.js'; * @param {number} [object.minYear=2010] - the minimum year to select from. Default: 2010. * @param {number} [object.maxYear=2023] - the maximum year to select from. Default: 2023. * @param {number} [object.bonusLength] - if not null or undefined, only return bonuses with number of parts equal to `bonusLength`. + * @param {boolean} [object.standardOnly=false] * @returns {Promise} */ async function getRandomBonuses({ @@ -26,6 +27,7 @@ async function getRandomBonuses({ minYear = DEFAULT_MIN_YEAR, maxYear = DEFAULT_MAX_YEAR, bonusLength, + standardOnly = false, } = {}) { const aggregation = [ { $match: { 'set.year': { $gte: minYear, $lte: maxYear } } }, @@ -51,6 +53,10 @@ async function getRandomBonuses({ aggregation[0].$match.answers = { $size: bonusLength }; } + if (standardOnly) { + aggregation[0].$match['set.standard'] = true; + } + return await bonuses.aggregate(aggregation).toArray(); } diff --git a/database/qbreader/get-random-tossups.js b/database/qbreader/get-random-tossups.js index c638a264..99b8d56b 100644 --- a/database/qbreader/get-random-tossups.js +++ b/database/qbreader/get-random-tossups.js @@ -15,6 +15,7 @@ import * as types from '../../types.js'; * @param {number} [object.minYear=2010] * @param {number} [object.maxYear=2023] * @param {boolean} [object.powermarkOnly=false] + * @param {boolean} [object.standardOnly=false] * @returns {Promise} */ async function getRandomTossups({ @@ -25,6 +26,7 @@ async function getRandomTossups({ minYear = DEFAULT_MIN_YEAR, maxYear = DEFAULT_MAX_YEAR, powermarkOnly = false, + standardOnly = false, } = {}) { const aggregation = [ { $match: { 'set.year': { $gte: minYear, $lte: maxYear } } }, @@ -48,6 +50,10 @@ async function getRandomTossups({ aggregation[0].$match.question = { $regex: '\\(\\*\\)' }; } + if (standardOnly) { + aggregation[0].$match['set.standard'] = true; + } + return await tossups.aggregate(aggregation).toArray(); } diff --git a/routes/api/random-bonus.js b/routes/api/random-bonus.js index 1af727b5..8d3a98cd 100644 --- a/routes/api/random-bonus.js +++ b/routes/api/random-bonus.js @@ -29,6 +29,8 @@ router.get('/', async (req, res) => { req.query.maxYear = isNaN(req.query.maxYear) ? undefined : parseInt(req.query.maxYear); req.query.number = isNaN(req.query.number) ? undefined : parseInt(req.query.number); + req.query.standardOnly = (req.query.standardOnly === 'true'); + const bonuses = await getRandomBonuses(req.query); if (bonuses.length === 0) { diff --git a/routes/api/random-tossup.js b/routes/api/random-tossup.js index ef91b2ee..42b57551 100644 --- a/routes/api/random-tossup.js +++ b/routes/api/random-tossup.js @@ -28,6 +28,7 @@ router.get('/', async (req, res) => { req.query.number = isNaN(req.query.number) ? undefined : parseInt(req.query.number); req.query.powermarkOnly = (req.query.powermarkOnly === 'true'); + req.query.standardOnly = (req.query.standardOnly === 'true'); const tossups = await getRandomTossups(req.query); diff --git a/server/TossupRoom.js b/server/TossupRoom.js index cbca7c4f..d8ead0c7 100644 --- a/server/TossupRoom.js +++ b/server/TossupRoom.js @@ -63,6 +63,7 @@ class TossupRoom { subcategories: subcategories, reverse: true, // used for `database.getSet` powermarkOnly: false, + standardOnly: false, }; this.settings = { @@ -144,6 +145,8 @@ class TossupRoom { setName: this.query.setName, validCategories: this.query.categories, validSubcategories: this.query.subcategories, + powermarkOnly: this.query.powermarkOnly, + standardOnly: this.query.standardOnly, public: this.settings.public, readingSpeed: this.settings.readingSpeed, @@ -342,6 +345,16 @@ class TossupRoom { }); break; + case 'toggle-standard-only': + this.query.standardOnly = message.standardOnly; + this.sendSocketMessage({ + type: 'toggle-standard-only', + standardOnly: message.standardOnly, + username: this.players[userId].username, + }); + this.adjustQuery(['standardOnly'], [message.standardOnly]); + break; + case 'toggle-timer': if (this.settings.public) { return;