2
\$\begingroup\$

the game is quite simple, you have to constantly guess the letters of a hidden word, but has a limited number of wrong attempts. The word you are looking for should be a random word from Wikipedia. I would like to get a review from you.

I used libcURL and the cURLpp wrapper.

Compilation (with Cygwin): g++ -L/usr/local/lib Hangman.cpp -o hm.exe -lcurl -lcurlpp

Hangman.cpp:

#include <iostream>
#include <vector>
#include <string>
#include <regex>
#include <curlpp/Easy.hpp>
#include <curlpp/cURLpp.hpp>
#include <curlpp/Options.hpp>

using namespace std;

string getRandomWordFromWikipedia()
{
    curlpp::Cleanup myCleanup;
    curlpp::Easy handle;
    handle.setOpt(curlpp::options::Url(string("https://en.wikipedia.org/wiki/special:random")));
    handle.setOpt(curlpp::options::FollowLocation(true));
    ostringstream os;
    os << handle;
    string seq = os.str();
    regex rgx1("<h1.+id=\"firstHeading\"[^>]*>(.+)</h1>");
    regex rgx2("<[^>]+>(.+)</[^>]+>");
    smatch sm1;
    if (regex_search(seq, sm1, rgx1))
    {
        string seq2 = sm1[1];
        while (seq2.find("<") != string::npos)
        {
            smatch sm2;
            if (regex_search(seq2, sm2, rgx2))
            {
                seq2 = sm2[1];
            }
            else
            {
                return "";
            }
        }
        return seq2;
    }
    return "";
}

struct ToFind
{
    string w1;
    string w2;
    int tries = 0;
    int right = 0;
    int health = 100;
    int hintsLeft = 2;
};

void initToFind(struct ToFind *stf)
{
    string w1 = getRandomWordFromWikipedia();
    string w2;
    for (int i = 0; i < w1.size(); i++)
    {
        if (w1[i] == ' ')
        {
            w2 += "  ";
        }
        else
        {
            w2 += "_ ";
        }
    }
    stf->w1 = w1;
    stf->w2 = w2;
}

int findInToFind(struct ToFind *stf, char c)
{
    int f = 0;
    for (int i = 0; i < stf->w1.size(); i++)
    {
        if (stf->w1[i] == c && stf->w2[i * 2] == '_')
        {
            f++;
            stf->right++;
            stf->w2[i * 2] = c;
        }
    }
    stf->tries++;
    if (f > 0)
    {
        stf->right++;
    }
    else
    {
        stf->health -= 10;
    }
    return f;
}

void printStatistics(struct ToFind *stf)
{
    static int round = 1;
    cout << "Round:      " << round << endl;
    cout << "Tries:      " << stf->tries << endl;
    cout << "Rights:     " << stf->right << endl;
    cout << "Health:     " << stf->health << " from 100" << endl;
    cout << "Hints left: " << stf->hintsLeft << endl;
    round++;
}

int main(int argc, char const *argv[])
{
    struct ToFind stf;
    initToFind(&stf);
    if (stf.w1.empty())
    {
        return EXIT_FAILURE;
    }
    cout << "The game begins!" << endl;
    cout << "The word you are looking for has a length of " << stf.w1.size() << endl;
    while (stf.w2.find("_") != string::npos && stf.health > 0)
    {
        printStatistics(&stf);
        cout << stf.w2 << endl;
        cout << "Which letter next?" << endl;
        char c;
        cin >> c;
        int f = findInToFind(&stf, c);
        if (f > 0)
        {
            cout << "Great, the letter " << c << " appeared " << f << " times!" << endl;
        }
        else
        {
            cout << "Unfortunately you weren't lucky this time ..." << endl;
        }
    }
    cout << "The game is over ..." << endl;
    if (stf.health > 0)
    {
        cout << "Great, you found the word " << stf.w1 << endl;
    }
    else
    {
        cout << "Unfortunately you didn't find the word " << stf.w1 << " and you're dead." << endl;
    }
    printStatistics(&stf);
    cout << "Any char to continue ..." << endl;
    char c;
    cin >> c;
    return EXIT_SUCCESS;
}
\$\endgroup\$
1
  • \$\begingroup\$ I see in retrospect a logical error in findInToFind. right is incremented multiple times. It should be incremented only once and should indicate the percentage of correct attempts. \$\endgroup\$ Commented Oct 15, 2023 at 13:38

1 Answer 1

5
\$\begingroup\$

General Observations

An interesting new twist on an old game! Using Wikipedia as a source gives a lot of possible words.

There is a lot of good ideas here, but there can be some improvement. An example of possible improvements is to make the struct ToFind data structure more object oriented. More on this later.

The code already follows some good best practices such as always using braces ({ and }) around code blocks.

Using raw pointers (struct ToFind* stf) is generally considered a bad practice in C++, there are better ways to do what the code is doing. The use of the raw pointer makes this code look more like C rather than C++.

For performance reasons, it is better to end output lines with "\n" rather than using std::endl. std::endl flushes the output buffer which generally isn't necessary and could be a system call. Using "\n" rather than std::endl also seems to simplify the code a little bit:

int main(int argc, char const* argv[])
{
    struct ToFind stf;
    initToFind(&stf);
    if (!stf.hasWord())
    {
        return EXIT_FAILURE;
    }
    std::cout << "The game begins!\n";
    std::cout << "The word you are looking for has a length of " << stf.w1.size() << "\n";
    while (stf.w2.find("_") != string::npos && stf.health > 0)
    {
        stf.printStatistics();
        std::cout << stf.w2 << "\n";
        std::cout << "Which letter next?\n";
        char c;
        cin >> c;
        int f = findInToFind(&stf, c);
        if (f > 0)
        {
            std::cout << "Great, the letter " << c << " appeared " << f << " times!\n";
        }
        else
        {
            std::cout << "Unfortunately you weren't lucky this time ...\n";
        }
    }
    std::cout << "The game is over ..." << "\n";
    if (stf.stillAlive())
    {
        std::cout << "Great, you found the word " << stf.w1 << "\n";
    }
    else
    {
        std::cout << "Unfortunately you didn't find the word " << stf.w1 << " and you're dead. \n";
    }
    stf.printStatistics();
    std::cout << "Any char to continue ...\n";
    char c;
    cin >> c;
    return EXIT_SUCCESS;
}

Please note that main() is doing too much, it should be one or two more functions.

Avoid using namespace std;

If you are coding professionally you probably should get out of the habit of using the using namespace std; statement. The code will more clearly define where cout and other identifiers are coming from (std::cin, std::cout). As you start using namespaces in your code it is better to identify where each function comes from because there may be function name collisions from different namespaces. The identifiercout you may override within your own classes, and you may override the operator << in your own classes as well. This stack overflow question discusses this in more detail.

Object Oriented Code

The code could be simplified if the struct ToFind contained a constructor and some methods (member functions). Personally I would use a class rather than a struct, so that the member variables could be private, but to give you some idea look at the following code and also the modified example of main() above. Please note that making this object oriented removes the need to pass a pointer around.

struct ToFind
{
    std::string w1;
    std::string w2;
    int tries;
    int right;
    int health;
    int hintsLeft;

    ToFind()
        : tries(0), right(0), health(100), hintsLeft(2)
    {

    }

    ToFind(std::string word)
        : w1(word), tries(0), right(0), health(100), hintsLeft(2)
    {
        for (int i = 0; i < w1.size(); i++)
        {
            if (w1[i] == ' ')
            {
                w2 += "  ";
            }
            else
            {
                w2 += "_ ";
            }
        }
    }

    void setWord(std::string word1)
    {
        w1 = word1;
        for (int i = 0; i < w1.size(); i++)
        {
            if (w1[i] == ' ')
            {
                w2 += "  ";
            }
            else
            {
                w2 += "_ ";
            }
        }
    }

    void printStatistics()
    {
        static int round = 1;
        std::cout << "Round:      " << round << "\n";
        std::cout << "Tries:      " << tries << "\n";
        std::cout << "Rights:     " << right << "\n";
        std::cout << "Health:     " << health << " from 100" << "\n";
        std::cout << "Hints left: " << hintsLeft << "\n";
        round++;
    }

    bool hasWord()
    {
        return !w1.empty();
    }

    bool stillAlive()
    {
        return health > 0;
    }

    int findInToFind(char c)
    {
        int f = 0;
        for (int i = 0; i < w1.size(); i++)
        {
            if (w1[i] == c && w2[i * 2] == '_')
            {
                f++;
                right++;
                w2[i * 2] = c;
            }
        }

        tries++;
        
        if (f > 0)
        {
            right++;
        }
        else
        {
            health -= 10;
        }

        return f;
    }
};
\$\endgroup\$
1
  • \$\begingroup\$ Thanks! It looks much more object-oriented than the original code. \$\endgroup\$ Commented Oct 15, 2023 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.