Another Guess My World

Hello. I continue to increase my understanding of the OOP principle using C++. I made another game - Guess My Word. Not a big revolution - just another exercise for me. Please take a look at these parts and let me know if something should be improved. The main process works this way :

It reads a file with a huge amount of words - a dictionary,
It generates a randomized number,
It selects a word according to the number,
It shuffles the word,
Waits an entry, checking if it corresponds to an anagram and a valid word...

PS : for the dictionary, I use this one which is available on GitHub at this address - https://github.com/dwyl/english-words (words-alpha.txt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>
#include <fstream>
#include <vector>
#include <random>

class guessMyWord {

public:
    guessMyWord(std::string& myBook, int length);
    bool bAnagram(std::string lhs, std::string rhs);
    void showSecretWords(std::string& guess, bool hiddenWord);
    // all words from the dictionary
    std::vector<std::string> words;
    std::pair<std::string, std::string> secretWord; // !!!

private:
    int randomNumber(const int min_, const int max_);
    void selectWord();
    std::string shuffleWord(std::string& word);
};
// constructor
guessMyWord::guessMyWord(std::string& myBook, int length)
{
    std::string word;
    std::ifstream input_file(myBook);

    if (!input_file.is_open())
    {
        std::cout << "Could not open the file " << myBook << std::endl;
        exit(EXIT_FAILURE);
    }

    while (input_file >> word)
        if (word.size() == length)
            words.push_back(word);

    input_file.close();
    // selects one word in the main vector
    selectWord();
}

int guessMyWord::randomNumber(const int min_, const int max_)
{
    static std::random_device rd;
    static std::mt19937 rng{ rd() };
    static std::uniform_int_distribution<int> uid(min_, max_);

    return uid(rng);
}

void guessMyWord::selectWord()
{
    size_t rn = randomNumber(1, words.size() - 1);
    secretWord.first = words[rn];
    secretWord.second = shuffleWord(words[rn]); // debug mode
//  std::cout << "Hidden word : " << secretWord.first << " " << secretWord.second << std::endl;
}

std::string guessMyWord::shuffleWord(std::string& word)
{
again:
    std::string tmp = word;
    std::random_shuffle(tmp.begin(), tmp.end());
    // check if the shuffled word is not a valid answer 
    if (std::find(words.begin(), words.end(), tmp) != words.end())
        goto again; // I know I know...

    return tmp;
}

bool guessMyWord::bAnagram(std::string lhs, std::string rhs) 
{
    std::sort(lhs.begin(), lhs.end());
    std::sort(rhs.begin(), rhs.end());

    return lhs == rhs;
}

void guessMyWord::showSecretWords(std::string& guess, bool showHiddenWord)
{
    std::cout << std::endl;
    std::cout << "Anagrams available for this word (alternative good answers) :" << std::endl;
    int occ = 0;

    if (showHiddenWord)
    {
        std::cout << secretWord.first << std::endl;
        occ++;
    }

    for (int i = 0; i < words.size(); i++)
    {
        if (bAnagram(secretWord.first, words[i]) && guess != words[i])
        {
            std::cout << words[i] << std::endl;
            occ++;
        }
    }
    // no occurrence for anagrams
    if ((occ == 0) ? std::cout << "[nothing]" << std::endl << std::endl
                   : std::cout << std::endl);
}

int main()
{
    std::string dico = "dictionary.txt";
    int letters = 4;

    while (true)
    {
        int tries = NULL;
        std::string myGuess;
        // select a word
        guessMyWord gmw(dico, letters);

        std::cout << "~~ Guess My Word ~~" << std::endl;
        std::cout << "Words in the dictionary with " << letters << " letters : " << gmw.words.size() << std::endl;
        std::cout << std::endl;

        do
        {
            std::cout << "What is your suggestion for the letters [" << gmw.secretWord.second << "] ? " << std::endl;
            std::cin >> myGuess;
            // to lower case
            std::transform(myGuess.begin(), myGuess.end(), myGuess.begin(), [](unsigned char c) { return std::tolower(c); });
            tries++;

            if (myGuess == "*cheat")
            {
                gmw.showSecretWords(gmw.secretWord.first, true);
                return EXIT_SUCCESS;
            }
            // boring game?
            if (myGuess == "exit")
                return EXIT_SUCCESS;
            // not the right word length
            if (myGuess.length() != gmw.secretWord.first.length())
                std::cout << "Not the same length" << std::endl;
            else if (!gmw.bAnagram(myGuess, gmw.secretWord.first)) // one or more letters are not in the secret word
                std::cout << "Not the right letters" << std::endl; // the letters are good, the length too, but this word is weird
            else if (gmw.bAnagram(myGuess, gmw.secretWord.first) && std::find(gmw.words.begin(), gmw.words.end(), myGuess) == gmw.words.end())
                std::cout << "It seems that this word does not exist" << std::endl;
        } while (!gmw.bAnagram(myGuess, gmw.secretWord.first) || std::find(gmw.words.begin(), gmw.words.end(), myGuess) == gmw.words.end());
        // check if two strings are anagrams and if the guessed word exists in the dictionary - or loop again
        std::cout << "Correct! You got it in " << tries << " guesses!" << std::endl;
        // display alternatives - except the guessed word
        gmw.showSecretWords(myGuess, false);
        // increase difficulty
        letters++;
        // max difficulty
        if (letters > 14)letters = 14;
    }

    return EXIT_SUCCESS;
}



~~ Guess My Word ~~
Words in the dictionary : 15920

What is your suggestion for the letters [aciht] ?
hacit
It seems that this word does not exist
What is your suggestion for the letters [aciht] ?
hicat
It seems that this word does not exist
What is your suggestion for the letters [aciht] ?
tachi
Correct! You got it in 3 guesses!

Anagrams available for this word (alternative good answers) :
aitch
chait
chati
chita
taich
tchai
Last edited on
Line 66: goto is a no-no. Granted it's only an 11 line function, but it's still a very bad habit. A do/while loop would work perfectly well here.
A quick first look. shuffleWord(). No!! Don't use a goto. Use a do loop instead. Also random_shuffle() was removed in C++17. Use shuffle instead.

1
2
3
4
5
6
7
8
9
std::string guessMyWord::shuffleWord(std::string& word) {
	std::string tmp { word };

	do {
		std::shuffle(tmp.begin(), tmp.end(), rng);
	} while (word == tmp);

	return tmp;
}


and move rd/rng into the private variables as non-static.

The constructor can be shortened:

1
2
3
4
5
6
7
8
9
10
11
12
13
guessMyWord::guessMyWord(std::string& myBook, int length) {
	if (std::ifstream input_file { myBook }) {
		for (std::string word; input_file >> word; )
			if (word.size() == length)
				words.push_back(std::move(word));

		// selects one word in the main vector
		selectWord();
	} else {
		std::cout << "Could not open the file " << myBook << '\n';
		exit(EXIT_FAILURE);
	}
}

Thank you for your comment. I really appreciate it. We were talking about this (bad) possibility recently - and there are diverging opinions. Overall I agree with you - we HAVE to avoid them. However in this case, it seemed to me ... elegant and useful. Take a look at this post. Again thank you ++

https://cplusplus.com/forum/beginner/284650/
However in this case, it seemed to me ... elegant and useful


No and no. Use say a do loop as per my suggestion above.
However in this case, it seemed to me ... elegant and useful.

If you ever think about becoming a professional programmer don't include this as part of your portfolio.

Or do include it, make it exhibit #1, and then wonder why no company will hire you.

There are legitimate uses for goto, very few cases, yet this is not one of them.
L100 - The if() is not needed.

L111 - No. Use 0 instead of NULL or just {}.

L125 - the return type from tolower() needs to be cast to a char. By default it is an int.

Also, the return type of .size() is size_t - not int. Hence you'll get some compiler warnings about "conversion from 'size_t' to 'const int', possible loss of data". In this situation these wont cause any issue, but you might want to replace uses of int with size_t (or auto) when used from a size_t value.

Last edited on
There are a number of languages that have a do/until loop which I will sometimes steal.
 
#define until(cond) while(!(cond)) 


As I mentioned in a previous post, any programmer that worked for me that used goto was a candidate for termination.

@AbstractionAnon,

Nice language hack, but this is C++ after all. Wouldn't having a typedef or using statement be more appropriate instead of a define?

Askin' "for a friend." :Þ

Just spitballin', I don't have any idea/example without some deep thinking how to C++-ify that #define.

Maybe a fershlugginer template....
You could use a function template, something like
1
2
3
4
5
template <typename Fn, typename Pred>
  void until(Fn fn, Pred p)
  {
    while (!p()) fn();
  }

But the macro is better.
Last edited on
The macro definitely is a lot simpler, and easier to understand what it is doing.
Ok ok don't panic friends :)
I erase this goto routine for a conventional loop do/while. Thanks.
Some said that Macros are evil! I guess that I should shut my mouth because I am starting another controversial discussion with pros and cons considerations...
However, thank you all for your comments which help me. I wish you the best ++

https://stackoverflow.com/questions/14041453/why-are-preprocessor-macros-evil-and-what-are-the-alternatives



Last edited on
Probably the first thing to say is, this isn't an object oriented program; there are no objects.

The constructor should not call exit(), it should throw and exception in event of an unmanageable error.

You're passing strings by reference when they're really read only, so you should consider passing by const reference instead. And on that note, in
 
int guessMyWord::randomNumber(const int min_, const int max_)
those consts are pretty much redundant.

bAnagram could check if the string lengths match before using sort.

Rather than calling return in the do loop in main, try using break instead. You're doing goto by another name.
Last edited on
Hello. Thank you for your comment. You said :
bAnagram could check if the string lengths match before using sort.

But it seems to me that I did that before...
1
2
3
4
5
6
7
// not the right word length
if (myGuess.length() != gmw.secretWord.first.length())
std::cout << "Not the same length" << std::endl;
else if (!gmw.bAnagram(myGuess, gmw.secretWord.first)) // one or more letters are not in the secret word
std::cout << "Not the right letters" << std::endl; // the letters are good, the length too, but this word is weird
else if (gmw.bAnagram(myGuess, gmw.secretWord.first) && std::find(gmw.words.begin(), gmw.words.end(), myGuess) == gmw.words.end())
std::cout << "It seems that this word does not exist" << std::endl;
But it seems to me that I did that before...
True, but if your program was sufficiently large, you'd notice you'd often have to do that check before each call to bAnagram(). In the context of your program, there doesn't appear to be a difference.
Last edited on
Topic archived. No new replies allowed.