schat2-clive/src/games/trivia.js

192 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const config = require('config');
const timers = require('timers/promises');
const { decode } = require('html-entities');
const questions = require('../../assets/jeopardy.json');
const shuffle = require('../utils/shuffle');
const settings = { ...config.trivia };
let game = null;
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 }) => {
if (user) {
context.setPoints(user, 1);
game.points[user.username] = (game.points[user.username] || 0) + 1;
return `**@${user.username}** gets a point`;
}
return null;
}).filter(Boolean).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.category}): ${question.question}`, context.room.id);
context.logger.info(`Trivia asked "${question.question}" with answer: ${question.answer}`);
try {
await timers.setTimeout((game.timeout / 3) * 1000, null, {
signal: ac.signal,
});
// replace space with U+2003 Em Space to separate words, since a single space separates the placeholders, and double spaces are removed during Markdown render
context.sendMessage(`**${Math.floor(game.timeout / 3) * 2} seconds** left, first hint for **question ${round + 1}/${game.questions.length}**: **${question.answer.slice(0, 1)} ${question.answer.slice(1).replace(/\s/g, '').replace(/[^\s]/g, '_ ').trim()}**`, context.room.id);
await timers.setTimeout((game.timeout / 3) * 1000, null, {
signal: ac.signal,
});
if (question.answer.length > 3) {
context.sendMessage(`**${Math.floor(game.timeout / 3)} seconds** left, second hint for **question ${round + 1}/${game.questions.length}**: **${question.answer.slice(0, 1)} ${question.answer.slice(1, -1).replace(/\s/g, '').replace(/[^\s]/g, '_ ')}${question.answer.slice(-1)}**`, context.room.id);
}
await timers.setTimeout((game.timeout / 3) * 1000, null, {
signal: ac.signal,
});
} catch (error) {
// abort expected, probably 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.fullAnswer || question.answer}** is the right answer, played in **${((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;
}
await timers.setTimeout(2000);
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! Use ${config.prefix}trivia:stop to reset. 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);
}
const subcommand = context.subcommand?.toLowerCase();
if (subcommand && settings[subcommand]) {
settings[subcommand] = typeof settings[subcommand] === 'number' ? (Number(args[0]) || settings[subcommand]) : args[0];
context.sendMessage(`${subcommand} set to ${settings[subcommand]}`, context.room.id);
}
}
async function onMessage(message, context) {
if (!game || context.user?.id === config.user?.id) {
return;
}
const { answer } = game.questions[game.round];
if (new RegExp(answer, 'i').test(decode(message.originalBody || message.body))) { // resolve HTML entities in case original body is not available, such as &amp; to &
game.answers.push({
user: context.user,
answer: message.body,
});
if (settings.mode === 'first') {
game.ac.abort();
}
}
}
module.exports = {
name: 'Trivia',
onCommand,
onMessage,
};