169 lines
5.2 KiB
JavaScript
169 lines
5.2 KiB
JavaScript
'use strict';
|
||
|
||
const config = require('config');
|
||
|
||
const words = require('../../assets/mash-words.json');
|
||
const pickRandom = require('../utils/pick-random');
|
||
const style = require('../utils/style');
|
||
|
||
const games = new Map();
|
||
|
||
const targets = ['🐣', '🐕', '🐈', '🐢', '🐇', '🐁', '🦔'];
|
||
const predators = ['🦅', '🐍', '🦖', '🐅', '🐆', '🐊', '🐉'];
|
||
const trees = ['🌴', '🌳', '🌲', '🌵'];
|
||
const flowers = ['🌹', '🌻', '🌷', '🌱', '🍄', '🪨'];
|
||
|
||
function getGuesses(word) {
|
||
if (Array.isArray(config.hangman.guesses)) {
|
||
return config.hangman.guesses[word.length - 1] || config.hangman.guesses.at(-1);
|
||
}
|
||
|
||
return config.hangman.guesses;
|
||
}
|
||
|
||
function renderBoard(context) {
|
||
const game = games.get(context.room.id);
|
||
const word = game.partial.map((letter) => letter || '_').join(' ').toUpperCase();
|
||
const track = `${game.flower}${game.target}${game.track.slice(0, getGuesses(game.word) - game.progress).join('')}${game.predator}`;
|
||
|
||
context.sendMessage(`${style.bold(style.code(word))} ${track}`, context.room.id);
|
||
}
|
||
|
||
function getOutcome(target, predator) {
|
||
return pickRandom([
|
||
`${target} and ${predator} will now have to spend time together`,
|
||
`${target} will now have to listen to boring stories from ${predator}`,
|
||
`${target} must now endure a horrible tickle attack from ${predator}`,
|
||
`${target} and ${predator} are now going to waste an entire day arguing which one of them has more legs`,
|
||
`${predator} is out of breath now`,
|
||
`${target} will now have to explain why they never answer any texts from ${predator}`,
|
||
]);
|
||
}
|
||
|
||
function progress(context) {
|
||
const game = games.get(context.room.id);
|
||
|
||
game.progress += 1;
|
||
|
||
if (game.progress > getGuesses(game.word)) {
|
||
context.sendMessage(`The word ${style.bold(game.word)} was not guessed. ${getOutcome(game.target, game.predator)}.`, context.room.id);
|
||
games.delete(context.room.id);
|
||
|
||
return;
|
||
}
|
||
|
||
renderBoard(context);
|
||
}
|
||
|
||
function playLetter(playedLetter, context) {
|
||
const game = games.get(context.room.id);
|
||
|
||
if (game.partial.includes(playedLetter)) {
|
||
return;
|
||
}
|
||
|
||
if (!game.word.includes(playedLetter)) {
|
||
progress(context);
|
||
return;
|
||
}
|
||
|
||
game.partial = game.partial.map((letter, index) => letter
|
||
|| (game.word[index] === playedLetter && game.word[index])
|
||
|| null);
|
||
|
||
renderBoard(context);
|
||
|
||
if (game.partial.every((letter) => letter !== null)) {
|
||
context.sendMessage(`${style.bold('The word was completed.')} ${style.username(context.user.prefixedUsername)} gets a point!`, context.room.id);
|
||
context.setPoints(context.user);
|
||
|
||
games.delete(context.room.id);
|
||
}
|
||
}
|
||
|
||
function playWord(word, context) {
|
||
const game = games.get(context.room.id);
|
||
|
||
if (word === game.word) {
|
||
context.sendMessage(`The word ${style.answer(word)} was guessed, ${style.username(context.user.prefixedUsername)}, who gets a point!`, context.room.id);
|
||
context.setPoints(context.user);
|
||
|
||
games.delete(context.room.id);
|
||
|
||
return;
|
||
}
|
||
|
||
if (Math.abs(game.word.length - word.length) <= 1 && word.split('').some((letter, index) => game.word[index] === letter)) {
|
||
context.sendMessage(`That's not it, ${context.user.prefixedUsername}.`, context.room.id);
|
||
progress(context);
|
||
}
|
||
}
|
||
|
||
function stop(context) {
|
||
const game = games.get(context.room.id);
|
||
|
||
if (!game) {
|
||
context.sendMessage(`There is no game going on, ${style.username(context.user.prefixedUsername)}. You can start one with ${config.prefix}hunt!`, context.room.id);
|
||
return;
|
||
}
|
||
|
||
games.delete(context.room.id);
|
||
context.sendMessage(`The game was stopped by ${style.username(context.user.prefixedUsername)}. The word was ${style.bold(game.word)} with ${getGuesses(game.word) - game.progress} guesses remaining.`, context.room.id);
|
||
}
|
||
|
||
function onCommand(args, context) {
|
||
if (context.subcommand === 'stop') {
|
||
stop(context);
|
||
return;
|
||
}
|
||
|
||
const minLength = Number(args[0]) || config.hangman.minLength;
|
||
const maxLength = Number(args[0]) || config.hangman.maxLength;
|
||
|
||
const playableWords = Array.from({ length: (maxLength - minLength) + 1 }, (value, index) => Object.values(words[index + minLength] || {})).flat(2);
|
||
const word = pickRandom(playableWords);
|
||
|
||
if (!word) {
|
||
context.sendMessage('Sorry, I was not able to find a word to play.', context.room.id);
|
||
return;
|
||
}
|
||
|
||
context.logger.info(`Hangman played '${word.word}' with ${getGuesses(word)} guesses`);
|
||
|
||
games.set(context.room.id, {
|
||
word: word.word,
|
||
progress: 0,
|
||
defintiions: word.definitions,
|
||
partial: word.word.split('').map(() => null),
|
||
flower: pickRandom(flowers),
|
||
target: pickRandom(targets),
|
||
predator: pickRandom(predators),
|
||
track: Array.from({ length: getGuesses(word) }, (value, index) => (index % 2 ? pickRandom(trees) : ' ')), // Em Space to ensure proper spacing in SChat
|
||
});
|
||
|
||
renderBoard(context);
|
||
}
|
||
|
||
function onMessage(message, context) {
|
||
if (games.has(context.room.id)) {
|
||
const letter = message.body.match(/^\s*\w\s*$/)?.[0];
|
||
const word = message.body.match(/^\s*\w{2,}\s*$/)?.[0];
|
||
|
||
if (letter) {
|
||
playLetter(letter.toLowerCase().trim(), context);
|
||
return;
|
||
}
|
||
|
||
if (word) {
|
||
playWord(word.toLowerCase().trim(), context);
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
onCommand,
|
||
onMessage,
|
||
commands: ['hangman'],
|
||
help: `Play letters and guess the word before the predator reaches its prey! Essentially, horizontal Hangman. Try ${config.prefix}hunt [length] for a specific word length.`,
|
||
};
|