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(', ') + '

' + '' + ''; var innerBuffer = {5:[], 4:[], 3:[], 2:[], 1:[]}; @@ -331,20 +342,21 @@ var Trivia = (function () { if (innerBuffer[i].length) buffer += ''; } - if (!winner) { - buffer += '
Points GainedCorrect
' + i + '' + Tools.escapeHTML(innerBuffer[i].join(', ')) + '
'; - 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:' + - '#CategoryQuestionAnswer(s)'; - - for (var i = 0; i < submissionsLen; i++) { + var buffer = '|raw|
' + + '' + + ''; + for (var i = 0; i < submissionsLen;) { var entry = submissions[i]; - buffer += ''; + buffer += ''; } buffer += '
' + submissionsLen + ' questions await review:
#CategoryQuestionAnswer(s)
' + (i + 1) + '' + entry.category + '' + entry.question + '' + entry.answers.join(', ') + '
' + (++i) + '' + entry.category + '' + entry.question + '' + entry.answers.join(', ') + '
'; + 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|
'; - 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 += ''; + + 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 += ''; } } buffer += '
RankUserLeaderboard scoreTotal game pointsTotal correct answers
' + i + '' + ladder[i][j] + '' + rank[0] + '' + rank[1] + '' + rank[2] + '
' + (++i) + '' + leader + '' + rank[0] + '' + rank[1] + '' + rank[2] + '
';