evilhangman/src/evilhangman.cpp
2024-09-05 21:35:48 +02:00

228 lines
7.6 KiB
C++
Executable File

#include <QCoreApplication>
#include <QDir>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
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<std::string> readDictionary(size_t wordLength) {
std::string dictionaryPath = QDir(QCoreApplication::applicationDirPath())
.filePath(DICTIONARY_NAME)
.toStdString();
std::string fileContent = fileRead(dictionaryPath);
std::vector<std::string> 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 <typename T>
inline void printContainer(std::vector<T> &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 <typename T> inline void printContainer(std::vector<T> &container) {
printContainer<T>(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<std::string, std::vector<std::string>>
getWordFamilies(const std::vector<std::string> &words,
const std::vector<char> &previousGuesses, const char guess) {
static const char NO_SELECTION = '-';
std::unordered_map<std::string, std::vector<std::string>> 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<std::string> dictionary = readDictionary(wordLength);
std::string guessProgress(wordLength, '-');
std::cout << "Word: " << guessProgress << std::endl;
std::vector<char> 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<std::string, std::vector<std::string>> families =
getWordFamilies(dictionary, guessedChars, guess);
using Entry = std::pair<std::string, std::vector<std::string>>;
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;
}