diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bbda35e1..10c6db014 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_subdirectory(machine_learning) add_subdirectory(numerical_methods) add_subdirectory(graph) add_subdirectory(divide_and_conquer) +add_subdirectory(games) cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0057 NEW) diff --git a/games/CMakeLists.txt b/games/CMakeLists.txt new file mode 100644 index 000000000..734509aa0 --- /dev/null +++ b/games/CMakeLists.txt @@ -0,0 +1,16 @@ +# If necessary, use the RELATIVE flag, otherwise each source file may be listed +# with full pathname. The RELATIVE flag makes it easier to extract an executable's name +# automatically. + +file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp ) +foreach( testsourcefile ${APP_SOURCES} ) + string( REPLACE ".cpp" "" testname ${testsourcefile} ) # File type. Example: `.cpp` + add_executable( ${testname} ${testsourcefile} ) + + set_target_properties(${testname} PROPERTIES LINKER_LANGUAGE CXX) + if(OpenMP_CXX_FOUND) + target_link_libraries(${testname} OpenMP::OpenMP_CXX) + endif() + install(TARGETS ${testname} DESTINATION "bin/games") # Folder name. Do NOT include `<>` + +endforeach( testsourcefile ${APP_SOURCES} ) diff --git a/games/memory_game.cpp b/games/memory_game.cpp new file mode 100644 index 000000000..39ed6cac2 --- /dev/null +++ b/games/memory_game.cpp @@ -0,0 +1,370 @@ +/** + * @file + * @brief A simple [Memory Game](https://en.wikipedia.org/wiki/Matching_game) + * with **3 different sizes** and multiple letters. + * @details + * The game consists on finding **the pair** of all the given letters depending on the table size. + * Once all of the instances are all found, the game will end and will ask you if you'd like to play again or not. + * + * It provides **3 different sizes** available that the user can choose (4x2, 5x2, 7x2). 7x2 being the + * biggest table size and hardest mode. The bigger the size, **the more letters are available**. + * + * @author [David Leal](https://github.com/Panquesito7) +*/ + +#include /// for IO operations +#include /// for std::time() +#include /// for std::srand() +#include /// for std::vector +#include /// for std::shuffle() +#include /// for std::mt19937 + +#ifdef _WIN32 +#include /// for sleep +#else +#include /// for sleep +#endif + +// `std::random_shuffle` was deprecated in C++14. To keep support with most compilers, we need to check the C++ version. +#if __cplusplus >= 201402L + template + constexpr auto SHUFFLE(T a, T b) { std::shuffle(a, b, std::mt19937(std::random_device()())); } +#else + template + constexpr auto SHUFFLE(T a, T b) { std::random_shuffle(a, b); } +#endif + +/** + * @namespace + * @brief (Mini)game implementations. + */ +namespace games { +/** + * @namespace + * @brief Functions for the [Memory Game](https://en.wikipedia.org/wiki/Matching_game) implementation + */ +namespace memory_game { +/** + * @brief Utility function to verify if the given input is a number or not. + * This is very useful to prevent the program being stuck in a loop. + * @tparam T The type of the input + * @param input The input to check. + * @returns false if the input IS empty or if it contains a non-digit character + * @returns true if the input is NOT empty and if it contains only digit characters + */ +template +bool is_number(const T& input) { + if (std::cin.fail()) { + std::cin.clear(); + std::cin.ignore(256, '\n'); + + return false; + } + + return true; +} + +/** + * @brief Initializes the table with the letters. + * @tparam T The type of the table. + * @param table The table to initialize. + * @returns void + */ +template +void init(std::vector *table) { + std::vector letters(7); + + // Decrease / increase the number of letters depending on the size. + if ((*table).size() == 10) { // 5x2 + letters = { 'A', 'E', 'Z', 'P', 'D' }; + } + else if ((*table).size() == 8) { // 4x2 + letters = { 'A', 'E', 'Z', 'D' }; + } + else if ((*table).size() == 14) { // 7x2 + letters = { 'A', 'E', 'Z', 'P', 'D', 'B', 'M' }; + } + + std::vector pairs; + for (char letter : letters) { + pairs.push_back(letter); + pairs.push_back(letter); + } + + SHUFFLE(pairs.begin(), pairs.end()); + + for (int i = 0; i < (*table).size(); i++) { + (*table)[i] = pairs[i]; + } + + std::cout << "All available types are: "; + + for (int i = 0; i < letters.size(); i++) { + if (i == letters.size() - 1) { + std::cout << "and " << letters[i] << ".\n\n"; + } + else { + std::cout << letters[i] << ", "; + } + } +} + +/** + * @brief Utility function to print the table. + * @tparam T The type of the table. + * @param table The table to print. + * @returns void + */ +template +void print_table(const std::vector &table) { + std::cout << "| "; + std::vector table_print(table.size()); + + for (int i = 0; i < table.size(); i++) { + table_print[i] = ' '; + + if (table[i] != 0) { + table_print[i] = table[i]; + } + } + + for (int i = 0; i < table.size(); i++) { + if (i % 5 == 0 && i != 0) { + std::cout << "\n| "; + } + + std::cout << table_print[i] << " | "; + } +} + +// Prototype function. This is needed as `ask_data` calls `reset_data`, and `reset_data` calls `ask_data`. +template +void reset_data(const std::vector &, int*, int*, int*); + +/** + * @brief Function that asks the user for their + * input in the table they previously chose. + * @tparam T The type of the table. + * @param table The table that's used to get the user's input and data. + * @param answer The user's answer. + * @param old_answer The user's previous answer. + * @param memory_count A counter to check if the user has already answered two values. + * @returns void + */ +template +void ask_data(const std::vector &table, int* answer, int* old_answer, int* memory_count) { + (*old_answer) = (*answer); + print_table(table); + + std::cout << "\n\nType your response here (number index):\n"; + std::cin >> (*answer); + + if (!is_number((*answer))) { + std::cout << "\nYou must enter a valid number.\n\n"; + reset_data(table, answer, old_answer, memory_count); + } + + // Increase the memory count, which will be later on used for checking if the user has already answered two values. + (*memory_count)++; + + if (((*answer) > table.size()) || ((*answer) < 1)) { + std::cout << "\nYou can't check a value that doesn't exist (or an invalid number).\n\n"; + reset_data(table, answer, old_answer, memory_count); + } + + if ((*old_answer) == (*answer)) { + std::cout << "\nYou can't check the same value twice.\n\n"; + reset_data(table, answer, old_answer, memory_count); + } + + // If two matches are answered already, but the user checkes a non-answered and an answered value, the program will mark it as no match, however, we must not allow the user to check the same value twice. + if ((table[(*answer) - 1] != 0) && ((table[(*old_answer)] == 0) || (table[(*old_answer)] != 0))) { + std::cout << "\nYou can't check the same value twice.\n\n"; + reset_data(table, answer, old_answer, memory_count); + } +} + +/** + * @brief Utility function that resets the data if the user enters an invalid value. + * @tparam T The type of the table. + * @param table The table that will be used to call `ask_data()`. + * @param answer The user's answer. + * @param old_answer The user's previous answer. + * @param memory_count A counter to check if the user has already answered two values. + * @returns void + */ +template +void reset_data(const std::vector& table, int* answer, int *old_answer, int* memory_count) { + (*answer) = (*old_answer); + (*memory_count)--; + + ask_data(table, answer, old_answer, memory_count); +} + +/** + * @brief Checks if the two values given by the user match. + * @tparam T The type of the table. + * @param table_empty The table with no values, slowly assigned from `table` depending on the user's input. + * @param table The table with the original values. + * @param answer The user's answer. + * @param first_time A boolean to check if the user has already answered a value. + * @param old_answer The user's previous answer. + * @param memory_count A counter to check if the user has already answered two values. + * @returns true IF the values given by the user match + * @returns false if the values given by the user do NOT match + */ +template +bool match(const std::vector &table, std::vector *table_empty, const int &answer, bool *first_time, int *old_answer, int *memory_count) { + if ((*first_time) == true) { + return true; + } + + // Search across the whole table and if the two values match, keep results, otherwise, hide 'em up. + for (int i = 0; i < table.size() + 1; i++) { + if (i == answer) { + if (table[i - 1] == table[(*old_answer) - 1]) { + (*first_time) = true; + (*memory_count) = 0; + + (*old_answer) = 0; + return true; + } + else { + std::cout << "\nNo match (value was " << table[i - 1] << ", index is " << i << ").\n\n"; + + (*table_empty)[(*old_answer) - 1] = 0; + (*table_empty)[answer - 1] = 0; + + (*first_time) = true; + (*memory_count) = 0; + + (*old_answer) = 0; + return false; + } + } + } + + return false; +} + +/** + * @brief Function to assign the results to the table. + * + * Also checkes if the user has answered all the values already, as well as + * verify if the user made a match or not. + * @tparam T The type of the tables. + * @param table_empty The table with no values, slowly assigned from `table` depending on the user's input. + * @param table The table with the original values. + * @param answer The user's answer. + * @param first_time A boolean to check if the user has already answered a value. + * @param old_answer The user's previous answer. + * @param memory_count A counter to check if the user has already answered two values. + * @returns void + */ +template +void assign_results(std::vector *table_empty, std::vector *table, int* answer, bool *first_time, int *old_answer, int *memory_count) { + // Search through the entire table and if the answer matches the index, show the value. + // If it doesn't match, hide both the values. Don't forget to keep older values already answered. + for (int i = 0; i < (*table).size() + 1; i++) { + if (i == (*answer)) { + if (match((*table), table_empty, (*answer), first_time, old_answer, memory_count) == true) { + (*table_empty)[i - 1] = (*table)[i - 1]; + (*first_time) = true; + } + } + } + + if ((*memory_count) == 1) { + (*first_time) = false; + (*memory_count) = 0; + } + + char try_again = 'n'; + + // Has the user finished the game? Use a `for` loop, and if the table is full, ask the user if he wants to play again. + for (int i = 0; i < (*table).size() + 1; i++) { + if ((*table_empty)[i] == 0) { + break; + } + else if (i == (*table).size() - 1) { + print_table((*table)); + + std::cout << "\n\nYou won. Congratulations! Do you want to play again? (y/n)\n"; + std::cout << "Size " << (*table).size() << " will be used. This can be changed by re-running the game."; + std::cin >> try_again; + if (try_again == 'y') { + // This is needed when checking if the user has two matches already. + for (int i = 0; i < (*table_empty).size(); i++) { + (*table_empty)[i] = 0; + } + + init(table); + } + else if (try_again == 'n') { + std::cout << "\nThanks for playing the game!\n"; + sleep(3); + + exit(0); + } + else { + std::cout << "\nInvalid input (exitting...).\n"; + sleep(3); + + exit(0); + } + } + } + + // Ask data again. + ask_data((*table_empty), answer, old_answer, memory_count); + assign_results(table_empty, table, answer, first_time, old_answer, memory_count); +} +} // namespace memory_game +} // namespace games + +/** + * @brief Main function + * @returns 0 on exit + */ +int main() { + // Start randomizer. This changes the values every time. + std::srand(std::time(nullptr)); + + int size = 0; ///< Size of the table. + int selection = 0; ///< Selection of the size (4x2, 5x2, 7x2). + + int response = 0; ///< The answer (number index) that the user chose. + int old_answer = 0; ///< Previous answer (number index). + + int memory_count = 0; ///< Counter to check if the user has already answered two values. + bool first_time = true; ///< Whether the user has answered 1 value or not (previous answered values do not count). + + std::cout << "\tMEMORY GAME\n"; + + do { + std::cout << "\n1. 4x2 (1)"; + std::cout << "\n2. 5x2 (2)"; + std::cout << "\n3. 7x2 (3)\n"; + + std::cout << "\nChoose table size: "; + std::cin >> selection; + } while ((selection < 1 || selection > 3) && (!games::memory_game::is_number(selection))); + + switch(selection) { + case 1: size = 8; break; + case 2: size = 10; break; + case 3: size = 14; break; + default: size = 10; break; + } + + std::vector table(size); + std::vector table_empty(size); + + std::cout << "\n"; + + games::memory_game::init(&table); + games::memory_game::ask_data(table_empty, &response, &old_answer, &memory_count); + games::memory_game::assign_results(&table_empty, &table, &response, &first_time, &old_answer, &memory_count); + + return 0; +}