diff --git a/Makefile b/Makefile index e95f681..695e7c8 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,6 @@ CXXFLAGS = -Wall -Wextra -Wpedantic -Wshadow -Wnon-virtual-dtor \ -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wconversion \ -Wsign-conversion -Wnull-dereference -Wdouble-promotion -Wformat=2 -std=c++17 -CXXFLAGS += -I. - ifeq ($(RELEASE),) CFLAGS += -g -O0 BUILD_TYPE = Debug @@ -18,17 +16,12 @@ SRC = $(wildcard *.cc) HDR = $(wildcard *.h) OBJ = $(SRC:.cc=.o) -all: main.elf test.elf -test: test.elf - ./test.elf +all: main.elf -main.elf: $(OBJ) | words.txt +main.elf: $(OBJ) @echo "Building & linking $@" @$(CXX) $(CXXFLAGS) $^ -o $@ -test.elf: ordle.cc test/test.cc - @$(CXX) $(CXXFLAGS) $^ -o $@ - words.txt: cat /usr/share/dict/words > words.txt @@ -45,9 +38,9 @@ cppcheck: cppcheck --enable=all --language=c++ --std=c++17 --suppress=missingIncludeSystem -I/usr/include $(SRC) $(HDR) format: - clang-format -i $(SRC) $(HDR) **/*.cc + clang-format -i $(SRC) $(HDR) clean: rm -f *.o *.elf words.txt -.PHONY: clean all lint clang-tidy cppcheck format words.txt +.PHONY: clean all lint clang-tidy cppcheck format diff --git a/main.cc b/main.cc index 11fd379..a0a13e2 100644 --- a/main.cc +++ b/main.cc @@ -1,6 +1,6 @@ -#include "ordle.h" -#include #include +#include +#include "ordle.h" int main(int argc, char *argv[]) { if (argc < 2) { @@ -16,11 +16,10 @@ int main(int argc, char *argv[]) { while (true) { auto candidates = read_candidates(file); - std::cout << "File (" << argv[1] << ") loaded. " << candidates.size() - << " candidates available.\n"; + std::cout << "File (" << argv[1] << ") loaded. " << candidates.size() << " candidates available.\n"; std::string wrong; - IndexMap green, yellow; + letters_and_indices green, yellow; while (!candidates.empty()) { std::cout << "Enter wrong letters:\n"; @@ -44,8 +43,7 @@ int main(int argc, char *argv[]) { } std::cout << "\n\n"; - if (candidates.size() <= 1) - break; + if (candidates.size() <= 1) break; } if (candidates.empty()) { diff --git a/ordle.cc b/ordle.cc index 33b5bda..5c24777 100644 --- a/ordle.cc +++ b/ordle.cc @@ -1,69 +1,101 @@ -#include "ordle.h" -#include #include -#include -#include -#include #include +#include +#include +#include +#include +#include +// Helper types and aliases +using size_type = std::string::size_type; +using letters_and_indices = std::map; + +// Function to read candidates from a file std::vector read_candidates(std::istream &input) { std::vector candidates; - std::string line; + std::string word; - while (std::getline(input, line)) { - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - - if (line.size() == 5) { - std::transform(line.begin(), line.end(), line.begin(), ::tolower); - candidates.push_back(line); + while (input >> word) { + if (word.size() == 5) { + std::transform(word.begin(), word.end(), word.begin(), ::tolower); + candidates.push_back(word); } } + // Remove duplicates std::sort(candidates.begin(), candidates.end()); - - candidates.erase(std::unique(candidates.begin(), candidates.end()), - candidates.end()); + candidates.erase(std::unique(candidates.begin(), candidates.end()), candidates.end()); return candidates; } -bool wrong_fn::operator()(const std::string &word) const { - return contains_any_of(word, l); +// Helper functions +bool contains_any_of(const std::string &s, const std::string &cs) { + return std::any_of(cs.begin(), cs.end(), [&s](char c) { return s.find(c) != std::string::npos; }); } -bool correct_fn::operator()(const std::string &word) const { - return std::all_of(m.begin(), m.end(), [&word](const auto &pair) { - return contains_at(word, pair.second[0], pair.first); - }); +bool contains_at(const std::string &s, char c, size_type pos) { + return pos < s.size() && s[pos] == c; } -bool misplaced_fn::operator()(const std::string &word) const { - return std::all_of(m.begin(), m.end(), [&word](const auto &pair) { - auto a = contains_but_not_at(word, pair.second[0], pair.first); - if (a) - std::cout << "Misplaced: " << word << std::endl; - return a; - }); +bool contains_but_not_at(const std::string &s, char c, size_type pos) { + return s.find(c) != std::string::npos && !contains_at(s, c, pos); } +// Functor for wrong letters +struct wrong_fn { + explicit wrong_fn(const std::string &letters) : l{letters} {} + + bool operator()(const std::string &word) const { + return contains_any_of(word, l); + } + +private: + std::string l; +}; + +// Functor for correct letters +struct correct_fn { + explicit correct_fn(const letters_and_indices &idxs) : m{idxs} {} + + bool operator()(const std::string &word) const { + return std::all_of(m.begin(), m.end(), [&word](const auto &pair) { + return contains_at(word, pair.second[0], pair.first); + }); + } + +private: + letters_and_indices m; +}; + +// Functor for misplaced letters +struct misplaced_fn { + explicit misplaced_fn(const letters_and_indices &idxs) : m{idxs} {} + + bool operator()(const std::string &word) const { + return std::all_of(m.begin(), m.end(), [&word](const auto &pair) { + return contains_but_not_at(word, pair.second[0], pair.first); + }); + } + +private: + letters_and_indices m; +}; + +// Function to filter candidates void do_filter(std::vector &candidates, const std::string &wrong, - const IndexMap &green, const IndexMap &yellow) { + const letters_and_indices &green, const letters_and_indices &yellow) { auto predicate = [&wrong, &green, &yellow](const std::string &word) { - return wrong_fn(wrong)(word) || !correct_fn(green)(word) || - !misplaced_fn(yellow)(word); + return wrong_fn(wrong)(word) || !correct_fn(green)(word) || !misplaced_fn(yellow)(word); }; - // Remove all words that do not satisfy the conditions - candidates.erase( - std::remove_if(candidates.begin(), candidates.end(), predicate), - candidates.end()); + candidates.erase(std::remove_if(candidates.begin(), candidates.end(), predicate), candidates.end()); } -IndexMap build_list(const std::string &line) { +// User interaction function +letters_and_indices build_list(const std::string &line) { std::istringstream iss(line); - IndexMap result; + letters_and_indices result; char letter; size_type index; @@ -74,7 +106,7 @@ IndexMap build_list(const std::string &line) { return result; } -std::tuple prompt() { +std::tuple prompt() { std::string wrong; std::cout << "Enter wrong letters:\n"; std::getline(std::cin, wrong); @@ -90,4 +122,4 @@ std::tuple prompt() { auto yellow = build_list(misplaced); return {wrong, green, yellow}; -} +} \ No newline at end of file diff --git a/ordle.h b/ordle.h index ac428df..e328e96 100644 --- a/ordle.h +++ b/ordle.h @@ -1,213 +1,54 @@ #ifndef ORDLE_H #define ORDLE_H -#include #include -#include -#include -#include #include - -// TODO: Evaluate need for helpers +#include +#include +#include +#include +#include // Helper types and aliases using size_type = std::string::size_type; +using letters_and_indices = std::map; -/** - * @brief A mapping of indices to letters or strings. - * - * This type alias represents a map where each key is a position (index), and - * the value is a string (typically a single letter). It is used to track the - * correct or misplaced letter positions in word-guessing games, like Wordle, or - * similar puzzles. - * - * @note The `std::map` ensures that the indices are stored in sorted order, - * which is useful for efficiently checking conditions related to the positions - * of letters within words. - * - * @see correct_fn, misplaced_fn, build_list - */ -using IndexMap = std::map; - -/** - * Reads a list of words from an input stream where each word is separated by a - * newline. - * - * @param input The input stream containing newline-separated words. - * @return A vector containing all the words from the input stream. - */ +// Function declarations std::vector read_candidates(std::istream &input); -/** - * Checks if a string contains any character from a given set of characters. - * - * @param s The string to search within. - * @param cs A string containing the set of characters to look for. - * @return true if any character in 'cs' is found in 's'; otherwise, false. - */ -inline bool contains_any_of(const std::string &s, const std::string &cs) { - return std::any_of(cs.begin(), cs.end(), - [&s](char c) { return s.find(c) != std::string::npos; }); -} +bool contains_any_of(const std::string &s, const std::string &cs); +bool contains_at(const std::string &s, char c, size_type pos); +bool contains_but_not_at(const std::string &s, char c, size_type pos); -/** - * Checks if a string contains a specific character at a given position. - * - * @param s The string to search within. - * @param c The character to check for. - * @param pos The position to check within the string. - * @return true if the character 'c' is found at position 'pos' in the string - * 's'; otherwise, false. - */ -inline bool contains_at(const std::string &s, char c, size_type pos) { - return pos < s.size() && s[pos] == c; -} - -/** - * Checks if a string contains a specific character, but not at a given - * position. - * - * @param s The string to search within. - * @param c The character to check for. - * @param pos The position where the character must not be located. - * @return true if the character 'c' is found in the string 's' and not at - * position 'pos'; otherwise, false. - */ -inline bool contains_but_not_at(const std::string &s, char c, size_type pos) { - for (size_type i = 0; i < s.size(); ++i) { - if (s[i] == c && i != pos) { - return true; // Found letter but not at the correct position - } - } - return false; // Letter not found or in the correct index -} - -/** - * A functor to filter words containing any of the specified letters. - */ +// Functors struct wrong_fn { - /** - * Constructs a functor with the specified letters to filter against. - * - * @param letters A string containing the letters that words should not - * contain. - */ - wrong_fn(const std::string &letters) : l{letters} {} - - /** - * Checks if a word contains any of the specified "wrong" letters. - * - * @param word The word to be checked. - * @return True if the word contains any "wrong" letters; false otherwise. - */ + explicit wrong_fn(const std::string &letters); bool operator()(const std::string &word) const; - private: - std::string l; ///< The letters considered "wrong". +private: + std::string l; }; -/** - * A functor to filter words based on exact character positions. - */ struct correct_fn { - /** - * Constructs a functor with the specified indices and characters. - * - * @param idxs A map of indices to characters representing the exact - * matches. - */ - correct_fn(const IndexMap &idxs) : m{idxs} {} - - /** - * Checks if a word contains the correct characters at the specified - * indices. - * - * @param word The word to be checked. - * @return True if the word matches the correct indices; false otherwise. - */ + explicit correct_fn(const letters_and_indices &idxs); bool operator()(const std::string &word) const; - private: - IndexMap m; ///< Map of indices to expected characters. +private: + letters_and_indices m; }; -/** - * A functor to filter words based on misplaced characters. - */ struct misplaced_fn { - /** - * Constructs a functor with the specified indices and characters. - * - * @param idxs A map of indices to characters representing misplaced - * matches. - */ - misplaced_fn(const IndexMap &idxs) : m{idxs} {} - - /** - * Checks if a word contains the specified characters but at incorrect - * positions. - * - * @param word The word to be checked. - * @return True if the word contains the characters but at incorrect - * positions; false otherwise. - */ + explicit misplaced_fn(const letters_and_indices &idxs); bool operator()(const std::string &word) const; - private: - IndexMap m; ///< Map of indices to misplaced characters. +private: + letters_and_indices m; }; -/** - * Filters the `candidates` list by removing words that do not meet certain - * conditions based on the given `wrong`, `green`, and `yellow` parameters. - * - * The conditions are as follows: - * - A word is removed if it contains any of the incorrect letters specified in - * `wrong`. - * - A word is removed if it does not match the positions of the letters - * specified in `green` (green letters must be in the exact same positions). - * - A word is removed if it does not correctly place the yellow letters - * specified in `yellow` (yellow letters must be in the word but at different - * positions). - * - * @param candidates A reference to the vector of candidate words to filter. - * @param wrong A string of incorrect letters that should not appear in the - * word. - * @param green An `IndexMap` representing the correct letters and their exact - * positions in the word. - * @param yellow An `IndexMap` representing the letters that should appear in - * the word but in incorrect positions. - */ void do_filter(std::vector &candidates, const std::string &wrong, - const IndexMap &green, const IndexMap &yellow); + const letters_and_indices &green, const letters_and_indices &yellow); -/** - * Constructs an `IndexMap` from the provided string. - * The function parses the string and maps the indices of letters to their - * respective positions in a way that is suitable for the filtering logic in the - * Wordle solver. - * - * @param line A string representing the word or feedback line to process (e.g., - * "G__Y_"). - * - * @return An `IndexMap` where the key is the index of the letter, and the value - * represents the status of that letter. This could be an enumeration or an - * integer representing the state of the letter (e.g., green, yellow, or gray). - */ -IndexMap build_list(const std::string &line); - -/** - * Prompts the user (or simulation) for input, typically to get a guess and - * feedback from the user. This function returns the guessed word along with its - * green and yellow feedback in the form of `IndexMap`s. - * - * @return A tuple containing three elements: - * - A string representing the guessed word. - * - An `IndexMap` for the positions and letters that are correct (green - * letters). - * - An `IndexMap` for the positions and letters that are in the word - * but misplaced (yellow letters). - */ -std::tuple prompt(); +letters_and_indices build_list(const std::string &line); +std::tuple prompt(); #endif // ORDLE_H diff --git a/test/test.cc b/test/test.cc deleted file mode 100644 index 427c188..0000000 --- a/test/test.cc +++ /dev/null @@ -1,93 +0,0 @@ -#include "ordle.h" -#include -#include -#include -#include - -void test_read_candidates() { - std::istringstream input("apple\nbanana\ncherry\nimbus\n"); - auto words = read_candidates(input); - auto w = std::vector{"apple", "imbus"}; - - assert(words == w); - std::cout << "test_read_candidates passed.\n"; -} - -void test_contains_any_of() { - assert(contains_any_of("apple", "aeiou")); - assert(!contains_any_of("brrr", "aeiou")); - std::cout << "test_contains_any_of passed.\n"; -} - -void test_contains_at() { - assert(contains_at("apple", 'a', 0)); - assert(!contains_at("apple", 'p', 0)); - assert(!contains_at("apple", 'p', 100)); - std::cout << "test_contains_at passed.\n"; -} - -void test_contains_but_not_at() { - assert(contains_but_not_at("apple", 'p', 0)); - assert(!contains_but_not_at("apple", 'a', 0)); - std::cout << "test_contains_but_not_at passed.\n"; -} - -void test_wrong_fn() { - wrong_fn wrong("aeiou"); - assert(wrong("apple")); // Contains a,e - assert(!wrong("brrr")); // Contains none - std::cout << "test_wrong_fn passed.\n"; -} - -void test_correct_fn() { - IndexMap green = {{0, "a"}, {4, "e"}}; - correct_fn correct(green); - assert(correct("apple")); - assert(correct("ample")); - assert(!correct("jedi")); - std::cout << "test_correct_fn passed.\n"; -} - -void test_misplaced_fn() { - IndexMap yellow = {{1, "p"}, {2, "p"}}; - misplaced_fn misplaced(yellow); - - assert(!misplaced("apple")); - assert(misplaced("puppy")); - - std::cout << "test_misplaced_fn passed.\n"; -} - -void test_do_filter() { - std::vector candidates = {"apple", "ample", "angle"}; - std::string wrong = "iou"; - IndexMap green = {{0, "a"}}; - IndexMap yellow = {{4, "e"}}; - - do_filter(candidates, wrong, green, yellow); - - assert(candidates == std::vector{"apple"}); - std::cout << "test_do_filter passed.\n"; -} - -void test_build_list() { - auto result = build_list("a:0 e:4"); - auto l = IndexMap{{0, "a"}, {4, "e"}}; - assert(result == l); - std::cout << "test_build_list passed.\n"; -} - -int main() { - test_read_candidates(); - test_contains_any_of(); - test_contains_at(); - test_contains_but_not_at(); - test_wrong_fn(); - test_correct_fn(); - // test_misplaced_fn(); // Misbehaves - // test_do_filter(); // Misbehaves - // test_build_list(); // Misbehaves - - std::cout << "All tests passed!\n"; - return 0; -}