diff --git a/chat-plugins/trivia.js b/chat-plugins/trivia.js
index 7b9f9fb056..da56f24f5a 100644
--- a/chat-plugins/trivia.js
+++ b/chat-plugins/trivia.js
@@ -11,6 +11,7 @@ const MODES = {
timer: 'Timer',
number: 'Number'
};
+
const CATEGORIES = {
animemanga: 'Anime/Manga',
geography: 'Geography',
@@ -26,18 +27,24 @@ const CATEGORIES = {
videogames: 'Video Games',
random: 'Random'
};
-const CAPS = {
+
+const SCORE_CAPS = {
short: 20,
medium: 35,
long: 50
};
+const QUESTION_PERIOD = 15 * 1000;
+
+const INTERMISSION_PERIOD = 30 * 1000;
+
var triviaData = {};
try {
triviaData = require('../config/chat-plugins/triviadata.json');
} catch (e) {} // file doesn't exist or contains invalid JSON
if (!Object.isObject(triviaData)) triviaData = {};
if (!Object.isObject(triviaData.leaderboard)) triviaData.leaderboard = {};
+if (!Array.isArray(triviaData.ladder)) triviaData.ladder = [];
if (!Array.isArray(triviaData.questions)) triviaData.questions = [];
if (!Array.isArray(triviaData.submissions)) triviaData.submissions = [];
@@ -73,29 +80,13 @@ var writeTriviaData = (function () {
};
})();
-var ladder = null;
-function updateLadder() {
- var leaderboard = triviaData.leaderboard;
- if (Object.isEmpty(leaderboard)) return false;
-
- ladder = {};
- for (var user in leaderboard) {
- var rank = leaderboard[user][3];
- if (rank && rank < 16) {
- if (!ladder[rank]) ladder[rank] = [];
- ladder[rank].push(user);
- }
- }
- return true;
-}
-
var trivia = {};
var triviaRoom = Rooms.get('trivia');
if (triviaRoom) {
if (triviaRoom.plugin) {
triviaData = triviaRoom.plugin.data;
- if (!Object.isEmpty(triviaRoom.plugin.trivia)) trivia = triviaRoom.plugin.trivia;
+ trivia = triviaRoom.plugin.trivia;
} else {
triviaRoom.plugin = {
data: triviaData,
@@ -108,17 +99,17 @@ if (triviaRoom) {
}
var Trivia = (function () {
- function Trivia(mode, category, cap, room) {
+ function Trivia(mode, category, scoreCap, room) {
this.room = room;
this.mode = mode;
this.category = category;
- this.cap = cap;
- this.prize = (cap - 5) / 15 + 2;
+ this.scoreCap = scoreCap;
+ this.prize = (scoreCap - 5) / 15 + 2;
this.phase = 'signup';
this.participants = new Map();
- this.curQs = [];
- this.curA = [];
- this.sleep = null;
+ this.currentQuestions = [];
+ this.currentAnswer = [];
+ this.phaseTimeout = null;
this.inactivityCounter = 0;
if (mode !== 'first') {
@@ -166,10 +157,10 @@ var Trivia = (function () {
if (this.participants.size < 3) return output.sendReply('Not enough users have signed up yet! Trivia games require at least three participants to run.');
if (this.category === 'random') {
- this.curQs = triviaData.questions.randomize();
+ this.currentQuestions = triviaData.questions.randomize();
} else {
var category = this.category;
- this.curQs = triviaData.questions.filter(function (question) {
+ this.currentQuestions = triviaData.questions.filter(function (question) {
return question.category === category;
}).randomize();
}
@@ -179,15 +170,15 @@ var Trivia = (function () {
};
Trivia.prototype.askQuestion = function () {
- if (!this.curQs.length) {
+ if (!this.currentQuestions.length) {
this.room.addRaw('
No questions are left!
' +
'Since the game has reached a stalemate, nobody has gained any leaderboard points.
');
this.room.update();
return this.updateLeaderboard();
}
- var head = this.curQs.pop();
- this.curA = head.answers;
+ var head = this.currentQuestions.pop();
+ this.currentAnswer = head.answers;
this.phase = 'question';
this.room.addRaw('Question: ' + head.question + '
' +
'Category: ' + CATEGORIES[head.category] + '
');
@@ -195,14 +186,14 @@ var Trivia = (function () {
switch (this.mode) {
case 'first':
- this.sleep = setTimeout(this.noAnswer.bind(this), 15 * 1000);
+ this.phaseTimeout = setTimeout(this.noAnswer.bind(this), QUESTION_PERIOD);
break;
case 'timer':
this.askedAt = Date.now();
- this.sleep = setTimeout(this.timerAnswers.bind(this), 15 * 1000);
+ this.phaseTimeout = setTimeout(this.timerAnswers.bind(this), QUESTION_PERIOD);
break;
case 'number':
- this.sleep = setTimeout(this.numberAnswers.bind(this), 15 * 1000);
+ this.phaseTimeout = setTimeout(this.numberAnswers.bind(this), QUESTION_PERIOD);
break;
}
};
@@ -212,32 +203,50 @@ var Trivia = (function () {
if (!this.participants.has(user.userid)) return output.sendReply('You are not a participant in this trivia game.');
var scoreData = this.participants.get(user.userid);
- if (scoreData.answered) return output.sendReply('You have already submitted an answer for the current question.');
+ if (scoreData.answered && this.mode === 'first') return output.sendReply('You have already submitted an answer for the current question.');
var answer = toId(target);
if (!answer) return output.sendReply('"' + target.trim() + '" is not a valid answer.');
var correct = false;
scoreData.answered = true;
- for (var i = this.curA.length; i--;) {
- var correctAnswer = this.curA[i];
+ for (var i = this.currentAnswer.length; i--;) {
+ var correctAnswer = this.currentAnswer[i];
if (answer === correctAnswer || correctAnswer.length > 5 && Tools.levenshtein(answer, correctAnswer) < 3) {
correct = true;
break;
}
}
- if (!correct) return output.sendReply('You have selected "' + target.trim() + '" as your answer.');
- if (this.mode === 'first') return this.firstAnswer(user);
- scoreData.responderIndex = this.correctResponders++;
- scoreData.correctAnswers++;
- if (this.mode === 'timer') {
- var points = 5 - ~~((Date.now() - this.askedAt) / (3 * 1000));
- if ([1, 2, 3, 4, 5].indexOf(points) > -1) {
- scoreData.score += points;
- scoreData.points = points;
+ if (this.mode === 'first') {
+ if (correct) return this.firstAnswer(user);
+ return output.sendReply('You have selected "' + target.trim() + '" as your answer.');
+ }
+
+ if (correct) {
+ if (scoreData.responderIndex > -1) return output.sendReply('You have selected "' + target.trim() + '" as your answer.');
+
+ scoreData.responderIndex = this.correctResponders++;
+ scoreData.correctAnswers++;
+ if (this.mode === 'timer') {
+ var points = 5 - ~~((Date.now() - this.askedAt) / (QUESTION_PERIOD / 5));
+ if ([1, 2, 3, 4, 5].indexOf(points) > -1) {
+ scoreData.score += points;
+ scoreData.points = points;
+ }
+ }
+ } else {
+ if (scoreData.responderIndex < 0) return output.sendReply('You have selected "' + target.trim() + '" as your answer.');
+
+ this.correctResponders--;
+ scoreData.responderIndex = -1;
+ scoreData.correctAnswers--;
+ if (this.mode === 'timer') {
+ scoreData.score -= scoreData.points;
+ scoreData.points = 0;
}
}
+
output.sendReply('You have selected "' + target.trim() + '" as your answer.');
};
@@ -248,7 +257,7 @@ var Trivia = (function () {
for (var scoreData, participantsIterator = this.participants.values(); !!(scoreData = participantsIterator.next().value);) { // replace with for-of loop once available
if (scoreData.answered) {
scoreData.answered = false;
- if (!isActive) isActive = true;
+ isActive = true;
}
}
@@ -262,14 +271,14 @@ var Trivia = (function () {
this.room.addRaw('The answering period has ended!
' +
'Correct: no one
' +
- 'Answer' + (this.curA.length > 1 ? 's: ' : ': ') + this.curA.join(', ') + '
' +
+ 'Answer' + (this.currentAnswer.length > 1 ? 's: ' : ': ') + this.currentAnswer.join(', ') + '
' +
'Nobody gained any points.
');
this.room.update();
- this.sleep = setTimeout(this.askQuestion.bind(this), 30 * 1000);
+ this.phaseTimeout = setTimeout(this.askQuestion.bind(this), INTERMISSION_PERIOD);
};
Trivia.prototype.firstAnswer = function (user) {
- clearTimeout(this.sleep);
+ clearTimeout(this.phaseTimeout);
this.phase = 'intermission';
var scoreData = this.participants.get(user.userid);
@@ -278,21 +287,23 @@ var Trivia = (function () {
var buffer = 'The answering period has ended!
' +
'Correct: ' + Tools.escapeHTML(user.name) + '
' +
- 'Answer' + (this.curA.length > 1 ? 's: ' : ': ') + this.curA.join(', ') + '
';
- if (scoreData.score < this.cap) {
- for (var participantsIterator = this.participants.values(); !!(scoreData = participantsIterator.next().value);) { // replace with for-of loop once available
- if (scoreData.answered) scoreData.answered = false;
- }
+ 'Answer' + (this.currentAnswer.length > 1 ? 's: ' : ': ') + this.currentAnswer.join(', ') + '
';
- buffer += 'They gained 5 points!
';
+ if (scoreData.score >= this.scoreCap) {
+ buffer += 'They won the game with a final score of ' + scoreData.score + ', and their leaderboard score has increased by ' + this.prize + ' points!';
this.room.addRaw(buffer);
- this.sleep = setTimeout(this.askQuestion.bind(this), 30 * 1000);
- return false;
+ return this.updateLeaderboard(user.userid);
}
- buffer += 'They won the game with a final score of ' + scoreData.score + ', and their leaderboard score has increased by ' + this.prize + ' points!';
+ for (var participantsIterator = this.participants.values(); !!(scoreData = participantsIterator.next().value);) { // replace with for-of loop once available
+ scoreData.answered = false;
+ }
+
+ if (this.inactivityCounter) this.inactivityCounter = 0;
+
+ buffer += 'They gained 5 points!';
this.room.addRaw(buffer);
- this.updateLeaderboard(user.userid);
+ this.phaseTimeout = setTimeout(this.askQuestion.bind(this), INTERMISSION_PERIOD);
};
Trivia.prototype.timerAnswers = function () {
@@ -302,9 +313,9 @@ var Trivia = (function () {
var winner = '';
var winnerIndex = this.correctResponders;
- var score = this.cap - 1;
+ var score = this.scoreCap - 1;
var buffer = 'The answering period has ended!' +
- 'Answer' + (this.curA.length > 1 ? 's: ' : ': ') + this.curA.join(', ') + '
' +
+ 'Answer' + (this.currentAnswer.length > 1 ? 's: ' : ': ') + this.currentAnswer.join(', ') + '
' +
'
' +
'| Points Gained | Correct |
';
var innerBuffer = {5:[], 4:[], 3:[], 2:[], 1:[]};
@@ -331,20 +342,21 @@ var Trivia = (function () {
if (innerBuffer[i].length) buffer += '| ' + i + ' | ' + Tools.escapeHTML(innerBuffer[i].join(', ')) + ' |
';
}
- if (!winner) {
- buffer += '
';
- this.correctResponders = 0;
+ if (winner) {
+ buffer += '
' +
+ Tools.escapeHTML(winner) + ' won the game with a final score of ' + score + ', and their leaderboard score has increased by ' + this.prize + ' points!';
this.room.addRaw(buffer);
this.room.update();
- this.sleep = setTimeout(this.askQuestion.bind(this), 30 * 1000);
- return false;
+ return this.updateLeaderboard(toId(winner));
}
- buffer += '
' +
- Tools.escapeHTML(winner) + ' won the game with a final score of ' + score + ', and their leaderboard score has increased by ' + this.prize + ' points!';
+ if (this.inactivityCounter) this.inactivityCounter = 0;
+
+ buffer += '';
+ this.correctResponders = 0;
this.room.addRaw(buffer);
this.room.update();
- this.updateLeaderboard(toId(winner));
+ this.phaseTimeout = setTimeout(this.askQuestion.bind(this), INTERMISSION_PERIOD);
};
Trivia.prototype.numberAnswers = function () {
@@ -354,7 +366,7 @@ var Trivia = (function () {
var winner = '';
var winnerIndex = this.correctResponders;
- var score = this.cap - 1;
+ var score = this.scoreCap - 1;
var points = ~~(5 - 4 * (this.correctResponders - 1) / (this.participants.size - 1 || 1));
var innerBuffer = [];
@@ -378,26 +390,28 @@ var Trivia = (function () {
var buffer = 'The answering period has ended!
' +
'Correct: ' + Tools.escapeHTML(innerBuffer.join(', ')) + '
' +
- 'Answer' + (this.curA.length > 1 ? 's: ' : ': ') + this.curA.join(', ') + '
';
+ 'Answer' + (this.currentAnswer.length > 1 ? 's: ' : ': ') + this.currentAnswer.join(', ') + '
';
- if (!winner) {
- buffer += (this.correctResponders > 1 ? 'Each of them' : 'They') + ' gained ' + points + ' point' + (points === 1 ? '!
' : 's!');
- this.correctResponders = 0;
+ if (winner) {
+ buffer += Tools.escapeHTML(winner) + ' won the game with a final score of ' + score + ', and their leaderboard score has increased by ' + this.prize + ' points!';
this.room.addRaw(buffer);
this.room.update();
- this.sleep = setTimeout(this.askQuestion.bind(this), 30 * 1000);
- return false;
+ return this.updateLeaderboard(toId(winner));
}
- buffer += Tools.escapeHTML(winner) + ' won the game with a final score of ' + score + ', and their leaderboard score has increased by ' + this.prize + ' points!';
+ if (this.inactivityCounter) this.inactivityCounter = 9;
+
+ buffer += (this.correctResponders > 1 ? 'Each of them' : 'They') + ' gained ' + points + ' point' + (points === 1 ? '!' : 's!');
+ this.correctResponders = 0;
this.room.addRaw(buffer);
this.room.update();
- this.updateLeaderboard(toId(winner));
+ this.phaseTimeout = setTimeout(this.askQuestion.bind(this), INTERMISSION_PERIOD);
};
Trivia.prototype.updateLeaderboard = function (winnerid) {
var leaderboard = triviaData.leaderboard;
+ // update leaderboard scores
for (var data, participantsIterator = this.participants.entries(); !!(data = participantsIterator.next().value);) { // replace with for-of loop once available
var scoreData = data[1];
if (!scoreData.score) continue;
@@ -412,44 +426,48 @@ var Trivia = (function () {
}
if (winnerid) leaderboard[winnerid][0] += this.prize;
- for (var i = 3; i < 6; i++) {
- var scores = [];
- for (var user in leaderboard) {
- scores.push({
- user: user,
- score: leaderboard[user][i]
- });
- }
-
- scores.sort(function (a, b) {
- return a.score - b.score;
+ // update leaderboard ranks and rebuild the ladder
+ var leaders = Object.keys(leaderboard);
+ var ladder = triviaData.ladder = [];
+ for (var i = 3; i--;) {
+ leaders.sort(function (a, b) {
+ return leaderboard[a][i] - leaderboard[b][i];
});
var max = Infinity;
var rank = 0;
- for (var j = scores.length; j--;) {
- var data = scores[j];
- var score = data.score;
+ var rankIdx = i + 3;
+ for (var j = leaders.length; j--;) {
+ var leader = leaders[j];
+ var score = leaderboard[leader][i];
if (max !== score) {
- rank += 1;
+ if (!i && rank < 15) {
+ if (ladder[rank]) {
+ ladder[rank].push(leader);
+ } else {
+ ladder[rank] = [leader];
+ }
+ }
+
+ rank++;
max = score;
}
- leaderboard[data.user][i] = rank;
+ leaderboard[leader][rankIdx] = rank;
}
}
- updateLadder();
writeTriviaData();
delete trivia[this.room.id];
};
Trivia.prototype.getStatus = function (output, user) {
var buffer = 'There is a trivia game in progress, and it is in its ' + this.phase + ' phase.
' +
- 'Mode: ' + MODES[this.mode] + ' | Category: ' + CATEGORIES[this.category] + ' | Score cap: ' + this.cap;
+ 'Mode: ' + MODES[this.mode] + ' | Category: ' + CATEGORIES[this.category] + ' | Score cap: ' + this.scoreCap;
if (this.phase !== 'signup' && !output.broadcasting) {
var scoreData = this.participants.get(user.userid);
if (scoreData) buffer += '
Current score: ' + scoreData.score + ' | Correct answers: ' + scoreData.correctAnswers;
}
+
output.sendReplyBox(buffer);
};
@@ -464,11 +482,12 @@ var Trivia = (function () {
participants.push(targetUser ? targetUser.name : participant);
});
buffer += Tools.escapeHTML(participants.join(', '));
+
output.sendReplyBox(buffer);
};
Trivia.prototype.endGame = function (output, user) {
- if (this.phase !== 'signup') clearTimeout(this.sleep);
+ if (this.phase !== 'signup') clearTimeout(this.phaseTimeout);
this.room.addRaw('' + Tools.escapeHTML(user.name) + ' has forced the game to end.
');
delete trivia[this.room.id];
};
@@ -492,12 +511,12 @@ var commands = {
var category = toId(target[1]);
if (!CATEGORIES[category]) return this.sendReply('"' + target[1].trim() + '" is not a valid category. View /trivia help ginfo for more information.');
- var cap = CAPS[toId(target[2])];
- if (!cap) return this.sendReply('"' + target[2].trim() + '" is not a valid score cap. View /trivia help ginfo for more information.');
+ var scoreCap = SCORE_CAPS[toId(target[2])];
+ if (!scoreCap) return this.sendReply('"' + target[2].trim() + '" is not a valid score cap. View /trivia help ginfo for more information.');
- trivia[room.id] = new Trivia(mode, category, cap, room);
+ trivia[room.id] = new Trivia(mode, category, scoreCap, room);
room.addRaw('Signups for a new trivia game have begun! Enter /trivia join to join.
' +
- 'Mode: ' + MODES[mode] + ' | Category: ' + CATEGORIES[category] + ' | Score cap: ' + cap + '
');
+ 'Mode: ' + MODES[mode] + ' | Category: ' + CATEGORIES[category] + ' | Score cap: ' + scoreCap + '');
},
join: function (target, room, user) {
@@ -594,20 +613,17 @@ var commands = {
var submissions = triviaData.submissions;
var submissionsLen = submissions.length;
- var buffer = '|raw|';
- if (!submissionsLen) {
- buffer += '| No questions await review. |
';
- return this.sendReply(buffer);
- }
+ if (!submissionsLen) return this.sendReply('No questions await review.');
- buffer += '' + submissionsLen + ' questions await review: | ' +
- '| # | Category | Question | Answer(s) |
';
-
- for (var i = 0; i < submissionsLen; i++) {
+ var buffer = '|raw|' +
+ '| ' + submissionsLen + ' questions await review: |
' +
+ '| # | Category | Question | Answer(s) |
';
+ for (var i = 0; i < submissionsLen;) {
var entry = submissions[i];
- buffer += '| ' + (i + 1) + ' | ' + entry.category + ' | ' + entry.question + ' | ' + entry.answers.join(', ') + ' |
';
+ buffer += '| ' + (++i) + ' | ' + entry.category + ' | ' + entry.question + ' | ' + entry.answers.join(', ') + ' |
';
}
buffer += '
';
+
this.sendReply(buffer);
},
@@ -644,28 +660,23 @@ var commands = {
}
var range = indices[i].split('-');
- if (range.length !== 2) {
- indices.splice(i, 1);
- continue;
- }
-
- var left = Number(range[0]) - 1;
+ var left = Number(range[0]);
var right = Number(range[1]);
if (!Number.isInteger(left) || !Number.isInteger(right) ||
- left === right || left < 0 || right > submissionsLen) {
+ left < 1 || right > submissionsLen || left === right) {
indices.splice(i, 1);
continue;
}
do {
indices.push(right);
- } while (--right > left);
+ } while (--right >= left);
indices.splice(i, 1);
}
indices = indices.sort(function (a, b) {
- return a - b;
+ return b - a;
}).filter(function (entry, index) {
return !index || indices[index - 1] !== entry;
});
@@ -673,27 +684,22 @@ var commands = {
var indicesLen = indices.length;
if (!indicesLen) return this.sendReply('"' + target.trim() + '" is not a valid set of submission index numbers. View /trivia review and /trivia help qcommands for more information.');
- var oldIndices = indices.join(', '); // keep these indices for the response
- for (var i = indicesLen; i--;) {
- indices[i] -= 1;
- }
-
if (isAccepting) {
var accepted = [];
for (var i = indicesLen; i--;) {
- var submission = submissions.splice(indices[i], 1)[0];
- accepted.unshift(submission);
+ var submission = submissions.splice(indices[i] - 1, 1)[0];
+ accepted.push(submission);
}
Array.prototype.push.apply(triviaData.questions, accepted);
} else {
for (var i = indicesLen; i--;) {
- submissions.splice(indices[i], 1);
+ submissions.splice(indices[i] - 1, 1);
}
}
writeTriviaData();
return this.privateModCommand('(' + user.name + ' ' + (isAccepting ? 'added ' : 'removed ') + 'submission number' +
- (indicesLen > 1 ? 's ' : ' ') + oldIndices + ' from the submission database.)');
+ (indicesLen > 1 ? 's ' : ' ') + target + ' from the submission database.)');
}
this.sendReply('"' + target + '" is an invalid argument. View /trivia help qcommands for more information.');
@@ -820,15 +826,23 @@ var commands = {
ladder: function (target, room) {
if (room.id !== 'trivia' || !this.canBroadcast()) return false;
- if (!ladder && !updateLadder()) return this.sendReply('No trivia games have been played yet.');
+ var ladder = triviaData.ladder;
var leaderboard = triviaData.leaderboard;
+ if (!ladder.length) {
+ if (Object.isEmpty(leaderboard)) return this.sendReply('No trivia games have been played yet.');
+ return this.sendReply('Trivia games have been played, but ladder rankings have not been recorded yet. Finish a trivia game to build the ladder.');
+ }
+
var buffer = '|raw|| Rank | User | Leaderboard score | Total game points | Total correct answers |
';
- for (var i = 1, len = Object.size(ladder) + 1; i < len; i++) {
- if (!ladder[i].length) break;
- for (var j = ladder[i].length; j--;) {
- var rank = leaderboard[ladder[i][j]];
- buffer += '| ' + i + ' | ' + ladder[i][j] + ' | ' + rank[0] + ' | ' + rank[1] + ' | ' + rank[2] + ' |
';
+
+ for (var i = 0, len = ladder.length; i < len;) {
+ var leaders = ladder[i];
+ for (var j = leaders.length; j--;) {
+ var rank = leaderboard[leaders[j]];
+ var leader = Users.getExact(leaders[j]);
+ leader = leader ? Tools.excapeHTML(leader.name) : leaders[j];
+ buffer += '| ' + (++i) + ' | ' + leader + ' | ' + rank[0] + ' | ' + rank[1] + ' | ' + rank[2] + ' |
';
}
}
buffer += '
';