From a916a31c697ffe6ba6b68e47191a60a6bd9db93f Mon Sep 17 00:00:00 2001 From: Soup For My Family Date: Wed, 10 Jun 2026 02:53:54 -0600 Subject: [PATCH] Add naive voting logic --- src/vote.zig | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/vote.zig diff --git a/src/vote.zig b/src/vote.zig new file mode 100644 index 0000000..6bc2525 --- /dev/null +++ b/src/vote.zig @@ -0,0 +1,132 @@ +const std = @import("std"); +const Wordle = @import("wordle.zig"); +const Dict = @import("dict.zig"); + +/// A "pattern": a word where nulls signify wildcards. +pub const Pattern = Wordle.Word; + +/// A list of candidates: indexes of words in the dictionary. +pub const Candidates = std.ArrayList(u16); + +/// A single vote. +pub const Vote = struct { + /// The pattern to match this vote to. + pattern: Pattern, + + /// The number of votes placed on this pattern. + score: usize, +}; + +/// A collection of votes. +pub const Election = []const Vote; + +/// Calculates the total number of votes in an election. +pub fn electionVoteTotal(election: Election) usize { + var total: usize = 0; + for (election) |vote| total += vote.score; + return total; +} + +/// Tests if a pattern is satisfied by a given word. +pub fn patternSatisfied(word: Wordle.Word, pat: Pattern) bool { + // find mismatches + for (word, pat) |wordLetter, patLetter| { + if (patLetter != 0 and patLetter != wordLetter) { + return false; + } + } + + // if no mismatches were found, the pattern is satisfied + return true; +} + +/// Naively calculates how many votes a word scores in an election. +pub fn wordScore(word: Wordle.Word, election: Election) usize { + var total: usize = 0; + for (election) |vote| { + if (patternSatisfied(word, vote.pattern)) { + total += vote.score; + } + } + + return total; +} + +/// Elects candidates using brute-force scoring on the whole dictionary. +/// +/// The result must be deinitialized after use. +pub fn naiveElect(alloc: std.mem.Allocator, election: Election) !Candidates { + // track the maximum known score + var maxScore: usize = 0; + + // keep a list of all highest-scoring candidates + var candidates: Candidates = .empty; + + // evaluate all words in the dictionary + for (Dict.getDictionary(), 0..) |word, idx| { + // score this word + const score = wordScore(word, election); + + // if score is lower than current known score, skip + if (score < maxScore) continue; + + // if score exceeds known score, clear candidates and accommodate + if (score > maxScore) { + maxScore = score; + try candidates.resize(alloc, 0); + } + + // add this candidate's index + try candidates.append(alloc, @intCast(idx)); + } + + // return the complete list of candidates + return candidates; +} + +/// Formats a list of the words referred to by candidate indices. +fn formatCandidates(alloc: std.mem.Allocator, candidates: Candidates) !std.ArrayList(u8) { + // cache dictionary for repeated lookup + const dict = Dict.getDictionary(); + + // dynamically allocate formatted words + var formatted: std.ArrayList(u8) = .empty; + for (candidates.items, 0..) |wordIdx, idx| { + // prefix word with space unless it's the first one + if (idx != 0) { + try formatted.append(alloc, ' '); + } + + // append the word in the dictionary + try formatted.appendSlice(alloc, &dict[wordIdx]); + } + + return formatted; +} + +test "simple naive score" { + const election = [_]Vote{.{ + .pattern = Wordle.strToWord("abas\x00"), + .score = 1, + }}; + + try std.testing.expectEqual(wordScore(Wordle.strToWord("abash"), &election), 1); + try std.testing.expectEqual(wordScore(Wordle.strToWord("abask"), &election), 1); + try std.testing.expectEqual(wordScore(Wordle.strToWord("abear"), &election), 0); +} + +test "simple naive election" { + const election = [_]Vote{.{ + .pattern = Wordle.strToWord("abas\x00"), + .score = 1, + }}; + + const alloc = std.heap.smp_allocator; + var candidates = try naiveElect(alloc, &election); + defer candidates.deinit(alloc); + + var candidatesFmt = try formatCandidates(alloc, candidates); + defer candidatesFmt.deinit(alloc); + + try std.testing.expectEqualStrings(candidatesFmt.items, "abash abask"); +}