diff --git a/main.zig b/src/main.zig similarity index 100% rename from main.zig rename to src/main.zig diff --git a/src/wordle.zig b/src/wordle.zig new file mode 100644 index 0000000..f9c08f9 --- /dev/null +++ b/src/wordle.zig @@ -0,0 +1,97 @@ +/// The length of Wordle words. +const WordLen = 5; + +/// A single Wordle word (ASCII). +const Word = [WordLen]u8; + +/// The type of string literals representing words. +const WordLiteral = *const [WordLen:0]u8; + +/// A clue for a spot given a guess. +const Clue = enum { + Gray, + Yellow, + Green, +}; + +/// The list of clues for a full guess. +const Clues = [WordLen]Clue; + +/// Guesses a word given a solution and returns the clues. +pub fn checkGuess(guess: Word, solution: Word) Clues { + // init clues + var clues: Clues = undefined; + + // compare each letter pairwise + for (guess, 0..WordLen) |guessLetter, idx| { + // if letters match, the clue is green + if (guessLetter == solution[idx]) { + clues[idx] = Clue.Green; + continue; + } + + // default to a gray clue + clues[idx] = Clue.Gray; + + // if a matching letter in the solution can be found, write yellow + for (solution, 0..WordLen) |solutionLetter, _| { + if (guessLetter == solutionLetter) { + clues[idx] = Clue.Yellow; + break; + } + } + } + + // return completed clues + return clues; +} + +/// Converts a fixed-length string literal into a word. +fn strToWord(str: WordLiteral) Word { + var buffer: Word = undefined; + @memcpy(&buffer, str); + return buffer; +} + +/// Asserts that two word literals produce the expected clues. +fn expectGuessClues(guess: WordLiteral, solution: WordLiteral, expectClues: WordLiteral) error{Mismatch}!void { + // convert literals to the non-null word type + const guessWord = strToWord(guess); + const solutionWord = strToWord(solution); + const expectCluesWord = strToWord(expectClues); + + // check the guess and produce clues + const clues = checkGuess(guessWord, solutionWord); + + // confirm that the expected clues match the clues + for (clues, 0..) |clue, idx| { + // convert character to associated clue variant + const expected = switch (expectCluesWord[idx]) { + ' ' => Clue.Gray, + 'Y' => Clue.Yellow, + 'G' => Clue.Green, + else => unreachable, + }; + + // if clues don't match, return an error + if (expected != clue) { + return error.Mismatch; + } + } +} + +test "full match" { + try expectGuessClues("crane", "crane", "GGGGG"); +} + +test "one off" { + try expectGuessClues("crane", "crank", "GGGG "); +} + +test "no match" { + try expectGuessClues("salet", "fuzzy", " "); +} + +test "duplicate letters" { + try expectGuessClues("fuzzy", "zesty", " Y "); +}