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,
|
uniqueUsername: true,
|
||||||
socket: 'ws://127.0.0.1:3000/socket',
|
socket: 'ws://127.0.0.1:3000/socket',
|
||||||
api: 'http://127.0.0.1:3000/api',
|
api: 'http://127.0.0.1:3000/api',
|
||||||
|
reconnectDelay: 10, // seconds
|
||||||
prefix: '~',
|
prefix: '~',
|
||||||
style: {
|
style: {
|
||||||
color: 'var(--message-56)',
|
color: 'var(--message-56)',
|
||||||
},
|
},
|
||||||
games: ['mash', 'cursed'],
|
|
||||||
channels: ['GamesNight'],
|
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') {
|
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}`;
|
const userKey = `${user.id}:${user.username}`;
|
||||||
|
|
||||||
if (!points[gameKey]) {
|
if (!points[gameKey]) {
|
||||||
|
@ -85,7 +90,7 @@ function getLeaderboard(game, { user, room }) {
|
||||||
.slice(-10)
|
.slice(-10)
|
||||||
.join(', ');
|
.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) {
|
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) {
|
function onMessage(message, bot, games) {
|
||||||
const [, command, subcommand] = message.body?.match(new RegExp(`^${config.prefix}(\\w+)(?:\\:(\\w+))?`)) || [];
|
const [, command, subcommand] = message.body?.match(new RegExp(`^${config.prefix}(\\w+)(?:\\:(\\w+))?`)) || [];
|
||||||
const user = bot.users[message.userId];
|
const user = bot.users[message.userId];
|
||||||
|
@ -160,6 +178,8 @@ const messageHandlers = {
|
||||||
connect: onConnect,
|
connect: onConnect,
|
||||||
rooms: onRooms,
|
rooms: onRooms,
|
||||||
message: onMessage,
|
message: onMessage,
|
||||||
|
join: onJoin,
|
||||||
|
leave: onLeave,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function initPoints() {
|
async function initPoints() {
|
||||||
|
@ -179,7 +199,7 @@ async function initPoints() {
|
||||||
|
|
||||||
function getGames(bot) {
|
function getGames(bot) {
|
||||||
const games = config.games.reduce((acc, key) => {
|
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) => {
|
const sendMessage = (body, roomId) => {
|
||||||
bot.socket.transmit('message', {
|
bot.socket.transmit('message', {
|
||||||
|
@ -195,6 +215,7 @@ function getGames(bot) {
|
||||||
...acc,
|
...acc,
|
||||||
[key]: {
|
[key]: {
|
||||||
...game,
|
...game,
|
||||||
|
...(key.game && key),
|
||||||
name: game.name || key,
|
name: game.name || key,
|
||||||
key,
|
key,
|
||||||
sendMessage,
|
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(', ')}`);
|
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) {
|
if (word.length !== mash.key.length) {
|
||||||
context.sendMessage(`Your answer needs to be ${mash.key.length} letters, @${context.user.username}`, context.room.id);
|
context.sendMessage(`Your answer needs to be ${mash.key.length} letters, @${context.user.username}`, context.room.id);
|
||||||
return;
|
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