Added playable round to letters game.
This commit is contained in:
parent
4534a1debe
commit
55343d5de7
|
@ -35,5 +35,6 @@ module.exports = {
|
|||
},
|
||||
letters: {
|
||||
length: 9,
|
||||
timeout: 30,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ function onCommand(args, context) {
|
|||
}
|
||||
|
||||
const messages = [
|
||||
`How could you miss *that*, ${config.usernamePrefix}${context.user.username}...?!`,
|
||||
`How could you miss ${style.italic('that')}, ${config.usernamePrefix}${context.user.username}...?!`,
|
||||
`That's a miss! Better luck next time, ${config.usernamePrefix}${context.user.username}.`,
|
||||
`The duck outsmarted you, ${config.usernamePrefix}${context.user.username}`,
|
||||
`Channeling Gareth Southgate, ${config.usernamePrefix}${context.user.username}? You missed!`,
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const timers = require('timers/promises');
|
||||
|
||||
const shuffle = require('../utils/shuffle');
|
||||
const style = require('../utils/style');
|
||||
const getLeaders = require('../utils/get-leaders');
|
||||
const getWordKey = require('../utils/get-word-key');
|
||||
const words = require('../../assets/mash-words.json');
|
||||
|
||||
const availableVowels = ['a', 'e', 'i', 'o', 'u'];
|
||||
const availableConsonants = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z']; // Countdown regards y as a consonant
|
||||
const types = { v: 'vowels', c: 'consonants' };
|
||||
|
||||
const settings = { ...config.letters };
|
||||
const games = new Map();
|
||||
|
||||
function getBoard(context) {
|
||||
|
@ -16,6 +22,10 @@ function getBoard(context) {
|
|||
return `${game.word.split('').map((letter) => `${style.grey('[')}${letter.toUpperCase()}${style.grey(']')}`).join('')}${`${style.silver('[')} ${style.silver(']')}`.repeat(config.letters.length - game.word.length)}`;
|
||||
}
|
||||
|
||||
function countLetters(word) {
|
||||
return word.split('').reduce((counts, letter) => ({ ...counts, [letter]: (counts[letter] || 0) + 1 }), {});
|
||||
}
|
||||
|
||||
function shuffleLetters(letters, acc = []) {
|
||||
const shuffled = shuffle(letters, config.letters.length);
|
||||
|
||||
|
@ -26,21 +36,93 @@ function shuffleLetters(letters, acc = []) {
|
|||
return [...acc, ...shuffled].slice(0, config.letters.length);
|
||||
}
|
||||
|
||||
function playRound(context) {
|
||||
context.sendMessage(`${getBoard(context)} Let's start!`, context.room.id);
|
||||
function playWord(rawWord, context) {
|
||||
const game = games.get(context.room.id);
|
||||
const word = rawWord.trim().toLowerCase();
|
||||
|
||||
if (!word || word.length > game.word.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
function pickLetter(type, context) {
|
||||
if (game.played.has(word)) {
|
||||
context.logger.debug(`${context.user.username} played a word that was already played: ${word}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!words[word.length]?.[getWordKey(word)]?.some((definition) => definition.word === word)) {
|
||||
context.logger.debug(`${context.user.username} played word not in the dictionary: ${word}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const counts = countLetters(word);
|
||||
const invalid = Object.entries(counts).filter(([letter, count]) => !game.counts[letter] || game.counts[letter] < count).map(([letter]) => letter);
|
||||
|
||||
if (invalid.length > 0) {
|
||||
context.logger.debug(`${context.user.username} played '${word}' containing letters that are not on the board: ${invalid.join(' ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
context.setPoints(context.user, word.length);
|
||||
game.points[context.user.username] = (game.points[context.user.username] || 0) + word.length;
|
||||
|
||||
game.played.add(word);
|
||||
|
||||
context.sendMessage(`${context.user.username} played ${style.bold(style.pink(word))} for ${style.bold(word.length)} points!`, context.room.id);
|
||||
}
|
||||
|
||||
function stop(context, aborted) {
|
||||
const game = games.get(context.room.id);
|
||||
|
||||
game.ac.abort();
|
||||
games.delete(context.room.id);
|
||||
|
||||
if (aborted) {
|
||||
context.sendMessage(`The game was stopped by ${style.cyan(`${config.usernamePrefix}${context.user.username}`)}. Best players: ${getLeaders(game.points)}`, context.room.id);
|
||||
}
|
||||
}
|
||||
|
||||
async function play(context) {
|
||||
const game = games.get(context.room.id);
|
||||
|
||||
game.state = 'words';
|
||||
game.counts = countLetters(game.word);
|
||||
|
||||
context.sendMessage(`${getBoard(context)} Let's start!`, context.room.id);
|
||||
|
||||
try {
|
||||
await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal });
|
||||
context.sendMessage(`${getBoard(context)} ${style.bold(style.green(`${Math.round((settings.timeout / 3) * 2)} seconds`))} left`, context.room.id);
|
||||
|
||||
await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal });
|
||||
context.sendMessage(`${getBoard(context)} ${style.bold(style.green(`${Math.round(settings.timeout / 3)} seconds`))} left`, context.room.id);
|
||||
|
||||
await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal });
|
||||
context.sendMessage(`Time's up! Best players: ${getLeaders(game.points)}`, context.room.id);
|
||||
|
||||
stop(context);
|
||||
} catch (error) {
|
||||
// abort expected, probably not an error
|
||||
}
|
||||
}
|
||||
|
||||
function pickLetters(type, context) {
|
||||
const game = games.get(context.room.id);
|
||||
|
||||
if (!game || game.word.length === config.letters.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'consonants' || type === 'vowels') {
|
||||
game.word = `${game.word}${game[type].pop()}`;
|
||||
} else {
|
||||
type.toLowerCase().slice(0, config.letters.length - game.word.length).split('').forEach((typeKey) => {
|
||||
game.word = `${game.word}${game[types[typeKey]].pop()}`;
|
||||
});
|
||||
}
|
||||
|
||||
if (game.word.length === config.letters.length) {
|
||||
playRound(context);
|
||||
play(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -54,43 +136,63 @@ function start(context) {
|
|||
}
|
||||
|
||||
games.set(context.room.id, {
|
||||
state: 'letters',
|
||||
word: '',
|
||||
vowels: shuffleLetters(availableVowels),
|
||||
consonants: shuffleLetters(availableConsonants),
|
||||
played: new Set(),
|
||||
points: {},
|
||||
ac: new AbortController(), // eslint-disable-line no-undef
|
||||
});
|
||||
|
||||
context.sendMessage('Let\'s play the letters! Would you like a consonant or a vowel?', context.room.id);
|
||||
}
|
||||
|
||||
function stop(context) {
|
||||
games.delete(context.room.id);
|
||||
|
||||
context.sendMessage('The game is stopped and reset', context.room.id);
|
||||
}
|
||||
|
||||
function onCommand(args, context) {
|
||||
if (args.subcommand === 'stop') {
|
||||
stop(context);
|
||||
if (context.subcommand === 'stop') {
|
||||
stop(context, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.subcommand) {
|
||||
if (['help', 'commands'].includes(context.subcommand)) {
|
||||
context.sendMessage('Make the longest word using the available letters. To pick the letters, say con(sonant), vow(el) or supply multiple: CCCCCVVVV. Available subcommands: :stop', context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.subcommand) {
|
||||
start(context);
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage(message, context) {
|
||||
const game = games.get(context.room.id);
|
||||
|
||||
if (game?.state === 'letters') {
|
||||
const multi = message.body.match(/\b[vc]{2,}\b/i)?.[0];
|
||||
|
||||
if (multi) {
|
||||
pickLetters(multi.toLowerCase(), context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (/(^|\b)cons?(onant)?($|\b)/i.test(message.body)) {
|
||||
pickLetter('consonants', context);
|
||||
pickLetters('consonants', context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (/(^|\b)vow(el)?($|\b)/i.test(message.body)) {
|
||||
pickLetter('vowels', context);
|
||||
pickLetters('vowels', context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (game?.state === 'words') {
|
||||
playWord(message.body, context);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'Letters',
|
||||
onCommand,
|
||||
onMessage,
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const config = require('config');
|
||||
|
||||
const style = require('../utils/style');
|
||||
const getWordKey = require('../utils/get-word-key');
|
||||
const words = require('../../assets/mash-words.json');
|
||||
|
||||
const mashes = new Map();
|
||||
|
@ -10,10 +11,6 @@ const mashes = new Map();
|
|||
const defineCommands = ['define', 'dict', 'dictionary'];
|
||||
const resolveCommands = ['solve', 'resolve', 'lookup'];
|
||||
|
||||
function getWordKey(word) {
|
||||
return word.split('').sort().join('');
|
||||
}
|
||||
|
||||
function start(length, context, attempt = 0) {
|
||||
const mash = mashes.get(context.room.id);
|
||||
const lengthWords = words[length];
|
||||
|
@ -51,13 +48,13 @@ function start(length, context, attempt = 0) {
|
|||
|
||||
const newMash = mashes.get(context.room.id);
|
||||
|
||||
context.sendMessage(`Stomp stomp, here's your mash: ${style.bold(style.purple(newMash.anagram))}`, context.room.id);
|
||||
context.sendMessage(`Stomp stomp, here's your mash: ${style.bold(style.pink(newMash.anagram))}`, context.room.id);
|
||||
context.logger.info(`Mash started, '${anagram}' with answers ${answers.map((answer) => `'${answer.word}'`).join(', ')}`);
|
||||
}
|
||||
|
||||
function play(rawWord, context, shouted) {
|
||||
const mash = mashes.get(context.room.id);
|
||||
const word = rawWord.toLowerCase();
|
||||
const word = rawWord.trim().toLowerCase();
|
||||
const key = getWordKey(word);
|
||||
const answer = mash.answers.find((answerX) => answerX.word === word);
|
||||
|
||||
|
@ -159,7 +156,7 @@ function hint(context) {
|
|||
}
|
||||
|
||||
if (mash.anagram.length === 4) {
|
||||
context.sendMessage(`Hints for ${style.bold(style.purple(mash.anagram))}, ${config.usernamePrefix}${context.user.username}: ${mash.answers.map((answer) => `${style.bold(`${answer.word.slice(0, 1)} ${'_ '.repeat(answer.word.length - 1).trim()}`)} (${answer.definitions[0]})`).join(', ')}`, context.room.id);
|
||||
context.sendMessage(`Hints for ${style.bold(style.pink(mash.anagram))}, ${config.usernamePrefix}${context.user.username}: ${mash.answers.map((answer) => `${style.bold(`${answer.word.slice(0, 1)} ${'_ '.repeat(answer.word.length - 1).trim()}`)} (${answer.definitions[0]})`).join(', ')}`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -192,7 +189,7 @@ function onCommand(args, context) {
|
|||
}
|
||||
|
||||
if (!word && mash) {
|
||||
context.sendMessage(`The current mash is: ${style.bold(style.purple(mash.anagram))}`, context.room.id);
|
||||
context.sendMessage(`The current mash is: ${style.bold(style.pink(mash.anagram))}`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ const { decode } = require('html-entities');
|
|||
const questions = require('../../assets/jeopardy.json');
|
||||
const shuffle = require('../utils/shuffle');
|
||||
const style = require('../utils/style');
|
||||
const getLeaders = require('../utils/get-leaders');
|
||||
|
||||
const settings = { ...config.trivia };
|
||||
const help = {
|
||||
|
@ -37,18 +38,6 @@ function scoreRound(context, round) {
|
|||
}).filter(Boolean).join(', ');
|
||||
}
|
||||
|
||||
function getLeaders(context) {
|
||||
const game = games.get(context.room.id);
|
||||
|
||||
return Object.entries(game.points).sort(([, scoreA], [, scoreB]) => scoreB - scoreA).map(([username, score], index) => {
|
||||
if (index === 0) {
|
||||
return `${style.bold(`${config.usernamePrefix}${username}`)} with ${style.bold(`${score}`)} points`;
|
||||
}
|
||||
|
||||
return `${style.bold(style.cyan(`${config.usernamePrefix}${username}`))} with ${style.bold(`${score}`)} points`;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
async function playRound(context, round = 0) {
|
||||
const game = games.get(context.room.id);
|
||||
const ac = new AbortController(); // eslint-disable-line no-undef
|
||||
|
@ -60,7 +49,7 @@ async function playRound(context, round = 0) {
|
|||
|
||||
const question = game.questions[round];
|
||||
|
||||
context.sendMessage(`${style.bold(style.purple(`Question ${round + 1}/${game.questions.length}`))} ${style.silver(`(${question.category})`)}: ${question.question}`, context.room.id);
|
||||
context.sendMessage(`${style.bold(style.pink(`Question ${round + 1}/${game.questions.length}`))} ${style.silver(`(${question.category})`)}: ${question.question}`, context.room.id);
|
||||
context.logger.info(`Trivia asked "${question.question}" with answer: ${question.answer}`);
|
||||
|
||||
try {
|
||||
|
@ -70,10 +59,10 @@ async function playRound(context, round = 0) {
|
|||
|
||||
// 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
|
||||
if (question.answer.length >= 3) {
|
||||
context.sendMessage(`${style.bold(style.green(`${Math.floor(game.timeout / 3) * 2} seconds`))} left, first hint for ${style.bold(style.purple(`question ${round + 1}/${game.questions.length}`))}: ${style.bold(`${question.answer.slice(0, 1)} ${question.answer.slice(1).replace(/\s/g, ' ').replace(/[^\s]/g, '_ ').trim()}`)}`, context.room.id);
|
||||
context.sendMessage(`${style.bold(style.green(`${Math.floor(game.timeout / 3) * 2} seconds`))} left, first hint for ${style.bold(style.pink(`question ${round + 1}/${game.questions.length}`))}: ${style.bold(`${question.answer.slice(0, 1)} ${question.answer.slice(1).replace(/\s/g, ' ').replace(/[^\s]/g, '_ ').trim()}`)}`, context.room.id);
|
||||
} else {
|
||||
// giving the first letter gives too much away, only give the placeholders
|
||||
context.sendMessage(`${style.bold(style.green(`${Math.floor(game.timeout / 3) * 2} seconds`))} left, first hint for ${style.bold(style.purple(`question ${round + 1}/${game.questions.length}`))}: ${style.bold(`${question.answer.replace(/\s/g, ' ').replace(/[^\s]/g, '_ ').trim()}`)}`, context.room.id);
|
||||
context.sendMessage(`${style.bold(style.green(`${Math.floor(game.timeout / 3) * 2} seconds`))} left, first hint for ${style.bold(style.pink(`question ${round + 1}/${game.questions.length}`))}: ${style.bold(`${question.answer.replace(/\s/g, ' ').replace(/[^\s]/g, '_ ').trim()}`)}`, context.room.id);
|
||||
}
|
||||
|
||||
await timers.setTimeout((game.timeout / 3) * 1000, null, {
|
||||
|
@ -81,7 +70,7 @@ async function playRound(context, round = 0) {
|
|||
});
|
||||
|
||||
if (question.answer.length >= 4) {
|
||||
context.sendMessage(`${style.bold(style.green(`${Math.floor(game.timeout / 3)} seconds`))} left, second hint for ${style.bold(style.purple(`question ${round + 1}/${game.questions.length}`))}: ${style.bold(`${question.answer.slice(0, 1)} ${question.answer.slice(1, -1).replace(/\s/g, ' ').replace(/[^\s]/g, '_ ')}${question.answer.slice(-1)}`)}`, context.room.id);
|
||||
context.sendMessage(`${style.bold(style.green(`${Math.floor(game.timeout / 3)} seconds`))} left, second hint for ${style.bold(style.pink(`question ${round + 1}/${game.questions.length}`))}: ${style.bold(`${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, {
|
||||
|
@ -96,7 +85,7 @@ async function playRound(context, round = 0) {
|
|||
}
|
||||
|
||||
if (game.stopped) {
|
||||
context.sendMessage(`The game was stopped by ${style.cyan(`${config.usernamePrefix}${game.stopped.username}`)}. The answer to the last question was: ${style.bold(question.answer)}. Best players: ${getLeaders(context)}`, context.room.id);
|
||||
context.sendMessage(`The game was stopped by ${style.cyan(`${config.usernamePrefix}${game.stopped.username}`)}. The answer to the last question was: ${style.bold(question.answer)}. Best players: ${getLeaders(game.points)}`, context.room.id);
|
||||
games.delete(context.room.id);
|
||||
|
||||
return;
|
||||
|
@ -120,7 +109,7 @@ async function playRound(context, round = 0) {
|
|||
await timers.setTimeout(5000);
|
||||
|
||||
if (game.stopped) {
|
||||
context.sendMessage(`The game was stopped by ${config.usernamePrefix}${game.stopped.username}. The answer to the last question was: ${style.bold(question.answer)}. Best players: ${getLeaders(context)}`, context.room.id);
|
||||
context.sendMessage(`The game was stopped by ${config.usernamePrefix}${game.stopped.username}. The answer to the last question was: ${style.bold(question.answer)}. Best players: ${getLeaders(game.points)}`, context.room.id);
|
||||
games.delete(context.room.id);
|
||||
|
||||
return;
|
||||
|
@ -132,7 +121,7 @@ async function playRound(context, round = 0) {
|
|||
|
||||
await timers.setTimeout(2000);
|
||||
|
||||
context.sendMessage(`That's the end of the game! Best players: ${getLeaders(context)}`, context.room.id);
|
||||
context.sendMessage(`That's the end of the game! Best players: ${getLeaders(game.points)}`, context.room.id);
|
||||
|
||||
games.delete(context.room.id);
|
||||
}
|
||||
|
@ -143,7 +132,7 @@ async function start(context) {
|
|||
games.set(context.room.id, {
|
||||
round: 0,
|
||||
questions: roundQuestions,
|
||||
answers: [],
|
||||
answers: new Map(),
|
||||
points: {},
|
||||
...settings,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const style = require('./style');
|
||||
|
||||
function getLeaders(points) {
|
||||
return Object.entries(points).sort(([, scoreA], [, scoreB]) => scoreB - scoreA).map(([username, score], index) => {
|
||||
if (index === 0) {
|
||||
return `${style.bold(`${config.usernamePrefix}${username}`)} with ${style.bold(`${score}`)} points`;
|
||||
}
|
||||
|
||||
return `${style.bold(style.cyan(`${config.usernamePrefix}${username}`))} with ${style.bold(`${score}`)} points`;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
module.exports = getLeaders;
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
function getWordKey(word) {
|
||||
return word.split('').sort().join('');
|
||||
}
|
||||
|
||||
module.exports = getWordKey;
|
Loading…
Reference in New Issue