Added playable round to letters game.

This commit is contained in:
2022-10-21 05:07:32 +02:00
parent 4534a1debe
commit 55343d5de7
7 changed files with 160 additions and 48 deletions

View File

@@ -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;
}
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 pickLetter(type, context) {
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;
}
game.word = `${game.word}${game[type].pop()}`;
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) {
if (/(^|\b)cons?(onant)?($|\b)/i.test(message.body)) {
pickLetter('consonants', context);
return;
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)) {
pickLetters('consonants', context);
return;
}
if (/(^|\b)vow(el)?($|\b)/i.test(message.body)) {
pickLetters('vowels', context);
return;
}
}
if (/(^|\b)vow(el)?($|\b)/i.test(message.body)) {
pickLetter('vowels', context);
if (game?.state === 'words') {
playWord(message.body, context);
}
}
module.exports = {
name: 'Letters',
onCommand,
onMessage,
};