178 lines
4.1 KiB
JavaScript
178 lines
4.1 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const config = require('config');
|
||
|
const timers = require('timers/promises');
|
||
|
|
||
|
const questions = require('../../assets/jeopardy.json');
|
||
|
|
||
|
const settings = {
|
||
|
rounds: config.trivia.rounds,
|
||
|
timeout: config.trivia.timeout,
|
||
|
mode: config.trivia.mode,
|
||
|
};
|
||
|
|
||
|
let game = null;
|
||
|
|
||
|
function shuffle(unshuffledQuestions, limit = 10) {
|
||
|
const shuffled = unshuffledQuestions;
|
||
|
|
||
|
for (let i = shuffled.length - 1; i > 0; i -= 1) {
|
||
|
const j = Math.floor(Math.random() * (i + 1));
|
||
|
const temp = shuffled[i];
|
||
|
|
||
|
shuffled[i] = shuffled[j];
|
||
|
shuffled[j] = temp;
|
||
|
}
|
||
|
|
||
|
return shuffled.slice(0, limit);
|
||
|
}
|
||
|
|
||
|
function scoreRound(context, round) {
|
||
|
if (game.answers.length === 0) {
|
||
|
return `No one scored in round ${round + 1}, better luck next time!`;
|
||
|
}
|
||
|
|
||
|
return game.answers.map(({ user }) => {
|
||
|
context.setPoints(user, 1);
|
||
|
game.points[user.username] = (game.points[user.username] || 0) + 1;
|
||
|
|
||
|
return `**@${user.username}** gets a point`;
|
||
|
}).join(', ');
|
||
|
}
|
||
|
|
||
|
async function playRound(context, round = 0) {
|
||
|
const ac = new AbortController(); // eslint-disable-line no-undef
|
||
|
const now = new Date();
|
||
|
|
||
|
game.answers = [];
|
||
|
game.round = round;
|
||
|
game.ac = ac;
|
||
|
|
||
|
const question = game.questions[round];
|
||
|
|
||
|
context.sendMessage(`**Question ${round + 1}/${game.questions.length}**: ${question.question}`, context.room.id);
|
||
|
context.logger.info(`Trivia asked "${question.question}" with answer: ${question.answer}`);
|
||
|
|
||
|
try {
|
||
|
await timers.setTimeout(game.timeout * 1000, null, {
|
||
|
signal: ac.signal,
|
||
|
});
|
||
|
} catch (error) {
|
||
|
// abort expected, not an error
|
||
|
}
|
||
|
|
||
|
if (!ac.signal.aborted) {
|
||
|
ac.abort();
|
||
|
}
|
||
|
|
||
|
if (game.stopped) {
|
||
|
context.sendMessage(`The game was stopped by ${game.stopped.username}. The answer to the last question was: **${question.answer}**`, context.room.id);
|
||
|
game = null;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (game.answers.length === 0) {
|
||
|
context.sendMessage(`**TIME'S UP!** No one guessed the answer: **${question.answer}**`, context.room.id);
|
||
|
} else {
|
||
|
const scores = scoreRound(context, round);
|
||
|
|
||
|
if (game.mode === 'first') {
|
||
|
context.sendMessage(`**${question.answer}** is the right answer after **${((new Date() - now) / 1000).toFixed(3)}s**! ${scores}`, context.room.id);
|
||
|
}
|
||
|
|
||
|
if (game.mode === 'timeout') {
|
||
|
context.sendMessage(`**STOP!** The correct answer is **${question.answer}**. ${scores}`, context.room.id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (round < game.questions.length - 1) {
|
||
|
await timers.setTimeout(5000);
|
||
|
|
||
|
if (game.stopped) {
|
||
|
context.sendMessage(`The game was stopped by ${game.stopped.username}`, context.room.id);
|
||
|
game = null;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
playRound(context, round + 1);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const leaders = Object.entries(game.points).sort(([, scoreA], [, scoreB]) => scoreB - scoreA).map(([username, score], index) => {
|
||
|
if (index === 0) {
|
||
|
return `**@${username}** with **${score}** points`;
|
||
|
}
|
||
|
|
||
|
return `**@${username}** with **${score}** points`;
|
||
|
}).join(', ');
|
||
|
context.sendMessage(`That's the end of the game! Best players: ${leaders}`, context.room.id);
|
||
|
|
||
|
game = null;
|
||
|
}
|
||
|
|
||
|
async function start(context) {
|
||
|
const roundQuestions = shuffle(questions, settings.rounds);
|
||
|
|
||
|
game = {
|
||
|
round: 0,
|
||
|
questions: roundQuestions,
|
||
|
answers: [],
|
||
|
points: {},
|
||
|
...settings,
|
||
|
};
|
||
|
|
||
|
playRound(context, 0);
|
||
|
}
|
||
|
|
||
|
async function stop(context) {
|
||
|
game.stopped = context.user;
|
||
|
game.ac.abort();
|
||
|
}
|
||
|
|
||
|
function onCommand(args, context) {
|
||
|
if (!context.subcommand && !game) {
|
||
|
start(context);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!context.subcommand && game) {
|
||
|
context.sendMessage(`There is already a game going on! The current question for round ${game.round + 1} is: ${game.questions[game.round].question}`, context.room.id);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (context.subcommand === 'stop' && game) {
|
||
|
stop(context);
|
||
|
}
|
||
|
|
||
|
if (context.subcommand === 'stop' && !game) {
|
||
|
context.sendMessage(`There is no game going on at the moment. Start one with ${config.prefix}trivia!`, context.room.id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function onMessage(message, context) {
|
||
|
if (!game) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const { answer } = game.questions[game.round];
|
||
|
|
||
|
if (new RegExp(answer, 'i').test(message.body)) {
|
||
|
game.answers.push({
|
||
|
user: context.user,
|
||
|
answer: message.body,
|
||
|
});
|
||
|
|
||
|
if (settings.mode === 'first') {
|
||
|
game.ac.abort();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = {
|
||
|
name: 'Trivia',
|
||
|
onCommand,
|
||
|
onMessage,
|
||
|
};
|