Created
January 23, 2022 20:05
-
-
Save tshddx/72b6a4a16d188eedef14d669aed756ef to your computer and use it in GitHub Desktop.
Script to test Wordle bots against each other
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Word = string; | |
// A game is a series of guesses and a status (note that a Game might still be | |
// in progress, and the current game status is the same as the status of the | |
// last guess). | |
type Game = { | |
readonly guesses: Guess[]; | |
readonly status: Status; | |
}; | |
// A guess is an array of LetterInfos, one for each letter in the guessed word. | |
type Guess = { | |
readonly letterInfos: LetterInfo[]; | |
readonly status: Status; | |
}; | |
type Status = 'in progress' | 'succeeded' | 'failed'; | |
const MAXIMUM_GUESSES = 6; | |
// These are the green/yellow/gray colors for each letter of a guessed word. | |
type LetterInfo = { | |
readonly letter: string; | |
readonly index: number; | |
readonly info: 'correct position' | 'incorrect position' | 'not in word'; | |
}; | |
// A player has a function which takes a game and guesses the next word. | |
type Player = { | |
readonly name: string; | |
readonly description: string; | |
readonly guessAWord: (game: Game) => Word; | |
}; | |
function runGame(correctWord: Word, player: Player): Game { | |
const correctLetters = [...correctWord.toUpperCase()]; | |
// Start a new game. | |
let game: Game = { guesses: [], status: 'in progress' }; | |
// Take guesses from the player until the game is completed. | |
while (game.status === 'in progress') { | |
const guessedWord = player.guessAWord(game); | |
const guessedLetters = [...guessedWord.toUpperCase()]; | |
const letterInfos = guessedLetters.map((letter, index): LetterInfo => { | |
const info = | |
letter === correctLetters[index] | |
? 'correct position' | |
: correctLetters.includes(letter) | |
? 'incorrect position' | |
: 'not in word'; | |
return { letter, info, index }; | |
}); | |
// Determine the status of this guess and concatenate it onto the game's | |
// guesses. | |
const status = letterInfos.every(({ info }) => info === 'correct position') | |
? 'succeeded' | |
: game.guesses.length + 1 >= MAXIMUM_GUESSES | |
? 'failed' | |
: 'in progress'; | |
const guess: Guess = { letterInfos, status }; | |
const guesses = [...game.guesses, guess]; | |
game = { guesses, status }; | |
} | |
return game; | |
} | |
const WORDS = ['way', 'day', 'man', 'eye', 'job', 'lot', 'lol'].map((word) => | |
word.toUpperCase() | |
); | |
const ConstantGuessPlayer: Player = { | |
name: 'ConstantGuessPlayer', | |
description: 'always guesses the word "lol"', | |
guessAWord: (_game) => 'lol', | |
}; | |
const FirstPossibleWordPlayer: Player = { | |
name: 'FirstPossibleWordPlayer', | |
description: | |
'determines all *possible* words from past clues and guesses the first one', | |
guessAWord: (game) => { | |
// Get all letterInfos from all guesses. | |
const allLetterInfos: LetterInfo[] = []; | |
game.guesses.forEach((guess) => { | |
guess.letterInfos.forEach((letterInfo) => | |
allLetterInfos.push(letterInfo) | |
); | |
}); | |
// Check for words that match all green letters. | |
const greenLetters: LetterInfo[] = []; | |
allLetterInfos | |
.filter(({ info }) => info === 'correct position') | |
.forEach((letterInfo) => greenLetters.push(letterInfo)); | |
function matchesAllGreenLetters(word: Word) { | |
return greenLetters.every( | |
({ letter, index }) => word.charAt(index) === letter | |
); | |
} | |
// Check for words that match all yellow letters. | |
const yellowLetters: LetterInfo[] = []; | |
allLetterInfos | |
.filter(({ info }) => info === 'incorrect position') | |
.forEach((letterInfo) => yellowLetters.push(letterInfo)); | |
function matchesAllYellowLetters(word: Word) { | |
return yellowLetters.every( | |
({ letter, index }) => | |
word.charAt(index) !== letter && word.includes(letter) | |
); | |
} | |
// Check for words containing no gray letters. | |
const grayLetters: string[] = []; | |
allLetterInfos | |
.filter(({ info }) => info === 'not in word') | |
.forEach(({ letter }) => grayLetters.push(letter)); | |
function containsNoGrayLetters(word: Word) { | |
return grayLetters.every((letter) => !word.includes(letter)); | |
} | |
return WORDS.find( | |
(word) => | |
matchesAllGreenLetters(word) && | |
matchesAllYellowLetters(word) && | |
containsNoGrayLetters(word) | |
)!; | |
}, | |
}; | |
const players = [ | |
// ConstantGuessPlayer, | |
FirstPossibleWordPlayer, | |
]; | |
WORDS.forEach((correctWord) => { | |
console.log('\n-----------------------------------------------------------'); | |
console.log(bold(`The correct word is ${[...correctWord].join(' ')}`)); | |
console.log(); | |
players.forEach((player) => { | |
console.log(`${player.name} (${player.description})`); | |
const completedGame = runGame(correctWord, player); | |
completedGame.guesses.forEach((guess, index) => { | |
const numeral = `${index + 1}.`; | |
const lettersWithColors = guess.letterInfos | |
.map(({ letter, info }) => { | |
const color = | |
info === 'correct position' | |
? green | |
: info === 'incorrect position' | |
? yellow | |
: gray; | |
return color(' ' + letter + ' '); | |
}) | |
.join(' '); | |
const gameResult = | |
guess.status === 'succeeded' | |
? 'Game won!' | |
: guess.status === 'failed' | |
? 'Game lost' | |
: ''; | |
console.log(numeral, lettersWithColors, gameResult); | |
}); | |
console.log(); | |
}); | |
}); | |
// | |
// Bash string formatting utilities | |
// | |
function green(s: string) { | |
let output = `\x1b[42m${s}\x1b[0m`; | |
return output; | |
} | |
function yellow(s: string) { | |
let output = `\x1b[43m${s}\x1b[0m`; | |
return output; | |
} | |
function gray(s: string) { | |
let output = `\x1b[100m${s}\x1b[0m`; | |
return output; | |
} | |
function bold(s: string) { | |
let output = `\x1b[22m${s}\x1b[0m`; | |
return output; | |
} | |
// | |
// General utilities | |
// | |
function last<T>(array: T[]): T { | |
return array[array.length - 1]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment