diff --git a/client/play/TossupBonusClient.js b/client/play/TossupBonusClient.js index ca0e380bc..793571d12 100644 --- a/client/play/TossupBonusClient.js +++ b/client/play/TossupBonusClient.js @@ -2,7 +2,9 @@ import { BonusClientMixin } from './BonusClient.js'; import { TossupClientMixin } from './TossupClient.js'; import QuestionClient from './QuestionClient.js'; -export default class TossupBonusClient extends BonusClientMixin(TossupClientMixin(QuestionClient)) { +export default class TossupBonusClient extends BonusClientMixin( + TossupClientMixin(QuestionClient) +) { constructor (room, userId, socket) { super(room, userId, socket); attachEventListeners(room, socket); @@ -11,8 +13,10 @@ export default class TossupBonusClient extends BonusClientMixin(TossupClientMixi onmessage (message) { const data = JSON.parse(message); switch (data.type) { - case 'toggle-enable-bonuses': return this.toggleEnableBonuses(data); - default: return super.onmessage(message); + case 'toggle-enable-bonuses': + return this.toggleEnableBonuses(data); + default: + return super.onmessage(message); } } @@ -27,8 +31,13 @@ export default class TossupBonusClient extends BonusClientMixin(TossupClientMixi } function attachEventListeners (room, socket) { - document.getElementById('toggle-enable-bonuses').addEventListener('click', function () { - this.blur(); - socket.sendToServer({ type: 'toggle-enable-bonuses', enableBonuses: this.checked }); - }); + document + .getElementById('toggle-enable-bonuses') + .addEventListener('click', function () { + this.blur(); + socket.sendToServer({ + type: 'toggle-enable-bonuses', + enableBonuses: this.checked + }); + }); } diff --git a/client/play/TossupClient.js b/client/play/TossupClient.js index 8c62e6401..77698e11b 100644 --- a/client/play/TossupClient.js +++ b/client/play/TossupClient.js @@ -3,99 +3,125 @@ import QuestionClient from './QuestionClient.js'; import audio from '../audio/index.js'; import { MODE_ENUM } from '../../quizbowl/constants.js'; -export const TossupClientMixin = (ClientClass) => class extends ClientClass { - constructor (room, userId, socket) { - super(room, userId, socket); - attachEventListeners(room, socket); - } - - onmessage (message) { - const data = JSON.parse(message); - switch (data.type) { - case 'buzz': return this.buzz(data); - case 'end-current-tossup': return this.endCurrentTossup(data); - case 'give-tossup-answer': return this.giveTossupAnswer(data); - case 'pause': return this.pause(data); - case 'reveal-tossup-answer': return this.revealTossupAnswer(data); - case 'set-reading-speed': return this.setReadingSpeed(data); - case 'start-next-tossup': return this.startNextTossup(data); - case 'toggle-powermark-only': return this.togglePowermarkOnly(data); - case 'toggle-rebuzz': return this.toggleRebuzz(data); - case 'update-question': return this.updateQuestion(data); - default: return super.onmessage(message); +export const TossupClientMixin = (ClientClass) => + class extends ClientClass { + constructor (room, userId, socket) { + super(room, userId, socket); + attachEventListeners(room, socket); } - } - buzz ({ userId }) { - document.getElementById('buzz').disabled = true; - document.getElementById('next').disabled = true; - document.getElementById('pause').disabled = true; - if (userId === this.USER_ID && audio.soundEffects) { audio.buzz.play(); } - } + onmessage (message) { + const data = JSON.parse(message); + switch (data.type) { + case 'buzz': + return this.buzz(data); + case 'end-current-tossup': + return this.endCurrentTossup(data); + case 'give-tossup-answer': + return this.giveTossupAnswer(data); + case 'pause': + return this.pause(data); + case 'reveal-tossup-answer': + return this.revealTossupAnswer(data); + case 'set-reading-speed': + return this.setReadingSpeed(data); + case 'start-next-tossup': + return this.startNextTossup(data); + case 'toggle-powermark-only': + return this.togglePowermarkOnly(data); + case 'toggle-rebuzz': + return this.toggleRebuzz(data); + case 'toggle-stop-on-power': + return this.toggleStopOnPower(data); + case 'update-question': + return this.updateQuestion(data); + default: + return super.onmessage(message); + } + } + + buzz ({ userId }) { + document.getElementById('buzz').disabled = true; + document.getElementById('next').disabled = true; + document.getElementById('pause').disabled = true; + if (userId === this.USER_ID && audio.soundEffects) { + audio.buzz.play(); + } + } + + endCurrentTossup ({ starred, tossup }) { + addTossupGameCard({ starred, tossup }); + } + + giveTossupAnswer ({ directive, directedPrompt, score, userId }) { + super.giveAnswer({ directive, directedPrompt, score, userId }); + + if (directive !== 'prompt') { + document.getElementById('next').disabled = false; + } + } + + pause ({ paused }) { + document.getElementById('pause').textContent = paused + ? 'Resume' + : 'Pause'; + } + + revealTossupAnswer ({ answer, question }) { + document.getElementById('question').innerHTML = question; + document.getElementById('answer').innerHTML = 'ANSWER: ' + answer; + document.getElementById('pause').disabled = true; + } + + setMode ({ mode }) { + super.setMode({ mode }); + switch (mode) { + case MODE_ENUM.SET_NAME: + document.getElementById('toggle-powermark-only').disabled = true; + document.getElementById('toggle-standard-only').disabled = true; + break; + case MODE_ENUM.RANDOM: + document.getElementById('toggle-powermark-only').disabled = false; + document.getElementById('toggle-standard-only').disabled = false; + break; + } + } + + setReadingSpeed ({ readingSpeed }) { + document.getElementById('reading-speed').value = readingSpeed; + document.getElementById('reading-speed-display').textContent = + readingSpeed; + } + + startNextTossup ({ tossup, packetLength }) { + this.startNextQuestion({ question: tossup, packetLength }); + document.getElementById('buzz').textContent = 'Buzz'; + document.getElementById('buzz').disabled = false; + document.getElementById('pause').textContent = 'Pause'; + document.getElementById('pause').disabled = false; + this.room.tossup = tossup; + } - endCurrentTossup ({ starred, tossup }) { - addTossupGameCard({ starred, tossup }); - } + togglePowermarkOnly ({ powermarkOnly }) { + document.getElementById('toggle-powermark-only').checked = powermarkOnly; + } - giveTossupAnswer ({ directive, directedPrompt, score, userId }) { - super.giveAnswer({ directive, directedPrompt, score, userId }); + toggleRebuzz ({ rebuzz }) { + document.getElementById('toggle-rebuzz').checked = rebuzz; + } - if (directive !== 'prompt') { - document.getElementById('next').disabled = false; + toggleStopOnPower ({ stopOnPower }) { + console.log('TossupClient.toggleStopOnPower called'); + document.getElementById('toggle-stop-on-power').checked = stopOnPower; } - } - - pause ({ paused }) { - document.getElementById('pause').textContent = paused ? 'Resume' : 'Pause'; - } - - revealTossupAnswer ({ answer, question }) { - document.getElementById('question').innerHTML = question; - document.getElementById('answer').innerHTML = 'ANSWER: ' + answer; - document.getElementById('pause').disabled = true; - } - - setMode ({ mode }) { - super.setMode({ mode }); - switch (mode) { - case MODE_ENUM.SET_NAME: - document.getElementById('toggle-powermark-only').disabled = true; - document.getElementById('toggle-standard-only').disabled = true; - break; - case MODE_ENUM.RANDOM: - document.getElementById('toggle-powermark-only').disabled = false; - document.getElementById('toggle-standard-only').disabled = false; - break; + + updateQuestion ({ word }) { + if (word === '(*)' || word === '[*]') { + return; + } + document.getElementById('question').innerHTML += word + ' '; } - } - - setReadingSpeed ({ readingSpeed }) { - document.getElementById('reading-speed').value = readingSpeed; - document.getElementById('reading-speed-display').textContent = readingSpeed; - } - - startNextTossup ({ tossup, packetLength }) { - this.startNextQuestion({ question: tossup, packetLength }); - document.getElementById('buzz').textContent = 'Buzz'; - document.getElementById('buzz').disabled = false; - document.getElementById('pause').textContent = 'Pause'; - document.getElementById('pause').disabled = false; - this.room.tossup = tossup; - } - - togglePowermarkOnly ({ powermarkOnly }) { - document.getElementById('toggle-powermark-only').checked = powermarkOnly; - } - - toggleRebuzz ({ rebuzz }) { - document.getElementById('toggle-rebuzz').checked = rebuzz; - } - - updateQuestion ({ word }) { - if (word === '(*)' || word === '[*]') { return; } - document.getElementById('question').innerHTML += word + ' '; - } -}; + }; function attachEventListeners (room, socket) { document.getElementById('buzz').addEventListener('click', function () { @@ -106,29 +132,58 @@ function attachEventListeners (room, socket) { document.getElementById('pause').addEventListener('click', function () { this.blur(); - const seconds = parseFloat(document.querySelector('.timer .face').textContent); - const tenths = parseFloat(document.querySelector('.timer .fraction').textContent); + const seconds = parseFloat( + document.querySelector('.timer .face').textContent + ); + const tenths = parseFloat( + document.querySelector('.timer .fraction').textContent + ); const pausedTime = (seconds + tenths) * 10; socket.sendToServer({ type: 'pause', pausedTime }); }); - document.getElementById('reading-speed').addEventListener('change', function () { - socket.sendToServer({ type: 'set-reading-speed', readingSpeed: this.value }); - }); - - document.getElementById('reading-speed').addEventListener('input', function () { - document.getElementById('reading-speed-display').textContent = this.value; - }); - - document.getElementById('toggle-powermark-only').addEventListener('click', function () { - this.blur(); - socket.sendToServer({ type: 'toggle-powermark-only', powermarkOnly: this.checked }); - }); - - document.getElementById('toggle-rebuzz').addEventListener('click', function () { - this.blur(); - socket.sendToServer({ type: 'toggle-rebuzz', rebuzz: this.checked }); - }); + document + .getElementById('reading-speed') + .addEventListener('change', function () { + socket.sendToServer({ + type: 'set-reading-speed', + readingSpeed: this.value + }); + }); + + document + .getElementById('reading-speed') + .addEventListener('input', function () { + document.getElementById('reading-speed-display').textContent = this.value; + }); + + document + .getElementById('toggle-powermark-only') + .addEventListener('click', function () { + this.blur(); + socket.sendToServer({ + type: 'toggle-powermark-only', + powermarkOnly: this.checked + }); + }); + + document + .getElementById('toggle-rebuzz') + .addEventListener('click', function () { + this.blur(); + socket.sendToServer({ type: 'toggle-rebuzz', rebuzz: this.checked }); + }); + + document + .getElementById('toggle-stop-on-power') + .addEventListener('click', function () { + console.log('stop-on-power checkbox has been checked'); + this.blur(); + socket.sendToServer({ + type: 'toggle-stop-on-power', + stopOnPower: this.checked + }); + }); } const TossupClient = TossupClientMixin(QuestionClient); diff --git a/client/play/mp/MultiplayerTossupBonusClient.js b/client/play/mp/MultiplayerTossupBonusClient.js index c6de9bf5c..88ff32899 100644 --- a/client/play/mp/MultiplayerTossupBonusClient.js +++ b/client/play/mp/MultiplayerTossupBonusClient.js @@ -39,6 +39,7 @@ export const MultiplayerClientMixin = (ClientClass) => class extends ClientClass case 'toggle-lock': return this.toggleLock(data); case 'toggle-login-required': return this.toggleLoginRequired(data); case 'toggle-public': return this.togglePublic(data); + case 'toggle-stop-on-power': return this.toggleStopOnPower(data); default: return super.onmessage(event.data); } } @@ -712,6 +713,10 @@ export const MultiplayerClientMixin = (ClientClass) => class extends ClientClass }); } + stopOnPower ({ stopOnPower, username }) { + this.logEventConditionally(username, `${stopOnPower ? 'enabled' : 'disabled'} stop on power`); + } + vkInit ({ targetUsername, threshold }) { this.logEventConditionally(`A votekick has been started against user ${targetUsername} and needs ${threshold} votes to succeed.`); } diff --git a/client/play/mp/room.html b/client/play/mp/room.html index 2a8ecca22..fa4a5637f 100644 --- a/client/play/mp/room.html +++ b/client/play/mp/room.html @@ -185,6 +185,10 @@

+
+ + +
diff --git a/client/play/mp/room.jsx b/client/play/mp/room.jsx index 66546c0f8..ad3c332d8 100644 --- a/client/play/mp/room.jsx +++ b/client/play/mp/room.jsx @@ -23,16 +23,20 @@ const room = { showingOffline: false, teams: {}, tossup: {}, - username: window.localStorage.getItem('multiplayer-username') || getRandomName() + username: + window.localStorage.getItem('multiplayer-username') || getRandomName() }; let oldCategories = JSON.stringify(room.categoryManager.export()); -const ROOM_NAME = decodeURIComponent(window.location.pathname.split('/').at(-1)); +const ROOM_NAME = decodeURIComponent( + window.location.pathname.split('/').at(-1) +); const USER_ID = window.localStorage.getItem('USER_ID') || 'unknown'; const socket = new window.WebSocket( - window.location.href.replace('http', 'ws').split('?')[0] + '?' + + window.location.href.replace('http', 'ws').split('?')[0] + + '?' + new URLSearchParams({ ...Object.fromEntries(new URLSearchParams(window.location.search)), roomName: ROOM_NAME, @@ -52,7 +56,9 @@ socket.sendToServer = (data) => socket.send(JSON.stringify(data)); socket.onclose = function (event) { const { code } = event; - if (code !== 3000) { window.alert('Disconnected from server'); } + if (code !== 3000) { + window.alert('Disconnected from server'); + } clearInterval(PING_INTERVAL_ID); }; @@ -60,7 +66,12 @@ const client = new MultiplayerTossupBonusClient(room, USER_ID, socket); socket.onmessage = (message) => client.onmessage(message); document.getElementById('answer-input').addEventListener('input', function () { - socket.send(JSON.stringify({ type: 'give-answer-live-update', givenAnswer: this.value })); + socket.send( + JSON.stringify({ + type: 'give-answer-live-update', + givenAnswer: this.value + }) + ); }); document.getElementById('chat').addEventListener('click', function () { @@ -70,55 +81,86 @@ document.getElementById('chat').addEventListener('click', function () { socket.send(JSON.stringify({ type: 'chat-live-update', message: '' })); }); -document.getElementById('chat-form').addEventListener('submit', function (event) { - event.preventDefault(); - event.stopPropagation(); +document + .getElementById('chat-form') + .addEventListener('submit', function (event) { + event.preventDefault(); + event.stopPropagation(); - const message = document.getElementById('chat-input').value; - document.getElementById('chat-input').value = ''; - document.getElementById('chat-input-group').classList.add('d-none'); - document.getElementById('chat-input').blur(); + const message = document.getElementById('chat-input').value; + document.getElementById('chat-input').value = ''; + document.getElementById('chat-input-group').classList.add('d-none'); + document.getElementById('chat-input').blur(); - socket.send(JSON.stringify({ type: 'chat', message })); -}); + socket.send(JSON.stringify({ type: 'chat', message })); + }); document.getElementById('chat-input').addEventListener('input', function () { - socket.send(JSON.stringify({ type: 'chat-live-update', message: this.value })); + socket.send( + JSON.stringify({ type: 'chat-live-update', message: this.value }) + ); }); const styleSheet = document.createElement('style'); -styleSheet.textContent = room.showingOffline ? '' : '.offline { display: none; }'; +styleSheet.textContent = room.showingOffline + ? '' + : '.offline { display: none; }'; document.head.appendChild(styleSheet); -document.getElementById('toggle-offline-players').addEventListener('click', function () { - room.showingOffline = this.checked; - this.blur(); - if (room.showingOffline) { - styleSheet.textContent = ''; - } else { - styleSheet.textContent = '.offline { display: none; }'; - } -}); - -document.getElementById('toggle-controlled').addEventListener('click', function () { - this.blur(); - socket.send(JSON.stringify({ type: 'toggle-controlled', controlled: this.checked })); -}); +document + .getElementById('toggle-offline-players') + .addEventListener('click', function () { + room.showingOffline = this.checked; + this.blur(); + if (room.showingOffline) { + styleSheet.textContent = ''; + } else { + styleSheet.textContent = '.offline { display: none; }'; + } + }); + +document + .getElementById('toggle-controlled') + .addEventListener('click', function () { + this.blur(); + socket.send( + JSON.stringify({ type: 'toggle-controlled', controlled: this.checked }) + ); + }); document.getElementById('toggle-lock').addEventListener('click', function () { this.blur(); socket.send(JSON.stringify({ type: 'toggle-lock', lock: this.checked })); }); -document.getElementById('toggle-login-required').addEventListener('click', function () { - this.blur(); - socket.send(JSON.stringify({ type: 'toggle-login-required', loginRequired: this.checked })); -}); +document + .getElementById('toggle-login-required') + .addEventListener('click', function () { + this.blur(); + socket.send( + JSON.stringify({ + type: 'toggle-login-required', + loginRequired: this.checked + }) + ); + }); document.getElementById('toggle-skip').addEventListener('click', function () { this.blur(); socket.send(JSON.stringify({ type: 'toggle-skip', skip: this.checked })); }); +document + .getElementById('toggle-stop-on-power') + .addEventListener('click', function () { + this.blur(); + socket.send( + JSON.stringify({ + type: 'toggle-stop-on-power', + stopOnPower: this.checked + }) + ); + }); + document.getElementById('toggle-public').addEventListener('click', function () { this.blur(); socket.send(JSON.stringify({ type: 'toggle-public', public: this.checked })); @@ -130,7 +172,13 @@ document.getElementById('reveal').addEventListener('click', function () { }); document.getElementById('username').addEventListener('change', function () { - socket.send(JSON.stringify({ type: 'set-username', userId: USER_ID, username: this.value })); + socket.send( + JSON.stringify({ + type: 'set-username', + userId: USER_ID, + username: this.value + }) + ); room.username = this.value; window.localStorage.setItem('multiplayer-username', room.username); }); @@ -144,7 +192,7 @@ document.addEventListener('keydown', (event) => { socket.send(JSON.stringify({ type: 'chat', message: '' })); } - if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) return; + if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) { return; } switch (event.key?.toLowerCase()) { case ' ': @@ -154,17 +202,27 @@ document.addEventListener('keydown', (event) => { document.getElementById('buzz').click(); } // Prevent spacebar from scrolling the page - if (event.target === document.body) { event.preventDefault(); } + if (event.target === document.body) { + event.preventDefault(); + } break; - case 'e': return document.getElementById('toggle-settings').click(); - case 'k': return document.getElementsByClassName('card-header-clickable')[0].click(); - case 'p': return document.getElementById('pause').click(); - case 't': return document.getElementsByClassName('star-tossup')[0].click(); - case 'y': return navigator.clipboard.writeText(room.tossup._id ?? ''); + case 'e': + return document.getElementById('toggle-settings').click(); + case 'k': + return document + .getElementsByClassName('card-header-clickable')[0] + .click(); + case 'p': + return document.getElementById('pause').click(); + case 't': + return document.getElementsByClassName('star-tossup')[0].click(); + case 'y': + return navigator.clipboard.writeText(room.tossup._id ?? ''); case 'n': - case 's': return document.getElementById('next').click(); + case 's': + return document.getElementById('next').click(); } }); @@ -184,7 +242,12 @@ ReactDOM.createRoot(document.getElementById('category-modal-root')).render( categoryManager={room.categoryManager} onClose={() => { if (oldCategories !== JSON.stringify(room.categoryManager.export())) { - socket.send(JSON.stringify({ type: 'set-categories', ...room.categoryManager.export() })); + socket.send( + JSON.stringify({ + type: 'set-categories', + ...room.categoryManager.export() + }) + ); } oldCategories = JSON.stringify(room.categoryManager.export()); }} @@ -194,6 +257,12 @@ ReactDOM.createRoot(document.getElementById('category-modal-root')).render( ReactDOM.createRoot(document.getElementById('difficulty-dropdown-root')).render( socket.send(JSON.stringify({ type: 'set-difficulties', difficulties: getDropdownValues('difficulties') }))} + onChange={() => + socket.send( + JSON.stringify({ + type: 'set-difficulties', + difficulties: getDropdownValues('difficulties') + }) + )} /> ); diff --git a/client/play/tossups/SoloTossupClient.js b/client/play/tossups/SoloTossupClient.js index 1f8ee763a..f36147ba7 100644 --- a/client/play/tossups/SoloTossupClient.js +++ b/client/play/tossups/SoloTossupClient.js @@ -9,6 +9,7 @@ const settingsVersion = '2024-11-02'; export default class SoloTossupClient extends TossupClient { constructor (room, userId, socket, aiBot) { + console.log('SoloTossupClient constructor called'); super(room, userId, socket); this.aiBot = aiBot; } @@ -16,16 +17,23 @@ export default class SoloTossupClient extends TossupClient { onmessage (message) { const data = JSON.parse(message); switch (data.type) { - case 'clear-stats': return this.clearStats(data); - case 'toggle-ai-mode': return this.toggleAiMode(data); - case 'toggle-correct': return this.toggleCorrect(data); - case 'toggle-type-to-answer': return this.toggleTypeToAnswer(data); - default: return super.onmessage(message); + case 'clear-stats': + return this.clearStats(data); + case 'toggle-ai-mode': + return this.toggleAiMode(data); + case 'toggle-correct': + return this.toggleCorrect(data); + case 'toggle-type-to-answer': + return this.toggleTypeToAnswer(data); + default: + return super.onmessage(message); } } buzz ({ timer, userId, username }) { - if (userId !== this.USER_ID) { return; } + if (userId !== this.USER_ID) { + return; + } super.buzz({ userId }); @@ -44,9 +52,19 @@ export default class SoloTossupClient extends TossupClient { endCurrentTossup ({ isSkip, starred, tossup }) { super.endCurrentTossup({ starred, tossup }); - if (!isSkip && this.room.previousTossup.userId === this.USER_ID && (this.room.mode !== MODE_ENUM.LOCAL)) { + 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.inPower + ? previous.powerValue + : 10 + : previous.endOfQuestion + ? 0 + : previous.negValue; questionStats.recordTossup({ _id: previous.tossup._id, celerity: previous.celerity, @@ -57,10 +75,19 @@ export default class SoloTossupClient extends TossupClient { } } - async giveTossupAnswer ({ directive, directedPrompt, perQuestionCelerity, score, tossup, userId }) { + async giveTossupAnswer ({ + directive, + directedPrompt, + perQuestionCelerity, + score, + tossup, + userId + }) { super.giveTossupAnswer({ directive, directedPrompt, score, userId }); - if (directive === 'prompt') { return; } + if (directive === 'prompt') { + return; + } if (userId === this.USER_ID) { this.updateStatDisplay(this.room.players[this.USER_ID]); @@ -92,125 +119,205 @@ export default class SoloTossupClient extends TossupClient { document.getElementById('next').textContent = 'Next'; document.getElementById('toggle-correct').classList.remove('d-none'); - document.getElementById('toggle-correct').textContent = this.room.previousTossup.isCorrect ? 'I was wrong' : 'I was right'; + document.getElementById('toggle-correct').textContent = this.room + .previousTossup.isCorrect + ? 'I was wrong' + : 'I was right'; } - setCategories ({ alternateSubcategories, categories, subcategories, percentView, categoryPercents }) { + setCategories ({ + alternateSubcategories, + categories, + subcategories, + percentView, + categoryPercents + }) { super.setCategories(); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } setDifficulties ({ difficulties }) { - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } setMaxYear ({ maxYear }) { super.setMaxYear({ maxYear }); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } setMinYear ({ minYear }) { super.setMinYear({ minYear }); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } setPacketNumbers ({ packetNumbers }) { super.setPacketNumbers({ packetNumbers }); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } setReadingSpeed ({ readingSpeed }) { super.setReadingSpeed({ readingSpeed }); - window.localStorage.setItem('singleplayer-tossup-settings', JSON.stringify({ ...this.room.settings, version: settingsVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); } async setSetName ({ setName, setLength }) { super.setSetName({ setName, setLength }); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } setStrictness ({ strictness }) { super.setStrictness({ strictness }); - window.localStorage.setItem('singleplayer-tossup-settings', JSON.stringify({ ...this.room.settings, version: settingsVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); } toggleAiMode ({ aiMode }) { - if (aiMode) { upsertPlayerItem(this.aiBot.player); } + if (aiMode) { + upsertPlayerItem(this.aiBot.player); + } this.aiBot.active = aiMode; document.getElementById('ai-settings').disabled = !aiMode; document.getElementById('toggle-ai-mode').checked = aiMode; - document.getElementById('player-list-group').classList.toggle('d-none', !aiMode); - document.getElementById('player-list-group-hr').classList.toggle('d-none', !aiMode); - window.localStorage.setItem('singleplayer-tossup-settings', JSON.stringify({ ...this.room.settings, version: settingsVersion })); + document + .getElementById('player-list-group') + .classList.toggle('d-none', !aiMode); + document + .getElementById('player-list-group-hr') + .classList.toggle('d-none', !aiMode); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); } toggleCorrect ({ correct, userId }) { this.updateStatDisplay(this.room.players[this.USER_ID]); - document.getElementById('toggle-correct').textContent = correct ? 'I was wrong' : 'I was right'; + document.getElementById('toggle-correct').textContent = correct + ? 'I was wrong' + : 'I was right'; } togglePowermarkOnly ({ powermarkOnly }) { super.togglePowermarkOnly({ powermarkOnly }); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } toggleRebuzz ({ rebuzz }) { super.toggleRebuzz({ rebuzz }); - window.localStorage.setItem('singleplayer-tossup-settings', JSON.stringify({ ...this.room.settings, version: settingsVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); + } + + toggleStopOnPower ({ stopOnPower }) { + console.log('SoloTossupClient.toggleStopOnPower called'); + super.toggleStopOnPower({ stopOnPower }); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); } setMode ({ mode }) { switch (mode) { case MODE_ENUM.SET_NAME: - document.getElementById('local-packet-settings').classList.add('d-none'); + document + .getElementById('local-packet-settings') + .classList.add('d-none'); break; case MODE_ENUM.RANDOM: - document.getElementById('local-packet-settings').classList.add('d-none'); + document + .getElementById('local-packet-settings') + .classList.add('d-none'); break; case MODE_ENUM.STARRED: document.getElementById('difficulty-settings').classList.add('d-none'); - document.getElementById('local-packet-settings').classList.add('d-none'); + document + .getElementById('local-packet-settings') + .classList.add('d-none'); document.getElementById('set-settings').classList.add('d-none'); document.getElementById('toggle-powermark-only').disabled = true; document.getElementById('toggle-standard-only').disabled = true; break; case MODE_ENUM.LOCAL: document.getElementById('difficulty-settings').classList.add('d-none'); - document.getElementById('local-packet-settings').classList.remove('d-none'); + document + .getElementById('local-packet-settings') + .classList.remove('d-none'); document.getElementById('set-settings').classList.add('d-none'); document.getElementById('toggle-powermark-only').disabled = true; document.getElementById('toggle-standard-only').disabled = true; break; } super.setMode({ mode }); - window.localStorage.setItem('singleplayer-tossup-mode', JSON.stringify({ mode, version: modeVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-mode', + JSON.stringify({ mode, version: modeVersion }) + ); } toggleStandardOnly ({ standardOnly }) { super.toggleStandardOnly({ standardOnly }); - window.localStorage.setItem('singleplayer-tossup-query', JSON.stringify({ ...this.room.query, version: queryVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-query', + JSON.stringify({ ...this.room.query, version: queryVersion }) + ); } toggleTimer ({ timer }) { super.toggleTimer({ timer }); - window.localStorage.setItem('singleplayer-tossup-settings', JSON.stringify({ ...this.room.settings, version: settingsVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); } toggleTypeToAnswer ({ typeToAnswer }) { document.getElementById('type-to-answer').checked = typeToAnswer; - window.localStorage.setItem('singleplayer-tossup-settings', JSON.stringify({ ...this.room.settings, version: settingsVersion })); + window.localStorage.setItem( + 'singleplayer-tossup-settings', + JSON.stringify({ ...this.room.settings, version: settingsVersion }) + ); } /** - * Updates the displayed stat line. - */ + * Updates the displayed stat line. + */ updateStatDisplay ({ 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})`; + const plural = tuh === 1 ? '' : 's'; + document.getElementById('statline').innerHTML = + `${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); + document.getElementById('clear-stats').disabled = tuh === 0; } } diff --git a/client/play/tossups/index.html b/client/play/tossups/index.html index e6409c303..fdae302ee 100644 --- a/client/play/tossups/index.html +++ b/client/play/tossups/index.html @@ -173,6 +173,10 @@

+
+ + +
diff --git a/client/play/tossups/index.jsx b/client/play/tossups/index.jsx index 621ca9c48..f5a83d332 100644 --- a/client/play/tossups/index.jsx +++ b/client/play/tossups/index.jsx @@ -123,6 +123,7 @@ if (window.localStorage.getItem('singleplayer-tossup-settings')) { socket.sendToServer({ type: 'set-reading-speed', ...savedSettings }); socket.sendToServer({ type: 'toggle-ai-mode', ...savedSettings }); socket.sendToServer({ type: 'toggle-rebuzz', ...savedSettings }); + socket.sendToServer({ type: 'toggle-stop-on-power', ...savedSettings }); socket.sendToServer({ type: 'toggle-timer', ...savedSettings }); socket.sendToServer({ type: 'toggle-type-to-answer', ...savedSettings }); } catch { diff --git a/quizbowl/TossupRoom.js b/quizbowl/TossupRoom.js index ea301e50a..58dd2632e 100644 --- a/quizbowl/TossupRoom.js +++ b/quizbowl/TossupRoom.js @@ -1,293 +1,450 @@ -import { ANSWER_TIME_LIMIT, DEAD_TIME_LIMIT, MODE_ENUM, TOSSUP_PROGRESS_ENUM } from './constants.js'; +import { + ANSWER_TIME_LIMIT, + DEAD_TIME_LIMIT, + MODE_ENUM, + TOSSUP_PROGRESS_ENUM +} from './constants.js'; import insertTokensIntoHTML from './insert-tokens-into-html.js'; import QuestionRoom from './QuestionRoom.js'; -export const TossupRoomMixin = (QuestionRoomClass) => class extends QuestionRoomClass { - constructor (name, categoryManager, supportedQuestionTypes = ['tossup']) { - super(name, categoryManager, supportedQuestionTypes); - - this.timeoutID = null; - /** - * @type {string | null} - * The userId of the player who buzzed in. - * We should ensure that buzzedIn is null before calling any readQuestion. - */ - this.buzzedIn = null; - this.buzzes = []; - this.buzzpointIndices = []; - this.liveAnswer = ''; - this.paused = false; - this.questionSplit = []; - this.tossup = {}; - this.tossupProgress = TOSSUP_PROGRESS_ENUM.NOT_STARTED; - this.wordIndex = 0; - - this.query = { - ...this.query, - powermarkOnly: false - }; - - this.settings = { - ...this.settings, - rebuzz: false, - readingSpeed: 50 - }; - - this.previousTossup = { - celerity: 0, - endOfQuestion: false, - isCorrect: true, - inPower: false, - negValue: -5, - powerValue: 15, - tossup: {}, - userId: null - }; - } - - async message (userId, message) { - switch (message.type) { - case 'buzz': return this.buzz(userId, message); - case 'give-answer': return this.giveTossupAnswer(userId, message); - case 'next': return this.next(userId, message); - case 'pause': return this.pause(userId, message); - case 'set-reading-speed': return this.setReadingSpeed(userId, message); - case 'toggle-powermark-only': return this.togglePowermarkOnly(userId, message); - case 'toggle-rebuzz': return this.toggleRebuzz(userId, message); - default: return super.message(userId, message); +export const TossupRoomMixin = (QuestionRoomClass) => + class extends QuestionRoomClass { + constructor (name, categoryManager, supportedQuestionTypes = ['tossup']) { + super(name, categoryManager, supportedQuestionTypes); + + this.timeoutID = null; + /** + * @type {string | null} + * The userId of the player who buzzed in. + * We should ensure that buzzedIn is null before calling any readQuestion. + */ + this.buzzedIn = null; + this.buzzes = []; + this.buzzpointIndices = []; + this.liveAnswer = ''; + this.paused = false; + this.questionSplit = []; + this.tossup = {}; + this.stopOnPowerEnded = false; + this.tossupProgress = TOSSUP_PROGRESS_ENUM.NOT_STARTED; + this.wordIndex = 0; + + this.query = { + ...this.query, + powermarkOnly: false + }; + + this.settings = { + ...this.settings, + rebuzz: false, + stopOnPower: false, + readingSpeed: 50 + }; + + this.previousTossup = { + celerity: 0, + endOfQuestion: false, + isCorrect: true, + inPower: false, + negValue: -5, + powerValue: 15, + tossup: {}, + userId: null + }; } - } - buzz (userId) { - if (!this.settings.rebuzz && this.buzzes.includes(userId)) { return; } - if (this.tossupProgress !== TOSSUP_PROGRESS_ENUM.READING) { return; } - - const username = this.players[userId].username; - if (this.buzzedIn) { - return this.emitMessage({ type: 'lost-buzzer-race', userId, username }); + async message (userId, message) { + switch (message.type) { + case 'buzz': + return this.buzz(userId, message); + case 'give-answer': + return this.giveTossupAnswer(userId, message); + case 'next': + return this.next(userId, message); + case 'pause': + return this.pause(userId, message); + case 'set-reading-speed': + return this.setReadingSpeed(userId, message); + case 'toggle-powermark-only': + return this.togglePowermarkOnly(userId, message); + case 'toggle-rebuzz': + return this.toggleRebuzz(userId, message); + case 'toggle-stop-on-power': + return this.toggleStopOnPower(userId, message); + default: + return super.message(userId, message); + } } - clearTimeout(this.timeoutID); - this.buzzedIn = userId; - this.buzzes.push(userId); - this.buzzpointIndices.push(this.questionSplit.slice(0, this.wordIndex).join(' ').length); - this.paused = false; + buzz (userId) { + if (!this.settings.rebuzz && this.buzzes.includes(userId)) { + return; + } + if (this.tossupProgress !== TOSSUP_PROGRESS_ENUM.READING) { + return; + } - this.emitMessage({ type: 'buzz', userId, username }); - this.emitMessage({ type: 'update-question', word: '(#)' }); + const username = this.players[userId].username; + if (this.buzzedIn) { + return this.emitMessage({ type: 'lost-buzzer-race', userId, username }); + } - this.startServerTimer( - ANSWER_TIME_LIMIT * 10, - (time) => this.emitMessage({ type: 'timer-update', timeRemaining: time }), - () => this.giveTossupAnswer(userId, { givenAnswer: this.liveAnswer }) - ); - } + clearTimeout(this.timeoutID); + this.buzzedIn = userId; + this.buzzes.push(userId); + this.buzzpointIndices.push( + this.questionSplit.slice(0, this.wordIndex).join(' ').length + ); + this.paused = false; - endCurrentTossup (userId) { - if (this.buzzedIn) { return false; } // prevents skipping when someone has buzzed in - if (this.queryingQuestion) { return false; } - const isSkip = this.tossupProgress === TOSSUP_PROGRESS_ENUM.READING; - if (isSkip && !this.settings.skip) { return false; } + this.emitMessage({ type: 'buzz', userId, username }); + this.emitMessage({ type: 'update-question', word: '(#)' }); - clearInterval(this.timer.interval); - clearTimeout(this.timeoutID); - this.emitMessage({ type: 'timer-update', timeRemaining: 0 }); + this.startServerTimer( + ANSWER_TIME_LIMIT * 10, + (time) => + this.emitMessage({ type: 'timer-update', timeRemaining: time }), + () => this.giveTossupAnswer(userId, { givenAnswer: this.liveAnswer }) + ); + } - this.buzzedIn = null; - this.buzzes = []; - this.buzzpointIndices = []; - this.paused = false; + endCurrentTossup (userId) { + if (this.buzzedIn) { + return false; + } // prevents skipping when someone has buzzed in + if (this.queryingQuestion) { + return false; + } + const isSkip = this.tossupProgress === TOSSUP_PROGRESS_ENUM.READING; + if (isSkip && !this.settings.skip) { + return false; + } - if (this.tossupProgress !== TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED) { this.revealTossupAnswer(); } + clearInterval(this.timer.interval); + clearTimeout(this.timeoutID); + this.emitMessage({ type: 'timer-update', timeRemaining: 0 }); - const starred = this.mode === MODE_ENUM.STARRED ? true : (this.mode === MODE_ENUM.LOCAL ? false : null); - this.emitMessage({ type: 'end-current-tossup', isSkip, starred, tossup: this.tossup }); - return true; - } + this.buzzedIn = null; + this.buzzes = []; + this.buzzpointIndices = []; + this.paused = false; - giveTossupAnswer (userId, { givenAnswer }) { - if (typeof givenAnswer !== 'string') { return false; } - if (this.buzzedIn !== userId) { return false; } + if (this.tossupProgress !== TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED) { + this.revealTossupAnswer(); + } + + const starred = + this.mode === MODE_ENUM.STARRED + ? true + : this.mode === MODE_ENUM.LOCAL + ? false + : null; + this.emitMessage({ + type: 'end-current-tossup', + isSkip, + starred, + tossup: this.tossup + }); + return true; + } - this.liveAnswer = ''; - clearInterval(this.timer.interval); - this.emitMessage({ type: 'timer-update', timeRemaining: ANSWER_TIME_LIMIT * 10 }); + giveTossupAnswer (userId, { givenAnswer }) { + if (typeof givenAnswer !== 'string') { + return false; + } + if (this.buzzedIn !== userId) { + return false; + } - if (Object.keys(this.tossup || {}).length === 0) { return; } + this.liveAnswer = ''; + clearInterval(this.timer.interval); + this.emitMessage({ + type: 'timer-update', + timeRemaining: ANSWER_TIME_LIMIT * 10 + }); + + if (Object.keys(this.tossup || {}).length === 0) { + return; + } + + const { celerity, directive, directedPrompt, points } = this.scoreTossup({ + givenAnswer + }); + + switch (directive) { + case 'accept': + this.buzzedIn = null; + this.revealTossupAnswer(); + this.players[userId].updateStats(points, celerity); + Object.values(this.players).forEach((player) => { + player.tuh++; + }); + break; + case 'reject': + this.buzzedIn = null; + this.players[userId].updateStats(points, celerity); + if ( + !this.settings.rebuzz && + this.buzzes.length === Object.keys(this.sockets).length + ) { + this.revealTossupAnswer(); + Object.values(this.players).forEach((player) => { + player.tuh++; + }); + } else { + this.readQuestion(Date.now()); + } + break; + case 'prompt': + this.startServerTimer( + ANSWER_TIME_LIMIT * 10, + (time) => + this.emitMessage({ type: 'timer-update', timeRemaining: time }), + () => + this.giveTossupAnswer(userId, { givenAnswer: this.liveAnswer }) + ); + } + + this.emitMessage({ + type: 'give-tossup-answer', + userId, + username: this.players[userId].username, + givenAnswer, + directive, + directedPrompt, + score: points, + celerity: this.players[userId].celerity.correct.average, + // the below fields are used to record buzzpoint data + tossup: this.tossup, + perQuestionCelerity: celerity + }); + } - const { celerity, directive, directedPrompt, points } = this.scoreTossup({ givenAnswer }); + async next (userId) { + if (this.tossupProgress === TOSSUP_PROGRESS_ENUM.NOT_STARTED) { + return await this.startNextTossup(userId); + } + const allowed = this.endCurrentTossup(userId); + if (allowed) { + await this.startNextTossup(userId); + } + } - switch (directive) { - case 'accept': - this.buzzedIn = null; - this.revealTossupAnswer(); - this.players[userId].updateStats(points, celerity); - Object.values(this.players).forEach(player => { player.tuh++; }); - break; - case 'reject': - this.buzzedIn = null; - this.players[userId].updateStats(points, celerity); - if (!this.settings.rebuzz && this.buzzes.length === Object.keys(this.sockets).length) { - this.revealTossupAnswer(); - Object.values(this.players).forEach(player => { player.tuh++; }); - } else { - this.readQuestion(Date.now()); - } - break; - case 'prompt': + pause (userId) { + if (this.buzzedIn) { + return false; + } + if (this.tossupProgress === TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED) { + return false; + } + + this.paused = !this.paused; + if (this.paused) { + clearTimeout(this.timeoutID); + clearInterval(this.timer.interval); + } else if (this.wordIndex >= this.questionSplit.length) { this.startServerTimer( - ANSWER_TIME_LIMIT * 10, - (time) => this.emitMessage({ type: 'timer-update', timeRemaining: time }), - () => this.giveTossupAnswer(userId, { givenAnswer: this.liveAnswer }) + this.timer.timeRemaining, + (time) => + this.emitMessage({ type: 'timer-update', timeRemaining: time }), + () => this.revealTossupAnswer() ); + } else { + this.readQuestion(Date.now()); + } + const username = this.players[userId].username; + this.emitMessage({ type: 'pause', paused: this.paused, username }); } - this.emitMessage({ - type: 'give-tossup-answer', - userId, - username: this.players[userId].username, - givenAnswer, - directive, - directedPrompt, - score: points, - celerity: this.players[userId].celerity.correct.average, - // the below fields are used to record buzzpoint data - tossup: this.tossup, - perQuestionCelerity: celerity - }); - } - - async next (userId) { - if (this.tossupProgress === TOSSUP_PROGRESS_ENUM.NOT_STARTED) { - return await this.startNextTossup(userId); + async readQuestion (expectedReadTime) { + if (Object.keys(this.tossup || {}).length === 0) { + return; + } + if (this.wordIndex >= this.questionSplit.length) { + this.startServerTimer( + DEAD_TIME_LIMIT * 10, + (time) => + this.emitMessage({ type: 'timer-update', timeRemaining: time }), + () => this.revealTossupAnswer() + ); + return; + } + + const word = this.questionSplit[this.wordIndex]; + + // stop reading and start timer if power and stopOnPower is enabled + if ((word === '(*)' || word === '[*]') && this.settings.stopOnPower) { + this.stopOnPowerEnded = true; + this.startServerTimer( + DEAD_TIME_LIMIT * 10, + (time) => + this.emitMessage({ type: 'timer-update', timeRemaining: time }), + () => this.revealTossupAnswer() + ); + return; + } + + this.wordIndex++; + this.emitMessage({ type: 'update-question', word }); + + // calculate time needed before reading next word + let time = Math.log(word.length) + 1; + if ( + (word.endsWith('.') && + word.charCodeAt(word.length - 2) > 96 && + word.charCodeAt(word.length - 2) < 123) || + word.slice(-2) === '.\u201d' || + word.slice(-2) === '!\u201d' || + word.slice(-2) === '?\u201d' + ) { + time += 2.5; + } else if (word.endsWith(',') || word.slice(-2) === ',\u201d') { + time += 1.5; + } else if (word === '(*)' || word === '[*]') { + time = 0; + } + + time = time * 0.9 * (140 - this.settings.readingSpeed); + const delay = time - Date.now() + expectedReadTime; + + this.timeoutID = setTimeout(() => { + this.readQuestion(time + expectedReadTime); + }, delay); } - const allowed = this.endCurrentTossup(userId); - if (allowed) { await this.startNextTossup(userId); } - } - pause (userId) { - if (this.buzzedIn) { return false; } - if (this.tossupProgress === TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED) { return false; } + revealTossupAnswer () { + if (Object.keys(this.tossup || {}).length === 0) return; + this.tossupProgress = TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED; + this.tossup.markedQuestion = insertTokensIntoHTML( + this.tossup.question, + this.tossup.question_sanitized, + { ' (#) ': this.buzzpointIndices } + ); + this.emitMessage({ + type: 'reveal-tossup-answer', + question: insertTokensIntoHTML( + this.tossup.question, + this.tossup.question_sanitized, + { ' (#) ': this.buzzpointIndices } + ), + answer: this.tossup.answer + }); + } - this.paused = !this.paused; - if (this.paused) { - clearTimeout(this.timeoutID); - clearInterval(this.timer.interval); - } else if (this.wordIndex >= this.questionSplit.length) { - this.startServerTimer( - this.timer.timeRemaining, - (time) => this.emitMessage({ type: 'timer-update', timeRemaining: time }), - () => this.revealTossupAnswer() + scoreTossup ({ givenAnswer }) { + const celerity = + this.questionSplit.slice(this.wordIndex).join(' ').length / + this.tossup.question.length; + const endOfQuestion = this.settings.stopOnPower + ? this.stopOnPowerEnded + : this.wordIndex === this.questionSplit.length; + const inPower = + Math.max( + this.questionSplit.indexOf('(*)'), + this.questionSplit.indexOf('[*]') + ) >= this.wordIndex; + const { directive, directedPrompt } = this.checkAnswer( + this.tossup.answer, + givenAnswer, + this.settings.strictness ); - } else { + const isCorrect = directive === 'accept'; + const points = this.settings.stopOnPower + ? isCorrect + ? 10 + : this.stopOnPowerEnded + ? 0 + : this.previousTossup.negValue + : isCorrect + ? inPower + ? this.previousTossup.powerValue + : 10 + : endOfQuestion + ? 0 + : this.previousTossup.negValue; + + this.previousTossup = { + ...this.previousTossup, + celerity, + endOfQuestion, + inPower, + isCorrect, + tossup: this.tossup, + userId: this.buzzedIn + }; + + return { + celerity, + directive, + directedPrompt, + endOfQuestion, + inPower, + points + }; + } + + setReadingSpeed (userId, { readingSpeed }) { + if (isNaN(readingSpeed)) { + return false; + } + if (readingSpeed > 100) { + readingSpeed = 100; + } + if (readingSpeed < 0) { + readingSpeed = 0; + } + + this.settings.readingSpeed = readingSpeed; + const username = this.players[userId].username; + this.emitMessage({ type: 'set-reading-speed', username, readingSpeed }); + } + + async startNextTossup (userId) { + const username = this.players[userId].username; + this.tossup = await this.getNextQuestion('tossups'); + this.queryingQuestion = false; + if (!this.tossup) { + return; + } + this.emitMessage({ + type: 'start-next-tossup', + packetLength: this.packet.tossups.length, + tossup: this.tossup, + userId, + username + }); + this.questionSplit = this.tossup.question_sanitized + .split(' ') + .filter((word) => word !== ''); + this.wordIndex = 0; + this.tossupProgress = TOSSUP_PROGRESS_ENUM.READING; + clearTimeout(this.timeoutID); this.readQuestion(Date.now()); } - const username = this.players[userId].username; - this.emitMessage({ type: 'pause', paused: this.paused, username }); - } - async readQuestion (expectedReadTime) { - if (Object.keys(this.tossup || {}).length === 0) { return; } - if (this.wordIndex >= this.questionSplit.length) { - this.startServerTimer( - DEAD_TIME_LIMIT * 10, - (time) => this.emitMessage({ type: 'timer-update', timeRemaining: time }), - () => this.revealTossupAnswer() - ); - return; + togglePowermarkOnly (userId, { powermarkOnly }) { + this.query.powermarkOnly = powermarkOnly; + const username = this.players[userId].username; + this.adjustQuery(['powermarkOnly'], [powermarkOnly]); + this.emitMessage({ + type: 'toggle-powermark-only', + powermarkOnly, + username + }); } - const word = this.questionSplit[this.wordIndex]; - this.wordIndex++; - this.emitMessage({ type: 'update-question', word }); - - // calculate time needed before reading next word - let time = Math.log(word.length) + 1; - if ((word.endsWith('.') && word.charCodeAt(word.length - 2) > 96 && word.charCodeAt(word.length - 2) < 123) || - word.slice(-2) === '.\u201d' || word.slice(-2) === '!\u201d' || word.slice(-2) === '?\u201d') { - time += 2.5; - } else if (word.endsWith(',') || word.slice(-2) === ',\u201d') { - time += 1.5; - } else if (word === '(*)' || word === '[*]') { - time = 0; + toggleRebuzz (userId, { rebuzz }) { + this.settings.rebuzz = rebuzz; + const username = this.players[userId].username; + this.emitMessage({ type: 'toggle-rebuzz', rebuzz, username }); } - time = time * 0.9 * (140 - this.settings.readingSpeed); - const delay = time - Date.now() + expectedReadTime; - - this.timeoutID = setTimeout(() => { - this.readQuestion(time + expectedReadTime); - }, delay); - } - - revealTossupAnswer () { - if (Object.keys(this.tossup || {}).length === 0) return; - this.tossupProgress = TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED; - this.tossup.markedQuestion = insertTokensIntoHTML(this.tossup.question, this.tossup.question_sanitized, { ' (#) ': this.buzzpointIndices }); - this.emitMessage({ - type: 'reveal-tossup-answer', - question: insertTokensIntoHTML(this.tossup.question, this.tossup.question_sanitized, { ' (#) ': this.buzzpointIndices }), - answer: this.tossup.answer - }); - } - - 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 { 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); - - this.previousTossup = { - ...this.previousTossup, - celerity, - endOfQuestion, - inPower, - isCorrect, - tossup: this.tossup, - userId: this.buzzedIn - }; - - return { celerity, directive, directedPrompt, endOfQuestion, inPower, points }; - } - - setReadingSpeed (userId, { readingSpeed }) { - if (isNaN(readingSpeed)) { return false; } - if (readingSpeed > 100) { readingSpeed = 100; } - if (readingSpeed < 0) { readingSpeed = 0; } - - this.settings.readingSpeed = readingSpeed; - const username = this.players[userId].username; - this.emitMessage({ type: 'set-reading-speed', username, readingSpeed }); - } - - async startNextTossup (userId) { - const username = this.players[userId].username; - this.tossup = await this.getNextQuestion('tossups'); - this.queryingQuestion = false; - if (!this.tossup) { return; } - this.emitMessage({ type: 'start-next-tossup', packetLength: this.packet.tossups.length, tossup: this.tossup, userId, username }); - this.questionSplit = this.tossup.question_sanitized.split(' ').filter(word => word !== ''); - this.wordIndex = 0; - this.tossupProgress = TOSSUP_PROGRESS_ENUM.READING; - clearTimeout(this.timeoutID); - this.readQuestion(Date.now()); - } - - togglePowermarkOnly (userId, { powermarkOnly }) { - this.query.powermarkOnly = powermarkOnly; - const username = this.players[userId].username; - this.adjustQuery(['powermarkOnly'], [powermarkOnly]); - this.emitMessage({ type: 'toggle-powermark-only', powermarkOnly, username }); - } - - toggleRebuzz (userId, { rebuzz }) { - this.settings.rebuzz = rebuzz; - const username = this.players[userId].username; - this.emitMessage({ type: 'toggle-rebuzz', rebuzz, username }); - } -}; + toggleStopOnPower (userId, { stopOnPower }) { + this.settings.stopOnPower = stopOnPower; + const username = this.players[userId].username; + this.emitMessage({ type: 'toggle-stop-on-power', stopOnPower, username }); + } + }; const TossupRoom = TossupRoomMixin(QuestionRoom); export default TossupRoom;