#include #include #include #include #include #include #include #include const std::string ALPHABET = "abcdefghijklmnopqrstuvwxyz"; const char *const FORMAT_UNDERSCORE = "\033[4m"; const char *const END_FORMATTING = "\033[0m"; const char *const DICTIONARY_NAME = "../../../dictionary.txt"; std::string input(const char *message) { if (message != nullptr) std::cout << message; std::cout << FORMAT_UNDERSCORE; std::flush(std::cout); std::string ret; std::getline(std::cin, ret); std::cout << END_FORMATTING; return ret; } int inputNumber(const char *message) { while (true) { std::string userInput = input(message); if (userInput.empty()) continue; bool isNumber = std::all_of(userInput.begin(), userInput.end(), [](const char c) { return std::isdigit(c); }); if (isNumber) return std::stoi(userInput); std::cout << "The input '" << userInput << "', is not a number" << std::endl; } } std::string fileRead(const std::string &filePath) { std::ifstream file(filePath, std::ios::binary); if (!file.is_open()) throw std::runtime_error("File couldn't be found"); std::string content; const int BUF_LENGTH = 1024; char buf[BUF_LENGTH]; while (file.read(buf, BUF_LENGTH)) content.append(buf, file.gcount()); content.append(buf, file.gcount()); if (!file.eof() && file.fail()) throw std::runtime_error("Error: Failed to read the file '" + filePath + "'"); return content; } std::vector readDictionary(size_t wordLength) { std::string dictionaryPath = QDir(QCoreApplication::applicationDirPath()) .filePath(DICTIONARY_NAME) .toStdString(); std::string fileContent = fileRead(dictionaryPath); std::vector ret; size_t last = 0, idx; while ((idx = fileContent.find('\n', last)) != std::string::npos) { std::string sub = fileContent.substr(last, idx - last); last = idx + 1; if (sub.size() == wordLength) ret.push_back(sub); } ret.push_back(fileContent.substr(last)); return ret; } template inline void printContainer(std::vector &container, const char *sep) { for (size_t i = 0; i < container.size(); i++) { std::cout << container[i]; if (sep != nullptr && i != container.size() - 1) std::cout << sep; } std::cout << std::endl; } template inline void printContainer(std::vector &container) { printContainer(container, nullptr); } char getGuess() { while (true) { std::string guess = input("Guess a character: "); if (guess.size() != 1) { std::cout << "The guess must be EXATCLY one character!" << std::endl; continue; } if (!std::isalpha(guess[0])) { std::cout << "The guess cannot be anything else than an alphanumeric character!" << std::endl; continue; } return guess[0]; } } std::unordered_map> getWordFamilies(const std::vector &words, const std::vector &previousGuesses, const char guess) { static const char NO_SELECTION = '-'; std::unordered_map> families; for (const std::string &word : words) { std::string key; key.reserve(word.length()); for (size_t i = 0; i < word.length(); i++) { const char at = word[i]; bool isGuessedChar = at == guess; bool isPreviouslyGuessed = std::find(previousGuesses.begin(), previousGuesses.end(), at) != std::end(previousGuesses); key.push_back((isGuessedChar || isPreviouslyGuessed) ? at : NO_SELECTION); } // indexing operator will create a default (empty) vector if it doesn't // exist families[key].push_back(word); } return families; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); std::cout << "Welcome to Hangman." << std::endl; int wordLength = inputNumber("How long should the word be: "); size_t maxAttempts = inputNumber("How many guesses do you want? "); bool showRemainingWords; { std::string aux = input("Should I tell you my thoughts? (y/N): "); std::transform(aux.begin(), aux.end(), aux.begin(), ::tolower); showRemainingWords = aux == "y" || aux == "yes"; } std::vector dictionary = readDictionary(wordLength); std::string guessProgress(wordLength, '-'); std::cout << "Word: " << guessProgress << std::endl; std::vector guessedChars; size_t wrongAttempts = 0; bool hasWon = false, hasLost = false; do { char guess; while (true) { std::cout << "(wrong attempts: " << wrongAttempts << '/' << maxAttempts << ") "; guess = getGuess(); bool guessIsUnique = std::find(guessedChars.begin(), guessedChars.end(), guess) == std::end(guessedChars); if (guessIsUnique) break; else std::cout << "You've already guessed '" << guess << "'!" << std::endl; } // Only update the word families if we're not in the endgame if (dictionary.size() > 1) { std::unordered_map> families = getWordFamilies(dictionary, guessedChars, guess); using Entry = std::pair>; auto biggestEntry = std::max_element( families.begin(), families.end(), [](const Entry &a, const Entry &b) { return a.second.size() < b.second.size(); }); guessProgress = biggestEntry->first; dictionary = biggestEntry->second; } else { std::string &word = dictionary[0]; for (size_t i = 0; i < word.size(); i++) { if (word[i] != guess) continue; guessProgress[i] = guess; } } if (showRemainingWords) { std::cout << "Possible words: "; printContainer(dictionary, ", "); } bool wasCorrect = guessProgress.find(guess) != std::string::npos; if (wasCorrect) { std::cout << "Correct!" << std::endl; } else { std::cout << "Wrong!" << std::endl; wrongAttempts++; } guessedChars.push_back(guess); std::cout << "Used letters: "; printContainer(guessedChars, " "); std::cout << "Word: " << guessProgress << std::endl; // Check if all the characters in the final word have been guessed hasWon = std::all_of(guessProgress.begin(), guessProgress.end(), [](char c) { return c != '-'; }); hasLost = wrongAttempts == maxAttempts; } while (!hasWon && !hasLost); const char *out = hasWon ? "congratulations! You've won! 🎉" : "you lost."; std::cout << "The word was '" << dictionary[0] << "', " << out << std::endl; return 0; }