REVISED
Code should be correct, maintainable, robust, reasonably efficient, and, most important, readable.
Wordle is a clever, challenging, and fun game implemented by a skilled software engineer.
Hi, I'm Josh [Wardle].
https://powerlanguage.co.uk/
Wordle is daily word guessing game I made.
The Wordle tile coloring rules are:
Guess the WORDLE in six tries.
Each guess must be a valid five-letter word. Hit the enter button to submit.
After each guess, the color of the tiles will change to show how close your guess was to the word.
Green: The letter is in the word and in the correct spot.
Yellow: The letter is in the word but in the wrong spot.
Gray: The letter is not in the word in any spot.
Review 1 is a code review that critiques the OP's code and provides a revised, simplified version of the OP's code that conforms to Wordle rules. The revised code efficiently satisfies the rule that five letters must be entered before the color of the tiles change by returning a five letter array of tile colors.
In a comment on Review 1, the OP says: "the "wordle.js" I'm plugging this into expects a function with the given signature [(word, guess, index)
]; I wanted to surgically replace just the buggy coloring function."
Review 2 refactors the revised solution from Review 1 to provide an efficient function with the expected signature: (word, guess, index)
.
REVIEW 1
I'd like to know whether it's correct
Your code is complicated. It's hard to prove it is correct.
Production executable code, like Wordle, is minified, use spacing and useful comments to enhance readability.
The letter background colors will be used both inside and outside the function. Use outside the function color constants.
Simplify your code. In particular, eliminate extraneous methods: split
, filter
, from
, keys
, and so on. Some of these methods implement implicit loops, which, inside a for
loop, have O(n**2) complexity. The simplified code has only two loops with O(n) complexity.
Don't use var
for variable declarations. Use let
or const
.
Don't use ==
for equality; use ===
for strict equality.
There is no reason to limit the function to five letter words and guesses. However, check the function word and guess length equality invariant.
The simplfied code, as required by Wordle, returns a complete array of letter background colors. The original function only returns the color for a single letter. There is (times five) computational rundundancy to color all five letters of a guess.
Using jsbench.me, coloring a five letter guess using the original code is over 90% slower than the simplified code.
Here is a simplified version of your code:
// letter background colors
const COLOR_NOT_ENTERED = "White";
const COLOR_CORRECT_SPOT = "Green";
const COLOR_WRONG_SPOT = "Yellow";
const COLOR_NOT_ANY_SPOT = "Gray";
// guessColors returns an array of guess letter background colors.
function guessColors(word, guess) {
let colors = new Array(guess.length);
if (word.length !== guess.length) {
return colors.fill(COLOR_NOT_ENTERED);
}
// color matched guess letters as correct-spot,
// and count unmatched word letters
let unmatched = {}; // unmatched word letters
for (let i = 0; i < word.length; i++) {
let letter = word[i];
if (letter === guess[i]) {
colors[i] = COLOR_CORRECT_SPOT;
} else {
unmatched[letter] = (unmatched[letter] || 0) + 1;
}
}
// color unmatched guess letters right-to-left,
// allocating remaining word letters as wrong-spot,
// otherwise, as not-any-spot
for (let i = 0; i < guess.length; i++) {
let letter = guess[i];
if (letter !== word[i]) {
if (unmatched[letter]) {
colors[i] = COLOR_WRONG_SPOT;
unmatched[letter]--;
} else {
colors[i] = COLOR_NOT_ANY_SPOT;
}
}
}
return colors;
}
if there's some way to compute just the color of the indexth tile, without computing all the tiles' colors as a side effect.
There is not. Why do you want to do that?
Consider a word
[ A X A A A ]
and a partial (index 0) guess of
[ X - - - - ]
which evaluates to colors
[ Yellow, ------, ------, ------, ------ ]
Now, add another letter to the guess, a partial (index 1) guess of
[ X X - - - ]
which evaluates to colors
[ Grey, Green, ------, ------, ------ ]
Partial colors evaluation is not always stable.
REVIEW 2
Here is a revised, simplified version of the OP's code to provide a more readable, more efficient function with the expected signature: (word, guess, index)
.
Using jsbench.me, the OP's code is about 96% slower than the following simplified code. The simplified code is about 25 times faster. The simplified code only has one loop. The simplified code does not contain extraneous code.
Wordle rules:
Each guess must be a valid five-letter word.
Hit the enter button to submit.
After each guess, the color of the tiles will change.
For an official, accurate Wordle guess tile color, on each call to the function "guess must be a five-letter word." Wait until the guess is submitted: "Hit the enter button to submit."
A simple example illustrates the problem. Using Wordle rules, tile color depends on guess letters before and after guess[index]:
AXAAA
X
[ 'Yellow', 'Gray', 'Gray', 'Gray', 'Gray' ]
AXAAA
XX
[ 'Gray', 'Green', 'Gray', 'Gray', 'Gray' ]
The tile color for guess[0] varies depending on how many guess letters are entered.
Also, coloring tiles before the guess is complete would ruin the game by making it too easy.
// Wordle letter tile background colors
const COLOR_CORRECT_SPOT = "Green";
const COLOR_WRONG_SPOT = "Yellow";
const COLOR_NOT_ANY_SPOT = "Gray";
// guessColor returns the guess[index] letter tile background color.
//
// Wordle tile coloring rules:
// Each guess must be a valid five-letter word.
// Hit the enter button to submit.
// After each guess, the color of the tiles will change.
// Green: The letter is in the word and in the correct spot.
// Yellow: The letter is in the word but in the wrong spot.
// Gray: The letter is not in the word in any spot.
function guessColor(word, guess, index) {
// correct (matched) index letter
if (guess[index] === word[index]) {
return COLOR_CORRECT_SPOT;
}
let wrongWord = wrongGuess = 0;
for (let i = 0; i < word.length; i++) {
// count the wrong (unmatched) letters
if (word[i] === guess[index] && guess[i] !== guess[index] ) {
wrongWord++;
}
if (i <= index) {
if (guess[i] === guess[index] && word[i] !== guess[index]) {
wrongGuess++;
}
}
// an unmatched guess letter is wrong if it pairs with
// an unmatched word letter
if (i >= index) {
if (wrongGuess === 0) {
break;
}
if (wrongGuess <= wrongWord) {
return COLOR_WRONG_SPOT;
}
}
}
// otherwise not any
return COLOR_NOT_ANY_SPOT;
}
function testGuessColor(tests) {
for (let [word, guess] of tests) {
console.log(word);
console.log(guess);
let colors = [];
for (let i = 0; i < word.length; i++) {
colors.push(guessColor(word, guess, i));
}
console.log(colors);
}
}
let tests = [
['BURNT', 'TOAST'],
['MAXIM', 'MAMMA'],
['SWIFT', 'IIWIS'],
['ABBEY', 'BOBBY'],
["ABCDE", "ABCDE"],
["ABCDE", "VWXYZ"],
];
testGuessColor(tests);
$ node wordle.js
BURNT
TOAST
[ 'Gray', 'Gray', 'Gray', 'Gray', 'Green' ]
MAXIM
MAMMA
[ 'Green', 'Green', 'Yellow', 'Gray', 'Gray' ]
SWIFT
IIWIS
[ 'Yellow', 'Gray', 'Yellow', 'Gray', 'Yellow' ]
ABBEY
BOBBY
[ 'Yellow', 'Gray', 'Green', 'Gray', 'Green' ]
ABCDE
ABCDE
[ 'Green', 'Green', 'Green', 'Green', 'Green' ]
ABCDE
VWXYZ
[ 'Gray', 'Gray', 'Gray', 'Gray', 'Gray' ]
$