'use strict'; const config = require('config'); const crypto = require('crypto'); const style = require('../utils/style'); const words = require('../../assets/mash-words.json'); const settings = { ...config.wordle }; const wordles = new Map(); const alphabet = Array.from({ length: 26 }, (value, index) => String.fromCharCode(65 + index)); function getBoard(letters, showLetters, context) { const wordle = wordles.get(context.room.id); const prefix = config.platform === 'irc' ? '' : style.grey(style.code('[')); const middle = letters.map((letter) => { if (letter === null) { return config.platform === 'irc' ? style.bgsilver(' ? ') : style.code(' '); // em space, U+2003, charcode 8195 } if (letter[1] === true) { return config.platform === 'irc' ? style.bggreen(` ${letter[0].toUpperCase()}${style.green('*')}`) : style.green(style.bold(style.code(letter[0].toUpperCase()))); } if (letter[1] === false) { return config.platform === 'irc' ? style.bgyellow(` ${letter[0].toUpperCase()}${style.yellow('?')}`) : style.orange(style.bold(style.code(letter[0].toUpperCase()))); } return config.platform === 'irc' ? style.bgsilver(` ${letter[0].toUpperCase()} `) : style.grey(style.bold(style.code(letter[0].toUpperCase()))); }).join(config.platform === 'irc' ? '' : style.grey(style.code('|'))); const suffix = config.platform === 'irc' ? '' : `${style.grey(style.code(']'))}`; if (showLetters) { const letterBoard = Array.from(wordle.letters).map(([letter, state]) => { if (state === true) { return config.platform === 'irc' ? style.bggreen(`${letter}${style.green('*')}`) : style.green(style.bold(letter)); } if (state === false) { return config.platform === 'irc' ? style.bgyellow(`${letter}${style.yellow('?')}`) : style.orange(style.bold(letter)); } return config.platform === 'irc' ? letter : style.grey(letter); }).join(config.platform === 'irc' ? ' ' : ' '); // regular space vs em space return `${prefix}${middle}${suffix}${wordle.mode === 'hard' ? ' (Hard)' : ''} Letters: ${letterBoard}`; // eslint-disable-line no-irregular-whitespace } return `${prefix}${middle}${suffix}`; } function start(length = settings.length, mode = settings.mode, context) { const wordPool = words[length]; if (length < settings.minLength) { context.sendMessage(`Wordle must be at least ${settings.minLength} letters long.`, context.room.id); return; } if (!wordPool) { context.sendMessage(`No words with ${length} letters available.`, context.room.id); return; } const wordList = Object.values(wordPool).flat(); const word = wordList[crypto.randomInt(wordList.length)]; wordles.set(context.room.id, { word: word.word.toUpperCase(), wordList, mode, definitions: word.definitions, letters: new Map(alphabet.map((letter) => [letter, null])), guesses: [], }); if (config.platform === 'irc') { context.sendMessage(`${getBoard(Array.from({ length }, () => null), false, context)}${mode === 'hard' ? ' (Hard)' : ''} guess the word with ${config.prefix}w [word]! ${style.bgsilver(' X ')} not in word; ${style.bgyellow(` X${style.yellow('?')}`)} wrong position; ${style.bggreen(` X${style.green('*')}`)} correct position.`, context.room.id); // eslint-disable-line no-irregular-whitespace } else { context.sendMessage(`${getBoard(Array.from({ length }, () => null), false, context)}${mode === 'hard' ? ' (Hard)' : ''} guess the word with ${config.prefix}w [word]! ${style.grey(style.bold('X'))} not in word; ${style.orange(style.bold('X'))} wrong position; ${style.green(style.bold('X'))} correct position.`, context.room.id); } context.logger.info(`Wordle started: ${word.word.toUpperCase()}`); } function play(guess, context) { const wordle = wordles.get(context.room.id); if (!wordle) { context.sendMessage(`There's no wordle going on. Start one with ${config.prefix}wordle [length]!`, context.room.id); return; } if (!guess || guess.length !== wordle.word.length) { context.sendMessage(`Your guess needs to be ${wordle.word.length} letters.`, context.room.id); return; } if (!wordle.wordList.some((definition) => definition.word.toLowerCase() === guess.toLowerCase())) { context.sendMessage(`The word '${guess}' is not in my dictionary.`, context.room.id); return; } const upperGuess = guess.toUpperCase(); const occurrences = wordle.word.split('').reduce((acc, letter) => acc.set(letter, (acc.get(letter) || 0) + 1), new Map()); const guessLetters = upperGuess.split(''); const check = guessLetters.map((letter) => [letter, null]); const prevGuess = wordle.guesses.at(-1)?.[1]; if (wordle.mode === 'hard' && prevGuess) { const prevLetters = prevGuess.split(''); const valid = prevLetters.every((letter, index) => { if (wordle.letters.get(letter) === false && !guessLetters.includes(letter)) { context.sendMessage(`(Hard) Your guess must include the letter ${config.platform === 'irc' ? style.bgyellow(` ${letter} `) : `${style.grey(style.code('['))}${style.orange(style.bold(style.code(letter)))}${style.grey(style.code(']'))}` }.`, context.room.id); return false; } if (wordle.letters.get(letter) === true && letter === wordle.word[index] && letter !== guessLetters[index]) { context.sendMessage(`(Hard) Your guess must include the letter ${config.platform === 'irc' ? style.bggreen(` ${letter} `) : `${style.grey(style.code('['))}${style.green(style.bold(style.code(letter)))}${style.grey(style.code(']'))}` } in the correct position.`, context.room.id); return false; } return true; }); if (!valid) { return; } } // correct guessLetters.forEach((letter, index) => { if (wordle.word[index] === letter) { wordle.letters.set(letter, true); check[index] = [letter, true]; } }); // wrong place guessLetters.forEach((letter, index) => { if (wordle.word.includes(letter)) { if (wordle.letters.get(letter) !== true) { wordle.letters.set(letter, false); } const marks = check.filter(([checkLetter, status]) => checkLetter === letter && status !== null).length; if (check[index][1] !== true && marks < occurrences.get(letter)) { check[index] = [letter, false]; } return; } wordle.letters.delete(letter); }); wordle.guesses = wordle.guesses.concat([[context.user.username, upperGuess]]); if (upperGuess === wordle.word) { const points = Math.max((wordle.word.length + 1) - wordle.guesses.length, 1); const definition = wordle.definitions[0] ? `: ${style.italic(`${wordle.definitions[0].slice(0, 100)}${wordle.definitions[0].length > 100 ? '...' : ''}`)}` : ''; context.setPoints(context.user, points); context.sendMessage(`${getBoard(check, false, context)} is correct in ${wordle.guesses.length} guesses! ${style.bold(style.cyan(`${config.usernamePrefix}${context.user.username}`))} gets ${points} ${points > 1 ? 'points' : 'point'}. ${style.bold(wordle.word)}${definition}`, context.room.id); wordles.delete(context.room.id); return; } context.sendMessage(`${getBoard(check, true, context)}`, context.room.id); } function onCommand(args, context) { const wordle = wordles.get(context.room.id); const length = args.map((arg) => Number(arg)).find(Boolean) || settings.length; const mode = args.find((arg) => arg === 'easy' || arg === 'hard') || (context.command === 'hardle' && 'hard') || settings.mode; if (['guess', 'w'].includes(context.command)) { play(args[0], context); return; } if (context.subcommand === 'stop') { if (wordle) { context.sendMessage(`The game was stopped by ${config.usernamePrefix}${context.user.username}. The word was ${style.bold(wordle.word)}.`, context.room.id); wordles.delete(context.room.id); return; } context.sendMessage(`The there is no wordle going on, ${config.usernamePrefix}${context.user.username}, start one with ${config.prefix}wordle [length]!`, context.room.id); return; } if (!wordle && context.room.id) { start(length, mode, context); return; } context.sendMessage(`There is already a ${wordle.word.length}-letter wordle going on, guess with ${config.prefix}w [word]!`, context.room.id); } module.exports = { name: 'Wordle', commands: ['wordle', 'hardle', 'lingo', 'guess', 'w'], help: `Guess the ${settings.length}-letter word on the board. Submit a guess with ${config.prefix}w [word]. If the letter is green, it is in the correct place. If it is yellow, it is part of the word, but not in the correct place. The number of letters in the word is the highest score you can get. You lose 1 point per guess, down to 1 point. Change the numbers of letters or switch to hard mode with ~wordle [length] [hard] or ~hardle [length].`, onCommand, // onMessage, };