diff --git a/server/Room.js b/server/Room.js index 6b88bf48f..12f85e0e3 100644 --- a/server/Room.js +++ b/server/Room.js @@ -20,13 +20,17 @@ class Room { this.tossup = {}; this.wordIndex = 0; + this.randomQuestionCache = []; + this.setCache = []; + this.query = { difficulties: [4, 5], packetNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], questionType: 'tossup', setName: '2022 PACE NSC', categories: [], - subcategories: [] + subcategories: [], + reverse: true // used for `database.getSet` }; this.settings = { @@ -150,7 +154,12 @@ class Room { } if (type === 'difficulties') { - this.adjustQuery(userId, 'difficulties', message.value); + this.sendSocketMessage({ + type: 'difficulties', + username: this.players[userId].username, + value: message.value + }); + this.adjustQuery(['difficulties'], [message.value]); } if (type === 'give-answer') { @@ -168,8 +177,7 @@ class Room { } if (type === 'packet-number') { - this.query.packetNumbers = message.value; - this.questionNumber = 0; + this.adjustQuery(['packetNumbers'], [message.value]); this.sendSocketMessage({ type: 'packet-number', username: this.players[userId].username, @@ -191,13 +199,13 @@ class Room { } if (type === 'set-name') { - this.query.setName = message.value; - this.questionNumber = 0; this.sendSocketMessage({ type: 'set-name', username: this.players[userId].username, - value: this.query.setName + value: message.value }); + + this.adjustQuery(['setName'], [message.value]); } if (type === 'toggle-rebuzz') { @@ -210,15 +218,14 @@ class Room { } if (type === 'toggle-select-by-set-name') { - this.settings.selectBySetName = message.selectBySetName; - this.query.setName = message.setName; - this.questionNumber = 0; this.sendSocketMessage({ type: 'toggle-select-by-set-name', - selectBySetName: this.settings.selectBySetName, + selectBySetName: message.selectBySetName, setName: this.query.setName, username: this.players[userId].username }); + this.settings.selectBySetName = message.selectBySetName; + this.adjustQuery(['setName'], [message.setName]); } if (type === 'toggle-visibility') { @@ -231,27 +238,39 @@ class Room { } if (type === 'update-categories') { - this.query.categories = message.categories; - this.query.subcategories = message.subcategories; this.sendSocketMessage({ type: 'update-categories', - categories: this.query.categories, - subcategories: this.query.subcategories, + categories: message.categories, + subcategories: message.subcategories, username: this.players[userId].username }); + this.adjustQuery(['categories', 'subcategories'], [message.categories, message.subcategories]); } } - adjustQuery(userId, setting, value) { - if (Object.prototype.hasOwnProperty.call(this.query, setting)) { - this.query[setting] = value; + adjustQuery(settings, values) { + if (settings.length !== values.length) { + return; } - this.sendSocketMessage({ - type: setting, - username: this.players[userId].username, - value: value - }); + for (let i = 0; i < settings.length; i++) { + const setting = settings[i]; + const value = values[i]; + if (Object.prototype.hasOwnProperty.call(this.query, setting)) { + this.query[setting] = value; + } + } + + if (this.settings.selectBySetName) { + this.questionNumber = 0; + database.getSet(this.query).then(set => { + this.setCache = set; + }); + } else { + database.getRandomQuestions(this.query).then(tossups => { + this.randomQuestionCache = tossups; + }); + } } async advanceQuestion() { @@ -261,25 +280,25 @@ class Room { this.paused = false; if (this.settings.selectBySetName) { - this.tossup = await database.getNextQuestion( - this.query.setName, - this.query.packetNumbers, - this.questionNumber, - this.query.categories, - this.query.subcategories - ); - if (Object.keys(this.tossup).length === 0) { + if (this.setCache.length === 0) { + this.setCache = await database.getSet(this.query); + } + + if (this.setCache.length === 0) { this.sendSocketMessage({ type: 'end-of-set' }); return false; } else { + this.tossup = this.setCache.pop(); this.questionNumber = this.tossup.questionNumber; this.query.packetNumbers = this.query.packetNumbers.filter(packetNumber => packetNumber >= this.tossup.packetNumber); } } else { - this.tossup = await database.getRandomQuestions(this.query); - this.tossup = this.tossup[0]; + if (this.randomQuestionCache.length === 0) { + this.randomQuestionCache = await database.getRandomQuestions(this.query); + } + this.tossup = this.randomQuestionCache.pop(); if (Object.keys(this.tossup).length === 0) { this.sendSocketMessage({ type: 'no-questions-found' diff --git a/server/database.js b/server/database.js index f5a1b2116..adc46aa16 100644 --- a/server/database.js +++ b/server/database.js @@ -40,14 +40,14 @@ function escapeRegExp(string) { * Gets the next question with a question number greater than `currentQuestionNumber` that satisfies the given conditions. * @param {String} setName - the name of the set (e.g. "2021 ACF Fall"). * @param {Array} packetNumbers - an array of packet numbers to search. Each packet number is 1-indexed. - * @param {Number} currentQuestionNumber - current question number. **Starts at 1.** - * @param {Array} validCategories - * @param {Array} validSubcategories + * @param {Array} categories + * @param {Array} subcategories * @param {'tossup' | 'bonus'} type - Type of question you want to get. Default: `'tossup'`. * @param {Boolean} alwaysUseUnformattedAnswer - whether to always use the unformatted answer. Default: `false` + * @param {Boolean} reverse - whether to reverse the order of the questions. Useful for functions that pop at the end of the array, Default: `false` * @returns {Promise} */ -async function getNextQuestion(setName, packetNumbers, currentQuestionNumber, validCategories, validSubcategories, type = 'tossup', alwaysUseUnformattedAnswer = false) { +async function getSet({ setName, packetNumbers, categories, subcategories, type = 'tossup', alwaysUseUnformattedAnswer = false, reverse = false }) { if (setName === '') { return 0; } @@ -57,43 +57,32 @@ async function getNextQuestion(setName, packetNumbers, currentQuestionNumber, va return 0; } - if (validCategories.length === 0) validCategories = CATEGORIES; - if (validSubcategories.length === 0) validSubcategories = SUBCATEGORIES_FLATTENED; - - const question = await questions.findOne({ - $or: [ - { - setName: setName, - category: { $in: validCategories }, - subcategory: { $in: validSubcategories }, - packetNumber: packetNumbers[0], - questionNumber: { $gt: currentQuestionNumber }, - type: type - }, - { - setName: setName, - category: { $in: validCategories }, - subcategory: { $in: validSubcategories }, - packetNumber: { $in: packetNumbers.slice(1) }, - type: type - }, - ] + if (categories.length === 0) categories = CATEGORIES; + if (subcategories.length === 0) subcategories = SUBCATEGORIES_FLATTENED; + + const questionArray = await questions.find({ + setName: setName, + category: { $in: categories }, + subcategory: { $in: subcategories }, + packetNumber: { $in: packetNumbers }, + type: type }, { - sort: { packetNumber: 1, questionNumber: 1 } - }).catch(error => { - console.log('[DATABASE] ERROR:', error); - return {}; - }); + sort: { packetNumber: reverse ? -1 : 1, questionNumber: reverse ? -1 : 1 } + }).toArray(); - if (!question) { + if (!questionArray) { return {}; } - if (!alwaysUseUnformattedAnswer && Object.prototype.hasOwnProperty.call(question, 'formatted_answer')) { - question.answer = question.formatted_answer; + if (!alwaysUseUnformattedAnswer && type === 'tossup') { + for (let i = 0; i < questionArray.length; i++) { + if (questionArray[i].formatted_answer) { + questionArray[i].answer = questionArray[i].formatted_answer; + } + } } - return question || {}; + return questionArray || []; } @@ -301,10 +290,10 @@ function getRandomName() { * @param {Array} difficulties - an array of allowed difficulty levels (1-10). Pass a 0-length array to select any difficulty. * @param {Array} categories - an array of allowed categories. Pass a 0-length array to select any category. * @param {Array} subcategories - an array of allowed subcategories. Pass a 0-length array to select any subcategory. - * @param {Number} number - how many random tossups to return + * @param {Number} number - how many random tossups to return. Default: 20. * @returns {Promise>} */ -async function getRandomQuestions({ questionType = 'tossup', difficulties = DIFFICULTIES, categories = CATEGORIES, subcategories = SUBCATEGORIES_FLATTENED, number = 1 }) { +async function getRandomQuestions({ questionType = 'tossup', difficulties = DIFFICULTIES, categories = CATEGORIES, subcategories = SUBCATEGORIES_FLATTENED, number = 20 }) { if (difficulties.length === 0) difficulties = DIFFICULTIES; if (categories.length === 0) categories = CATEGORIES; if (subcategories.length === 0) subcategories = SUBCATEGORIES_FLATTENED; @@ -354,4 +343,4 @@ async function reportQuestion(_id, reason, description) { } -module.exports = { DEFAULT_QUERY_RETURN_LENGTH, getNextQuestion, getNumPackets, getPacket, getQuery, getRandomQuestions, getSetList, getRandomName, reportQuestion }; +module.exports = { DEFAULT_QUERY_RETURN_LENGTH, getSet, getNumPackets, getPacket, getQuery, getRandomQuestions, getSetList, getRandomName, reportQuestion };