I am trying to create a simple Hangman game. The game has an edit mode for first run in which the user has to supply 6 unique words. The game starts by shuffling words, then for every turn, it selects one word that the user has to figure out. The user has only 6 tries to get it right for every word.
How can I improve it?
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <random>
#include <iterator>
#include <functional>
#include <set>
#include <array>
#include <regex>
#include <limits>
namespace
{
const std::size_t Max = 6;
const std::regex Letters("^[A-Za-z]+$");
}
std::mt19937 randomEngine()
{
std::array<std::mt19937::result_type, std::mt19937::state_size> seed_data;
thread_local std::random_device source;
std::generate(seed_data.begin(), seed_data.end(), std::ref(source));
std::seed_seq seeds(seed_data.begin(), seed_data.end());
thread_local std::mt19937 seeded_engine(seeds);
return seeded_engine;
}
template <class InputIt, class Size, class OutputIt, class UnaryPredicate>
void copy_if_n(InputIt first, InputIt last, Size count,
OutputIt result, UnaryPredicate pred)
{
if (count <= 0 || first == last) return;
if (pred(*first)) *result++ = *first;
for (Size i = 1; i < count && first != last; ++i)
{
if (pred(*first)) *result++ = *++first;
}
}
std::vector<std::string> ask_words()
{
std::set<std::string> s;
bool is_good = false;
while (!is_good)
{
std::cout << "Enter 6 unique words: ";
auto begin = std::istream_iterator<std::string>(std::cin);
auto end = std::istream_iterator<std::string>();
copy_if_n(begin, end,
Max,
std::inserter(s, s.begin()),
[](const auto& str)
{
return std::regex_match(str, Letters);
});
if (std::cin.fail() || s.size() != Max)
{
std::cout << "Not valid input.\n";
if (!s.empty()) s.clear();
is_good = false;
}
else
{
is_good = true;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
return {s.begin(), s.end()};
}
std::string ask_guess(const std::string& str)
{
std::string result;
bool is_good = false;
while (!is_good)
{
std::cout << '\n' << str << ": ";
std::getline(std::cin, result);
if (std::cin.fail() || !std::regex_match(result, Letters))
{
std::cout << "Not valid input.\n";
if (!result.empty()) result.clear();
is_good = false;
}
else
{
is_good = true;
}
std::cin.clear();
std::cin.ignore(std::cin.rdbuf()->in_avail());
};
return result;
}
bool askYN(const std::string& str)
{
char result;
bool is_good = false;
while (!is_good)
{
std::cout << str << ": ";
std::cin >> result;
result = std::toupper(result);
if (std::cin.fail() || (result != 'Y' && result != 'N'))
{
std::cout << "Not valid input.\n";;
is_good = false;
}
else
{
is_good = true;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
};
return result == 'Y';
}
int main()
{
// set up game
auto words = ask_words();
thread_local auto engine = randomEngine();
std::shuffle(words.begin(), words.end(), engine);
// main loop
do
{
// reset game
auto word(words.back());
words.pop_back();
const auto origin(word);
std::string score(word.size(), '-');
std::size_t wrong = 0;
bool running = true;
// game loop
while (running)
{
const auto guess(ask_guess("Guess a letter or a word"));
if (guess.length() > 1) // for long word
{
if (guess == origin)
std::cout << "\n\nThat's right, you win!\n\n";
else
std::cout << "\n\nWrong word\n\n";
running = false;
}
else // for single letter
{
bool found = false;
const auto ch = guess.back();
for (std::string::size_type i = 0; i != word.size(); ++i)
{
if (word[i] != ch) continue;
score[i] = ch;
word[i] = '!';
found = true;
}
if (!found)
{
std::cout << "\n\nThat's wrong\n\n";
wrong++;
}
}
if (wrong == Max)
{
running = false;
std::cout << "\n\nYou Lose! The word was: " << origin << "\n\n";
}
std::cout << score << '\n';
std::cout << "\n\nThere are " << word.length() << " letters left\n";
std::cout << "You have " << Max - wrong << " more tries left\n\n";
if (score == origin)
{
running = false;
std::cout << "\n\nYou win!\n\n";
}
};
}
while (!words.empty() && askYN("Do you want to play again? [y/n]: "));
}