From 5e396a4abeb93ab9c4bb3465cd742f2465b8fd16 Mon Sep 17 00:00:00 2001 From: ThePendulum Date: Mon, 3 Jun 2024 01:02:30 +0200 Subject: [PATCH] Added/implemented wordle game. --- config/default.js | 6 ++ src/games/wordle.js | 177 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 src/games/wordle.js diff --git a/config/default.js b/config/default.js index 4fedab8..c871b36 100755 --- a/config/default.js +++ b/config/default.js @@ -27,6 +27,7 @@ module.exports = { 'chat', 'mash', 'trivia', + 'wordle', 'letters', 'numbers', 'hunt', @@ -133,6 +134,11 @@ module.exports = { z: 1, }, }, + wordle: { + minLength: 3, + defaultLength: 5, + highlightRepeat: false, // in wordle, if the I in the guess HINDI is in the wrong place, only the first I is orange + }, numbers: { length: 6, timeout: 90, diff --git a/src/games/wordle.js b/src/games/wordle.js new file mode 100644 index 0000000..1951b36 --- /dev/null +++ b/src/games/wordle.js @@ -0,0 +1,177 @@ +'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 = style.grey(style.code('[')); + + const middle = letters.map((letter) => { + if (letter === null) { + return style.code(' '); // em space, U+2003, charcode 8195 + } + + if (letter[1] === true) { + return style.green(style.bold(style.code(letter[0].toUpperCase()))); + } + + if (letter[1] === false) { + return style.orange(style.bold(style.code(letter[0].toUpperCase()))); + } + + return style.grey(style.bold(style.code(letter[0].toUpperCase()))); + }).join(style.grey(style.code('|'))); + + const suffix = `${style.grey(style.code(']'))}`; + + if (showLetters) { + const letterBoard = Array.from(wordle.letters).map(([letter, state]) => { + if (state === true) { + return style.green(style.bold(letter)); + } + + if (state === false) { + return style.orange(style.bold(letter)); + } + + return style.grey(letter); + }).join(' '); + + return `${prefix}${middle}${suffix} ${letterBoard}`; // eslint-disable-line no-irregular-whitespace + } + + return `${prefix}${middle}${suffix}`; +} + +function start(length = settings.defaultLength, 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, + definitions: word.definitions, + letters: new Map(alphabet.map((letter) => [letter, null])), + guesses: [], + }); + + context.sendMessage(`${getBoard(Array.from({ length }, () => null), false, context)} guess the 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 processed = new Set(); + + const check = upperGuess.split('').map((letter, index) => { + const alreadySeen = settings.highlightRepeat ? false : processed.has(letter); + + processed.add(letter); + + if (wordle.word[index] === letter) { + wordle.letters.set(letter, true); + return [letter, true]; + } + + if (wordle.word.includes(letter)) { + wordle.letters.set(letter, false); + return [letter, alreadySeen ? null : false]; // repeating letter in the wrong place is not highlighted + } + + wordle.letters.delete(letter); + + return [letter, null]; + }); + + 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! ${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 = Number(args[0]) || settings.defaultLength; + + 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, 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', 'guess', 'w'], + help: `Guess the ${settings.defaultLength}-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.`, + onCommand, + // onMessage, +};