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/mp/MultiplayerTossupBonusClient.js b/client/play/mp/MultiplayerTossupBonusClient.js
index cd53eee31..1cb5b9dbd 100644
--- a/client/play/mp/MultiplayerTossupBonusClient.js
+++ b/client/play/mp/MultiplayerTossupBonusClient.js
@@ -338,7 +338,9 @@ export const MultiplayerClientMixin = (ClientClass) => class extends ClientClass
document.getElementById('buzz').disabled = !document.getElementById('toggle-rebuzz').checked && userId === this.USER_ID;
}
- if (score > 10) {
+ if (score === 20) {
+ this.room.players[userId].superpowers++;
+ } else if (score === 15) {
this.room.players[userId].powers++;
} else if (score === 10) {
this.room.players[userId].tens++;
diff --git a/client/play/tossups/SoloTossupClient.js b/client/play/tossups/SoloTossupClient.js
index 1f8ee763a..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.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 43c82d30e..9270b96cc 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].superpowers += 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 {
@@ -103,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', `
+ - Superpowers${superpowers}
- Powers${powers}
- Tens${tens}
- Negs${negs}
diff --git a/client/user/stats/tossup/graph.js b/client/user/stats/tossup/graph.js
index bacd9aa06..aa07e78f4 100644
--- a/client/user/stats/tossup/graph.js
+++ b/client/user/stats/tossup/graph.js
@@ -20,6 +20,7 @@ function showTossupGraphStats ({ cumulative = false, difficulties = '', filterLo
labels: stats.map(stat => stat._id),
datasets: [
{ data: accumulate(stats.map(stat => stat.count)), label: 'Total', borderColor: '#3e95cd', fill: false },
+ { data: accumulate(stats.map(stat => stat.superpowers)), label: 'Superpowers', borderColor: '#ff9f40', fill: false },
{ data: accumulate(stats.map(stat => stat.powers)), label: 'Powers', borderColor: '#8e5ea2', fill: false },
{ data: accumulate(stats.map(stat => stat.tens)), label: 'Tens', borderColor: '#3cba9f', fill: false },
{ data: accumulate(stats.map(stat => stat.deads)), label: 'Dead', borderColor: '#e8c3b9', fill: false },
@@ -31,6 +32,7 @@ function showTossupGraphStats ({ cumulative = false, difficulties = '', filterLo
labels: stats.map(stat => stat._id),
datasets: [
{ data: stats.map(stat => stat.count), label: 'Total', borderColor: '#3e95cd', fill: false },
+ { data: stats.map(stat => stat.superpowers), label: 'Superpowers', borderColor: '#ff9f40', fill: false },
{ data: stats.map(stat => stat.powers), label: 'Powers', borderColor: '#8e5ea2', fill: false },
{ data: stats.map(stat => stat.tens), label: 'Tens', borderColor: '#3cba9f', fill: false },
{ data: stats.map(stat => stat.deads), label: 'Dead', borderColor: '#e8c3b9', fill: false },
@@ -49,7 +51,7 @@ function showTossupGraphStats ({ cumulative = false, difficulties = '', filterLo
resultPerTossup.data = {
labels: stats.map(stat => stat._id),
datasets: [
- { data: stats.map(stat => 100 * stat.powers / stat.count), label: 'Power Percentage', borderColor: '#3e95cd', fill: false },
+ { data: stats.map(stat => 100 * (stat.superpowers + stat.powers) / stat.count), label: 'Power Percentage', borderColor: '#3e95cd', fill: false },
{ data: stats.map(stat => 100 * (stat.deads + stat.negs) / stat.count), label: 'Neg or Dead Percentage', borderColor: '#8e5ea2', fill: false }
]
};
diff --git a/client/user/stats/tossup/index.html b/client/user/stats/tossup/index.html
index bf1efffb6..277e58c62 100644
--- a/client/user/stats/tossup/index.html
+++ b/client/user/stats/tossup/index.html
@@ -189,6 +189,7 @@
| Category |
TUH |
+ 20 |
15 |
10 |
-5 |
@@ -208,6 +209,7 @@
| Subcategory |
TUH |
+ 20 |
15 |
10 |
-5 |
@@ -227,6 +229,7 @@
| Alternate Subcategory |
TUH |
+ 20 |
15 |
10 |
-5 |
@@ -246,6 +249,7 @@
| Set Name |
TUH |
+ 20 |
15 |
10 |
-5 |
diff --git a/client/user/stats/tossup/index.js b/client/user/stats/tossup/index.js
index 290002f7b..baba39aab 100644
--- a/client/user/stats/tossup/index.js
+++ b/client/user/stats/tossup/index.js
@@ -41,6 +41,7 @@ async function fetchTossupStats ({ difficulties = '', setName = '', includeMulti
| ${stat._id} |
${stat.count} |
+ ${stat['20s']} |
${stat['15s']} |
${stat['10s']} |
${stat['-5s']} |
@@ -68,6 +69,7 @@ async function fetchTossupStats ({ difficulties = '', setName = '', includeMulti
| Total |
${totalStats.count ?? 0} |
+ ${totalStats['20s'] ?? 0} |
${totalStats['15s'] ?? 0} |
${totalStats['10s'] ?? 0} |
${totalStats['-5s'] ?? 0} |
@@ -93,7 +95,7 @@ document.getElementById('form').addEventListener('submit', event => {
attachDropdownChecklist();
fetchTossupStats();
-const isNumericColumn = [false, true, true, true, true, true, true, true];
+const isNumericColumn = [false, true, true, true, true, true, true, true, true];
document.getElementById('set-stats').querySelectorAll('th').forEach((th, index) => {
th.addEventListener('click', () => sortTable(index, isNumericColumn[index], 'set-stats-body', 0, 0));
diff --git a/database/account-info/question-stats/get-single-tossup-stats.js b/database/account-info/question-stats/get-single-tossup-stats.js
index 4a57ee787..cb5d7cdba 100644
--- a/database/account-info/question-stats/get-single-tossup-stats.js
+++ b/database/account-info/question-stats/get-single-tossup-stats.js
@@ -12,8 +12,8 @@ async function getSingleTossupStats (tossupId) {
// data should always be an array
if (!Array.isArray(data) || data.length === 0) { return null; }
- // guarantee that we include 15, 10, 0, and -5
- const resultCounts = { 15: 0, 10: 0, 0: 0, '-5': 0 };
+ // guarantee that we include 20, 15, 10, 0, and -5
+ const resultCounts = { 20: 0, 15: 0, 10: 0, 0: 0, '-5': 0 };
// below is just in case there are other point values
const pointValues = new Set(data.map(d => d.pointValue));
for (const pointValue of pointValues) {
diff --git a/database/account-info/user-stats/get-summary-tossup-stats.js b/database/account-info/user-stats/get-summary-tossup-stats.js
index fa1f1c918..83eea8696 100644
--- a/database/account-info/user-stats/get-summary-tossup-stats.js
+++ b/database/account-info/user-stats/get-summary-tossup-stats.js
@@ -22,7 +22,8 @@ export default async function getSummaryTossupStats (userId, groupByField, query
{ $match: { 'data.user_id': userId } },
{
$addFields: {
- is15: { $gt: ['$data.pointValue', 10] },
+ is20: { $eq: ['$data.pointValue', 20] },
+ is15: { $eq: ['$data.pointValue', 15] },
is10: { $eq: ['$data.pointValue', 10] },
isNeg5: { $lt: ['$data.pointValue', 0] }
}
@@ -32,6 +33,7 @@ export default async function getSummaryTossupStats (userId, groupByField, query
numCorrect: { $sum: { $cond: ['$data.isCorrect', 1, 0] } },
_id: '$' + groupByField,
count: { $sum: 1 },
+ '20s': { $sum: { $cond: ['$is20', 1, 0] } },
'15s': { $sum: { $cond: ['$is15', 1, 0] } },
'10s': { $sum: { $cond: ['$is10', 1, 0] } },
'-5s': { $sum: { $cond: ['$isNeg5', 1, 0] } },
diff --git a/database/account-info/user-stats/get-tossup-graph-stats.js b/database/account-info/user-stats/get-tossup-graph-stats.js
index c598d74ad..3e16922b3 100644
--- a/database/account-info/user-stats/get-tossup-graph-stats.js
+++ b/database/account-info/user-stats/get-tossup-graph-stats.js
@@ -13,6 +13,7 @@ async function getTossupGraphStats (userId, query) {
result: {
$switch: {
branches: [
+ { case: { $eq: ['$data.pointValue', 20] }, then: 'superpower' },
{ case: { $eq: ['$data.pointValue', 15] }, then: 'power' },
{ case: { $eq: ['$data.pointValue', 10] }, then: 'ten' },
{ case: { $eq: ['$data.pointValue', 0] }, then: 'dead' },
@@ -37,6 +38,7 @@ async function getTossupGraphStats (userId, query) {
pptu: { $avg: '$data.pointValue' },
count: { $sum: 1 },
correct: { $sum: { $cond: ['$data.isCorrect', 1, 0] } },
+ superpowers: { $sum: { $cond: [{ $eq: ['$result', 'superpower'] }, 1, 0] } },
powers: { $sum: { $cond: [{ $eq: ['$result', 'power'] }, 1, 0] } },
tens: { $sum: { $cond: [{ $eq: ['$result', 'ten'] }, 1, 0] } },
deads: { $sum: { $cond: [{ $eq: ['$result', 'dead'] }, 1, 0] } },
diff --git a/quizbowl/Player.js b/quizbowl/Player.js
index e52c1d346..565113fc9 100644
--- a/quizbowl/Player.js
+++ b/quizbowl/Player.js
@@ -6,6 +6,7 @@ class Player {
this.teamId = undefined;
this.username = '';
this.buzzes = 0;
+ this.superpowers = 0;
this.powers = 0;
this.tens = 0;
this.zeroes = 0;
@@ -27,6 +28,7 @@ class Player {
clearStats () {
this.buzzes = 0;
this.powers = 0;
+ this.superpowers = 0;
this.tens = 0;
this.zeroes = 0;
this.negs = 0;
@@ -49,7 +51,9 @@ class Player {
this.celerity.all.total += celerity;
this.celerity.all.average = this.celerity.all.total / this.tuh;
- if (points > 10) {
+ if (points === 20) {
+ this.superpowers++;
+ } else if (points === 15) {
this.powers++;
} else if (points === 10) {
this.tens++;
@@ -60,8 +64,9 @@ class Player {
}
if (points > 0) {
+ const correctBuzzes = this.superpowers + this.powers + this.tens;
this.celerity.correct.total += celerity;
- this.celerity.correct.average = this.celerity.correct.total / (this.powers + this.tens);
+ this.celerity.correct.average = this.celerity.correct.total / correctBuzzes;
}
}
@@ -70,7 +75,7 @@ class Player {
* @returns {boolean}
*/
hasActivity () {
- return this.tuh > 0 || this.buzzes > 0 || this.powers > 0 || this.tens > 0 || this.zeroes > 0 || this.negs > 0;
+ return this.tuh > 0 || this.buzzes > 0 || this.powers > 0 || this.tens > 0 || this.zeroes > 0 || this.negs > 0 || this.superpowers > 0;
}
/**
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 }) {