From b0b183df926101592230d4cfcc9d5c483005d7f3 Mon Sep 17 00:00:00 2001 From: David Leal Date: Mon, 12 Jun 2023 10:17:36 -0600 Subject: [PATCH] feat: add memory game and a games folder (#2471) * feat: add memory game and a games folder * fix: `clang-tidy` warnings (hopefully) * fix: use `random_shuffle` `random_shuffle` was removed in C++17, however, we're using C++11 here, so there should be no harm. * updating DIRECTORY.md * clang-format and clang-tidy fixes for 8cd0a772 * fix: remove repeated `random` header * clang-format and clang-tidy fixes for 02786880 * fix: Windows build errors * fix: CI warnings (hopefully) * fix: CI warnings * fix: (finally) CI warnings * fix: wrong parameter name * fix: yet another attempt to fix CI --------- Co-authored-by: github-actions[bot] --- CMakeLists.txt | 1 + DIRECTORY.md | 3 + games/CMakeLists.txt | 16 ++ games/memory_game.cpp | 416 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 436 insertions(+) create mode 100644 games/CMakeLists.txt create mode 100644 games/memory_game.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index def89c22a..f92eefd74 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) add_subdirectory(cpu_scheduling_algorithms) cmake_policy(SET CMP0054 NEW) diff --git a/DIRECTORY.md b/DIRECTORY.md index 38b36e936..e15d1a07f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -120,6 +120,9 @@ * [Tree Height](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/tree_height.cpp) * [Word Break](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/word_break.cpp) +## Games + * [Memory Game](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/games/memory_game.cpp) + ## Geometry * [Graham Scan Algorithm](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/geometry/graham_scan_algorithm.cpp) * [Graham Scan Functions](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/geometry/graham_scan_functions.hpp) 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..9283340ae --- /dev/null +++ b/games/memory_game.cpp @@ -0,0 +1,416 @@ +/** + * @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 std::shuffle() +#include /// for std::srand() +#include /// for std::time() +#include /// for IO operations +#include /// for std::mt19937 +#include /// for std::vector + +// `Sleep` is only available in Windows in milliseconds. +// However, on Unix/Linux systems it is `sleep`, in seconds. +#ifdef _WIN32 +#include /// for Sleep() +template +constexpr typename std::enable_if::value, void>::type SLEEP( + T milliseconds) { + Sleep(milliseconds * 1000); +} +#else +#include /// for sleep() +template +constexpr T SLEEP(T seconds) { + return sleep(seconds); +} +#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); + } + + std::shuffle(pairs.begin(), pairs.end(), + std::mt19937(std::random_device()())); + + 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"; + std::cout << std::boolalpha; + std::cout << std::is_literal_type::value; + + 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; +}