schat2-clive/src/games/trivia.js

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,
};