Compare commits

...
6 Commits
Author SHA1 Message Date
soup 64882d2cf2 Use mem.eql for dictionary test 2026-06-11 21:08:15 -06:00
soup 8691afa048 Update dictionary tests 2026-06-11 21:07:25 -06:00
soup b63a752d8a Demordle -> Crowdle 2026-06-11 20:47:22 -06:00
soup 73e92c88b9 Basic stdio Wordle game 2026-06-11 20:47:14 -06:00
soup ce081ba799 Switch dictionary to only common words 2026-06-11 20:46:41 -06:00
soup 39a25350ee Basic Wordle game internals 2026-06-11 20:38:36 -06:00
5 changed files with 2465 additions and 10676 deletions
+2 -4
View File
@@ -1,8 +1,6 @@
# Demordle
# Crowdle
- TODO: come up with a better name.
**Demordle** is *democratic wordle*. This is a nebulous premise with multiple interpretations; so instead of picking one, `demordle` implements them all!
**Crowdle** is *democratic wordle*. This is a nebulous premise with multiple interpretations; so instead of picking one, `crowdle` implements them all!
In all cases, "democratic" means _multiplayer cooperative_ where game actions are decided by vote. The players agree to play ahead of time and share a victory or loss. We also specifically mean _direct_ democracy where every player has equal representation in the voting process.[^representative]
+2315 -10657
View File
File diff suppressed because it is too large Load Diff
+3 -13
View File
@@ -62,18 +62,8 @@ fn loadDict() []const Wordle.Word {
// TODO: binary search? need to assert that words are sorted
pub fn isInDictionary(word: Wordle.Word) bool {
// exhaustively search through dictionary
for (getDictionary()) |entry| {
// compare each letter at a time
var match = true;
for (entry, word) |entryLetter, wordLetter| {
if (entryLetter != wordLetter) {
match = false;
break;
}
}
// if every letter matches, return a complete match
if (match) {
for (getDictionary()) |*entry| {
if (std.mem.eql(u8, entry, &word)) {
return true;
}
}
@@ -93,7 +83,7 @@ fn expectNotDictionary(word: Wordle.WordLiteral) !void {
}
test "in dictionary" {
try expectDictionary("abash");
try expectDictionary("aback");
}
test "not in dictionary" {
+83 -2
View File
@@ -1,5 +1,86 @@
const std = @import("std");
const Wordle = @import("wordle.zig");
const Dict = @import("dict.zig");
pub fn main() void {
std.debug.print("Hello, {s}!\n", .{"World"});
pub fn main() !void {
// acquire stdout writer
var stdoutBuf: [1024]u8 = undefined;
var stdoutFile = std.fs.File.stdout().writer(&stdoutBuf);
if (stdoutFile.err) |err| return err;
const stdout = &stdoutFile.interface;
// acquire stdin reader
var stdinBuf: [1024]u8 = undefined;
var stdinFile = std.fs.File.stdin().reader(&stdinBuf);
if (stdinFile.err) |err| return err;
const stdin = &stdinFile.interface;
// select a random dictionary word as the solution
const dict = Dict.getDictionary();
const solutionIdx = std.crypto.random.intRangeAtMost(usize, 0, dict.len);
// create a game with the chosen solution
var game: Wordle.Game = .{ .solution = dict[solutionIdx] };
// play game until over
while (!game.isOver()) {
// display guess prompt
try stdout.writeAll("Guess: ");
try stdout.flush();
// read next guess
const input = try stdin.takeDelimiter('\n') orelse continue;
// assert that input is correct size
if (input.len != Wordle.WordLen) {
try stdout.writeAll("Guess is wrong length.\n");
try stdout.flush();
continue;
}
// convert to word
var guess: Wordle.Word = undefined;
@memcpy(&guess, input);
// test if guess is valid
if (!Dict.isInDictionary(guess)) {
try stdout.writeAll("Guess is not in dictionary.\n");
try stdout.flush();
continue;
}
// display clues
const clues = Wordle.checkGuess(guess, game.solution);
try writeClues(stdout, clues);
// add guess
try game.addGuess(guess);
}
// display message win/lose
if (game.isWon()) {
try stdout.writeAll("You win!\n");
} else {
try stdout.writeAll("You lose :(\n");
}
// display spoilered guesses
for (game.guesses()) |guess| {
try writeClues(stdout, Wordle.checkGuess(guess, game.solution));
}
// flush stdout
try stdout.flush();
}
pub fn writeClues(out: *std.io.Writer, clues: Wordle.Clues) !void {
for (clues) |clue| {
try out.writeAll(switch (clue) {
Wordle.Clue.Gray => "⬛",
Wordle.Clue.Yellow => "🟨",
Wordle.Clue.Green => "🟩",
});
}
try out.writeAll("\n");
}
+62
View File
@@ -9,6 +9,51 @@ pub const Word = [WordLen]u8;
/// The type of string literals representing words.
pub const WordLiteral = *const [WordLen:0]u8;
/// The number of rounds in a single Wordle game.
pub const GameLen = 6;
/// An error reported when a move cannot be played since the game is over.
pub const GameOver = error{GameOver};
/// A structure for a single Wordle game.
pub const Game = struct {
/// The hidden word to guess.
solution: Word,
/// The round number (starts with 0).
round: u32 = 0,
/// The current (and future) word guesses.
///
/// The values of guessBuf[self.round..] are undefined.
guessBuf: [GameLen]Word = @splat(@splat(0)),
/// Fetches the current guesses.
pub fn guesses(self: *const Game) []const Word {
return self.guessBuf[0..self.round];
}
/// Tests if the game is won.
pub fn isWon(self: Game) bool {
for (self.guesses()) |guess|
if (std.mem.eql(u8, &guess, &self.solution)) return true;
return false;
}
/// Tests if the game is over.
pub fn isOver(self: Game) bool {
return self.round >= GameLen or self.isWon();
}
/// Guesses a word unless the game is over.
pub fn addGuess(self: *Game, guess: Word) GameOver!void {
if (self.isOver()) return error.GameOver;
self.guessBuf[self.round] = guess;
self.round += 1;
}
};
/// A clue for a spot given a guess.
pub const Clue = enum {
Gray,
@@ -98,3 +143,20 @@ test "anagram" {
test "duplicate letters" {
try expectGuessClues("fuzzy", "zesty", " Y G");
}
test "game" {
// default state
var game: Game = .{ .solution = strToWord("poise") };
try std.testing.expect(!game.isWon());
try std.testing.expect(!game.isOver());
// first guess
try game.addGuess(strToWord("brown"));
try std.testing.expect(!game.isWon());
try std.testing.expect(!game.isOver());
// second guess
try game.addGuess(strToWord("poise"));
try std.testing.expect(game.isWon());
try std.testing.expect(game.isOver());
}