Skip to content

Instantly share code, notes, and snippets.

@willkill07
Created October 31, 2025 23:39
Show Gist options
  • Select an option

  • Save willkill07/0402675e3deb30f9356a5bf4b411ea90 to your computer and use it in GitHub Desktop.

Select an option

Save willkill07/0402675e3deb30f9356a5bf4b411ea90 to your computer and use it in GitHub Desktop.
My own Wordle Guess Program inspired by Dr. Chad Hogg -- Requires C++23
#include <algorithm>
#include <fstream>
#include <iostream>
#include <ranges>
#include <random>
#include <string>
#include <string_view>
#include <vector>
constexpr int WORD_SIZE{5};
constexpr int NUM_LETTERS{26};
constexpr std::string_view DONE{"GGGGG"};
/// \brief Read all words from the file
/// \return the string of all words
///
/// The algorithm reads all words from the file "words.txt".
/// The words are returned as a string.
[[nodiscard]] auto read_all_words() -> std::string
{
std::ifstream file("words.txt");
return std::string{std::istreambuf_iterator<char>(file), {}};
}
/// \brief Split words into a list of words of a given length
/// \param words the string of words to split
/// \param delimiter the delimiter to split on
/// \param length the length of the words to split
/// \return the list of words
///
/// The algorithm splits the words into a list of words of a given length.
/// The words are split on the delimiter and the words are filtered to only include words of the given length.
/// The words are filtered to only include words that contain only lowercase letters.
[[nodiscard]] auto split_words(std::string_view words, char delimiter, unsigned length) -> std::vector<std::string_view>
{
return words
| std::views::split(delimiter)
| std::views::filter([length](auto word) { return word.size() == length; })
| std::views::transform([](auto word) { return std::string_view(word.begin(), word.end()); })
| std::views::filter([](auto word) { return std::ranges::all_of(word, [](char c) { return 'a' <= c && c <= 'z'; }); })
| std::ranges::to<std::vector<std::string_view>>();
}
/// \brief Filter words based on the guess and pattern
/// \param guess the guess word
/// \param words the list of words to filter
/// \param pattern the pattern of the guess
/// \return the filtered list of words
///
/// The pattern is a string of 'G', 'Y', and 'W' characters, where:
/// - 'G' means the letter is in the word and in the correct position
/// - 'Y' means the letter is in the word but in the wrong position
/// - 'W' means the letter is not in the word
///
/// The algorithm is a simple brute force algorithm that checks all words against the guess and pattern.
[[nodiscard]] auto filter_words(std::string_view guess, std::vector<std::string_view> words, std::string_view pattern) -> std::vector<std::string_view>
{
std::array<int, NUM_LETTERS> min_letter_counts;
min_letter_counts.fill(0);
for (auto [i, gch]: std::views::enumerate(guess)) {
if (pattern[i] == 'G' || pattern[i] == 'Y') {
++min_letter_counts[gch - 'a'];
}
}
std::vector<std::string_view> filtered;
filtered.reserve(words.size());
for (const auto& word : words) {
// Count all letter occurrences in candidate word using array
std::array<int, NUM_LETTERS> word_letter_counts;
word_letter_counts.fill(0);
for (auto ch: word) {
++word_letter_counts[ch - 'a'];
}
// Validate all constraints using zip and all_of (with early exit)
bool const valid = std::ranges::all_of(
std::views::zip(guess, word, pattern),
[&](const auto& triple) {
if (const auto& [gch, wch, pch] = triple; pch == 'G') {
// Green: exact position match required
return wch == gch;
} else if (pch == 'Y') {
// Yellow: letter exists elsewhere, but not at this position
return (wch != gch) && (word_letter_counts[gch - 'a'] > 0);
} else if (pch == 'W') {
// White: letter count cannot exceed minimum required (G+Y)
return (wch != gch) && (word_letter_counts[gch - 'a'] == min_letter_counts[gch - 'a']);
}
return true; // Unknown pattern character, skip validation
}
) && std::ranges::all_of(
std::views::zip(word_letter_counts, min_letter_counts),
[](const auto& counts) {
const auto& [word_count, min_count] = counts;
return word_count >= min_count;
}
);
if (valid) {
filtered.push_back(word);
}
}
return filtered;
}
/// \brief Guess the best word based on the letter frequency at each position
/// \param words the list of words to guess from
/// \return the best word
///
/// The algorithm counts the frequency of each letter at each position in the words.
/// The word with the highest score is the word with the highest sum of letter frequencies at each position.
[[nodiscard]] auto guess_best_word(std::vector<std::string_view> words) -> std::string_view
{
std::array<std::array<int, NUM_LETTERS>, WORD_SIZE> letter_pos_counts;
for (auto& row: letter_pos_counts) {
row.fill(0);
}
for (const auto& word : words) {
for (auto [i, ch]: std::views::enumerate(word)) {
++letter_pos_counts[i][ch - 'a'];
}
}
auto score = [&letter_pos_counts](std::string_view word) -> int {
return std::ranges::fold_left(
std::views::enumerate(word),
0,
[&letter_pos_counts](int acc, const auto& pair) {
const auto& [i, ch] = pair;
return acc + letter_pos_counts[i][ch - 'a'];
}
);
};
return *std::ranges::max_element(words, std::ranges::less{}, score);
}
auto main() -> int {
std::minstd_rand rng{std::random_device{}()};
auto words = read_all_words();
auto word_list = split_words(words, '\n', WORD_SIZE);
std::string pattern;
while (true) {
std::string_view guess = guess_best_word(word_list);
std::cout << "You should guess: " << guess << std::endl;
std::cout << "Enter a pattern: ";
std::cin >> pattern;
if (pattern == DONE) {
std::cout << "You win!" << std::endl;
break;
}
word_list = filter_words(guess, word_list, pattern);
// Shuffle the word list to potentially randomize the next guess
std::ranges::shuffle(word_list, rng);
std::cout << word_list.size() << " words remaining" << std::endl;
if (word_list.empty()) {
std::cout << "Did you make a mistake?" << std::endl;
break;
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment