c++wordle-game

How to improve a checking algorithm for a copycat "WORDLE" game?


I'm working on a project to replicate 'TERMO', a Portuguese version of 'WORDLE'. One of the main challenges I'm facing is correctly outputting letters that are yellow (right letter but in the wrong position). I've currently used nested for loops to handle this, but I'm wondering if there's a more efficient solution that avoids overlapping for loops.

For example, given the following:

selectedWord = "MOLDO"
word = "OMODO"

I want to achieve this output:

O(yellow) M(yellow) O(normal) D(green) O(green)

However, my current implementation is producing:

O(yellow) M(yellow) O(yellow) D(green) O(green)

Is there a way to handle this without using nested loops, or is this the only approach?


The following code is my current approach:

string checkWord(string word, string selectedWord){
    // 0 for green
    // 1 for yellow
    // 2 for normal

    // string assuming "normal" for all letters
    string result = {'2','2','2','2','2'};
    
    

    for(int i = 0; i < 5; ++i){
        char letter = word[i];

        //check if it's green
        if(letter == selectedWord[i]){
            result[i] = '0';
            continue;
        }

        //check if it's yellow
        bool willBeGreen = false;
        for(int k = 0; k < 5; ++k){
            //Check if will be green
            if(word[i] == selectedWord[k] && word[k] == letter){
                willBeGreen = true;
                break;
            }
            if(letter == selectedWord[k] && k != i && result[k] != '0'){
                result[i] = '1';
                // break;
            }
        }
        if(willBeGreen){
            result[i] = '2';
        }
    }


    return result;
    
}

Solution

  • EDIT: Actually my previous version of this was wrong...

    You can't determine if a guessed letter is yellow without knowing which letters are going to be green; that is, you do need two passes. In the following I do the first pass via a range pipeline.

    In the second pass, you need to keep track of the hidden letters that will not ultimately be green in the output and that have not been "used" yet by a yellow letter. In the following I use a multiset of letters to keep track. Output is like YY-GG for yellow, yellow, gray, green, green.

    (this is C++23 because of the use of std::ranges::to<T> and zip but illustrates the idea)

    #include <iostream>
    #include <ranges>
    #include <vector>
    #include <string>
    #include <sstream>
    #include <set>
    
    namespace r = std::ranges;
    namespace rv = std::ranges::views;
    
    std::string score_wordle_guess(const std::string& guess, const std::string& hidden) {
    
        // "leftover_letters" are all the letters in the hidden word,
        // including duplications, minus those that will be green in 
        // the output...
    
        auto leftover_letters = rv::zip(guess, hidden) | rv::filter(
                [](auto pair) { return std::get<0>(pair) != std::get<1>(pair); }
            ) |  rv::transform(
                [](auto pair)->char {return std::get<1>(pair); }
            ) | r::to<std::multiset<char>>();
    
        std::stringstream scored_guess;
        for (auto [guess_char, hidden_char] : rv::zip(guess, hidden)) {
            char color = '-';
            if (guess_char == hidden_char) {
                color = 'G';
            } else if (leftover_letters.contains(guess_char)) {
                color = 'Y';
                leftover_letters.erase(leftover_letters.find(guess_char));
            }
            scored_guess << color;
        }
        return scored_guess.str();
    }
    
    void test(const std::string& guess, const std::string& hidden) {
        std::cout << "guess: " << guess << " + hidden: " << hidden
            << " => " << score_wordle_guess(guess, hidden) << "\n";
    }
    
    int main()
    {
        test("omodo", "moldo"); // => "YY-GG"
        test("fffff", "staff"); // => "---GG"
        test("fffof", "staff"); // => "Y---G"
    }