Added rudimentary Trivia game.
This commit is contained in:
parent
14fb0f90bf
commit
09f1f5a14a
|
@ -0,0 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const questions = require('./jeopardy_raw.json');
|
||||
|
||||
async function init() {
|
||||
const curatedQuestions = questions.map((question) => ({
|
||||
...question,
|
||||
question: question.question.replace(/^'|'$/g, ''),
|
||||
}));
|
||||
|
||||
await fs.writeFile('assets/jeopardy.json', JSON.stringify(curatedQuestions));
|
||||
|
||||
console.log(curatedQuestions);
|
||||
}
|
||||
|
||||
init();
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -14,11 +14,16 @@ module.exports = {
|
|||
uniqueUsername: true,
|
||||
socket: 'ws://127.0.0.1:3000/socket',
|
||||
api: 'http://127.0.0.1:3000/api',
|
||||
reconnectDelay: 10, // seconds
|
||||
prefix: '~',
|
||||
style: {
|
||||
color: 'var(--message-56)',
|
||||
},
|
||||
games: ['mash', 'cursed'],
|
||||
channels: ['GamesNight'],
|
||||
reconnectDelay: 10, // seconds
|
||||
games: ['mash', 'trivia'],
|
||||
trivia: {
|
||||
mode: 'first', // first or timeout
|
||||
rounds: 10,
|
||||
timeout: 30,
|
||||
},
|
||||
};
|
||||
|
|
25
src/app.js
25
src/app.js
|
@ -44,6 +44,11 @@ async function getWsId(httpSession) {
|
|||
}
|
||||
|
||||
async function setPoints(gameKey, user, value, mode = 'add') {
|
||||
if (!user) {
|
||||
logger.warn(`Failed to set ${gameKey} points for missing user`);
|
||||
return;
|
||||
}
|
||||
|
||||
const userKey = `${user.id}:${user.username}`;
|
||||
|
||||
if (!points[gameKey]) {
|
||||
|
@ -85,7 +90,7 @@ function getLeaderboard(game, { user, room }) {
|
|||
.slice(-10)
|
||||
.join(', ');
|
||||
|
||||
game.sendMessage(`The top ${Math.min(Object.keys(curatedLeaderboard).length, 10)} ${game.name} players are ${curatedLeaderboard}`, room.id);
|
||||
game.sendMessage(`The top ${Math.min(Object.keys(curatedLeaderboard).length, 10)} *${game.name}* players are: ${curatedLeaderboard}`, room.id);
|
||||
}
|
||||
|
||||
function onConnect(data, bot) {
|
||||
|
@ -109,6 +114,19 @@ function onRooms({ rooms, users }, bot) {
|
|||
});
|
||||
}
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
function onJoin(data, bot) {
|
||||
bot.users[data.user.id] = data.user;
|
||||
|
||||
if (!bot.rooms[data.roomId].includes(data.user.id)) {
|
||||
bot.rooms[data.roomId].push(data.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
function onLeave(data, bot) {
|
||||
bot.rooms[data.roomId].users = bot.rooms[data.roomId].users.filter((userId) => userId !== data.userId);
|
||||
}
|
||||
|
||||
function onMessage(message, bot, games) {
|
||||
const [, command, subcommand] = message.body?.match(new RegExp(`^${config.prefix}(\\w+)(?:\\:(\\w+))?`)) || [];
|
||||
const user = bot.users[message.userId];
|
||||
|
@ -160,6 +178,8 @@ const messageHandlers = {
|
|||
connect: onConnect,
|
||||
rooms: onRooms,
|
||||
message: onMessage,
|
||||
join: onJoin,
|
||||
leave: onLeave,
|
||||
};
|
||||
|
||||
async function initPoints() {
|
||||
|
@ -179,7 +199,7 @@ async function initPoints() {
|
|||
|
||||
function getGames(bot) {
|
||||
const games = config.games.reduce((acc, key) => {
|
||||
const game = require(`./games/${key}`); // eslint-disable-line global-require, import/no-dynamic-require
|
||||
const game = require(`./games/${key.game || key}`); // eslint-disable-line global-require, import/no-dynamic-require
|
||||
|
||||
const sendMessage = (body, roomId) => {
|
||||
bot.socket.transmit('message', {
|
||||
|
@ -195,6 +215,7 @@ function getGames(bot) {
|
|||
...acc,
|
||||
[key]: {
|
||||
...game,
|
||||
...(key.game && key),
|
||||
name: game.name || key,
|
||||
key,
|
||||
sendMessage,
|
||||
|
|
|
@ -48,7 +48,9 @@ function start(length, context, attempt = 0) {
|
|||
context.logger.info(`Mash started, '${anagram}' with answers ${answers.map((answer) => `'${answer.word}'`).join(', ')}`);
|
||||
}
|
||||
|
||||
function play(word, context) {
|
||||
function play(rawWord, context) {
|
||||
const word = rawWord.toLowerCase();
|
||||
|
||||
if (word.length !== mash.key.length) {
|
||||
context.sendMessage(`Your answer needs to be ${mash.key.length} letters, @${context.user.username}`, context.room.id);
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
'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,
|
||||
};
|
Loading…
Reference in New Issue