diff --git a/.gitignore b/.gitignore index f0201ace..fa85fba9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ /data/* 2048 /.vscode +/src/.vscode/ .DS_Store # CMake @@ -47,4 +48,4 @@ Makefile cmake_install.cmake install_manifest.txt compile_commands.json -CTestTestfile.cmake \ No newline at end of file +CTestTestfile.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ed10330..352c8dfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ endif() add_executable(2048 ${SOURCES}) target_include_directories(2048 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/headers/) -target_compile_features(2048 PRIVATE cxx_std_14) +target_compile_features(2048 PRIVATE cxx_std_17) # --- test diff --git a/src/game-graphics.cpp b/src/game-graphics.cpp index 1282d6bf..6c36d957 100644 --- a/src/game-graphics.cpp +++ b/src/game-graphics.cpp @@ -117,11 +117,22 @@ std::string BoardSizeErrorPrompt() { return error_prompt_richtext.str(); } +/** + * @brief Generates a string prompt listing the available input commands. + * + * This function creates a formatted string that lists the available input commands for the game. + * The commands include movements (Up, Left, Down, Right), saving the game, and returning to the menu. + * The prompt is formatted with indentation for readability. + * + * + * + * @return std::string A formatted string containing the list of input commands. + */ std::string InputCommandListPrompt() { constexpr auto sp = " "; const auto input_commands_list_text = { "W or K or ↑ => Up", "A or H or ← => Left", "S or J or ↓ => Down", - "D or L or → => Right", "Z or P => Save"}; + "D or L or → => Right", "Z or P => Save", "M => Return to menu"}; std::ostringstream str_os; for (const auto txt : input_commands_list_text) { str_os << sp << txt << "\n"; diff --git a/src/game-pregamemenu.cpp b/src/game-pregamemenu.cpp index 9b464e69..5a128030 100644 --- a/src/game-pregamemenu.cpp +++ b/src/game-pregamemenu.cpp @@ -125,32 +125,40 @@ void SetUpNewGame(NewGameFlag ns) { endlessLoop(); } -load_gameboard_status_t initialiseContinueBoardArray() { +/** + * @brief Initializes a GameBoard object by loading game data from a specified file. + * + * This function constructs the full path to the game data file by prepending "../data/" to the given filename. + * It then calls the `load_game` function to load the game data into a new GameBoard object. The function returns + * a tuple containing the status of the game load operation (true if successful, false otherwise) and the initialized + * GameBoard object. + * + * @param filename The name of the file from which to read the game data. + * @return A tuple containing a boolean indicating the success of the load operation and the initialized GameBoard object. + */ +load_gameboard_status_t initialiseContinueBoardArray(const std::string& filename) +{ using namespace Loader; - constexpr auto gameboard_data_filename = "../data/previousGame"; - constexpr auto game_stats_data_filename = "../data/previousGameStats"; - auto loaded_gameboard{false}; - auto loaded_game_stats{false}; - auto tempGBoard = GameBoard{1}; - // Note: Reserved for gameboard.score and gameboard.moveCount! - // TODO: Combine data into one resource file. - auto score_and_movecount = - std::tuple{}; - std::tie(loaded_gameboard, tempGBoard) = - load_GameBoard_data_from_file(gameboard_data_filename); - std::tie(loaded_game_stats, score_and_movecount) = - load_game_stats_from_file(game_stats_data_filename); - std::tie(tempGBoard.score, tempGBoard.moveCount) = score_and_movecount; - - const auto all_files_loaded_ok = (loaded_gameboard && loaded_game_stats); - - return std::make_tuple(all_files_loaded_ok, tempGBoard); + // const auto gameboard_data_filename = "../data/" + filename; + auto gb = GameBoard{1}; + auto loaded_game = load_game(filename, gb); + return std::make_tuple(loaded_game, gb); } -void DoContinueOldGame() { + +/** + * @brief Continues a previously saved game from a specified file. + * + * This function attempts to load the game state from the provided filename. + * If successful, it continues the game using the loaded state. If the loading + * fails, it sets up a new game indicating that no previous save is available. + * + * @param filename The name of the file from which to load the previous game state. + */ +void DoContinueOldGame(const std::string& filename) { bool load_old_game_ok; GameBoard oldGameBoard; - std::tie(load_old_game_ok, oldGameBoard) = initialiseContinueBoardArray(); + std::tie(load_old_game_ok, oldGameBoard) = initialiseContinueBoardArray(filename); if (load_old_game_ok) { playGame(PlayGameFlag::ContinuePreviousGame, oldGameBoard); } else { @@ -164,8 +172,16 @@ void SetUpNewGame() { SetUpNewGame(NewGameFlag::NewGameFlagNull); } -void ContinueOldGame() { - DoContinueOldGame(); +/** + * @brief Continue a previously saved game. + * + * The ContinueOldGame function has been updated to accept a filename directly. + * This allows the user to load a specific save file instead of only the last saved game. + * + * @param filename The name of the file containing the saved game to load. + */ +void ContinueOldGame(const std::string& filename) { + DoContinueOldGame(filename); } } // namespace PreGameSetup diff --git a/src/game.cpp b/src/game.cpp index 95dc3309..7206e61e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -14,6 +14,7 @@ #include #include #include +#include // Required for generating random numbers/integers in the RemoveTiles() function. namespace Game { namespace { @@ -28,6 +29,7 @@ enum GameStatusFlag { FLAG_ENDLESS_MODE, FLAG_GAME_IS_ASKING_QUESTION_MODE, FLAG_QUESTION_STAY_OR_QUIT, + FLAG_TILES_REMOVED, // Indicates if tiles have already been removed MAX_NO_GAME_STATUS_FLAGS }; @@ -35,6 +37,16 @@ using gamestatus_t = std::array; using gamestatus_gameboard_t = std::tuple; +/** + * @brief Processes the game logic for the current game state. + * + * This function updates the game status and game board based on the current state. + * It handles tile movements, checks for game-winning conditions, and verifies if the game can continue. + * If the player cannot make any more moves, it prompts the player to remove random tiles before ending the game. + * + * @param gsgb A tuple containing the current game status and game board. + * @return gamestatus_gameboard_t A tuple containing the updated game status and game board. + */ gamestatus_gameboard_t process_gamelogic(gamestatus_gameboard_t gsgb) { gamestatus_t gamestatus; GameBoard gb; @@ -53,7 +65,31 @@ gamestatus_gameboard_t process_gamelogic(gamestatus_gameboard_t gsgb) { } } if (!canMoveOnGameboard(gb)) { - gamestatus[FLAG_END_GAME] = true; + if(gamestatus[FLAG_TILES_REMOVED] == false) + { + char input; + std::cout << "You lose. Do you want to remove random tiles (Y/N) ?" << std::endl; + std::cin >> input; + + if(input == 'Y' || input == 'y') + { + gamestatus[FLAG_TILES_REMOVED] = true; + removeTiles(gb); + } + else if (input == 'N' || input == 'n') + { + gamestatus[FLAG_END_GAME] = true; + gamestatus[FLAG_START_MENU] = true; + } + else + { + std::cout << "Invalid input." << std::endl; + } + } + else + { + gamestatus[FLAG_END_GAME] = true; + } } return std::make_tuple(gamestatus, gb); } @@ -162,6 +198,21 @@ gamestatus_t update_one_shot_display_flags(gamestatus_t gamestatus) { } using bool_gamestatus_t = std::tuple; +/** + * @brief Processes non-standard input commands and updates the game status accordingly. + * + * This function handles additional input commands that are not part of the standard gameplay. + * It updates the game status based on the input character received. The function supports saving + * the game, quitting endless mode, and returning to the main menu. + * + * @param c The input character received from the user. + * @param gamestatus The current game status flags. + * @return bool_gamestatus_t A tuple containing a boolean indicating if the keycode is invalid and the updated game status flags. + * + * @note Changes in the new version: + * - Added support for returning to the main menu with CODE_RETURN_TO_MENU. + * - When CODE_RETURN_TO_MENU is detected, the appropriate main menu flags are set. + */ bool_gamestatus_t check_input_other(char c, gamestatus_t gamestatus) { using namespace Input::Keypress::Code; auto is_invalid_keycode{true}; @@ -178,6 +229,12 @@ bool_gamestatus_t check_input_other(char c, gamestatus_t gamestatus) { is_invalid_keycode = false; } break; + case CODE_RETURN_TO_MENU: + // When CODE_RETURN_TO_MENU is detected, the main menu is set to start, and game flags are adjusted. mainmenustatus[FLAG_START_MENU] = true; + mainmenustatus[FLAG_START_GAME] = false; + mainmenustatus[FLAG_CONTINUE_GAME] = false; + is_invalid_keycode = false; + break; } return std::make_tuple(is_invalid_keycode, gamestatus); } @@ -267,6 +324,21 @@ bool continue_playing_game(std::istream &in_os) { return true; } +/** + * @brief Processes the current game status and updates the game loop control. + * + * This function evaluates the current game status flags and takes appropriate actions: + * - It handles winning conditions and prompts the user to continue playing. + * - It checks for the end of the game conditions. + * - It manages saving the game state by prompting the user for a filename. + * - It resets the question asking event trigger for a new loop cycle. + * + * @param gsgb A tuple containing the current game status and game board. + * @return bool_gamestatus_t A tuple containing a boolean indicating whether to continue the game loop and the updated game status flags. + * + * @note Changes in the new version: + * - Added a prompt asking the user to enter a filename when saving the game state. + */ bool_gamestatus_t process_gameStatus(gamestatus_gameboard_t gsgb) { gamestatus_t gamestatus; GameBoard gb; @@ -288,7 +360,10 @@ bool_gamestatus_t process_gameStatus(gamestatus_gameboard_t gsgb) { loop_again = false; } if (gamestatus[FLAG_SAVED_GAME]) { - Saver::saveGamePlayState(gb); + std::cout << "Please enter the filename to save the game state" << std::endl; + std::string filename; + std::cin >> filename; + Saver::saveGamePlayState(gb, filename); } // New loop cycle: reset question asking event trigger @@ -361,13 +436,31 @@ std::string drawEndGameLoopGraphics(current_game_session_t finalgamestatus) { return str_os.str(); } +/** + * @brief Runs the endless game loop until the game ends or the user returns to the menu. + * + * This function manages the continuous gameplay loop for the endless game mode. + * It repeatedly calls the solo game loop until the game ends or the user chooses to return to the menu. + * The game status and game board are updated accordingly in each iteration. + * + * @param currentBestScore The current best score achieved. + * @param cm The competition mode settings. + * @param gb The current game board state. + * @return GameBoard The final state of the game board after the loop ends. + * + * @note Changes in the new version: + * - Added checks to monitor if the current state is set to GAME or MENU. + * - The loop will terminate if the user presses the M key and the state changes to MENU. + */ GameBoard endlessGameLoop(ull currentBestScore, competition_mode_t cm, GameBoard gb) { auto loop_again{true}; auto currentgamestatus = std::make_tuple(currentBestScore, cm, gamestatus_t{}, gb); - - while (loop_again) { + // Monitor the loop to check if the current state is still set to GAME + // If not, then the M key was pressed, setting the current state to MENU + // Thus, we return to the menu + while (loop_again && ((mainmenustatus[FLAG_START_GAME] == true) || (mainmenustatus[FLAG_CONTINUE_GAME] == true))) { std::tie(loop_again, currentgamestatus) = soloGameLoop(currentgamestatus); } @@ -423,8 +516,45 @@ void startGame() { PreGameSetup::SetUpNewGame(); } -void continueGame() { - PreGameSetup::ContinueOldGame(); +/** + * @brief Continue a previously saved game. + * + * The ContinueOldGame function has been updated to accept a filename directly. + * This allows the user to load a specific save file instead of only the last saved game. + * + * @param filename The name of the file containing the saved game to load. + */ +void continueGame(const std::string& filename) { + PreGameSetup::ContinueOldGame(filename); +} + +/** + * @brief Randomly removes two tiles from a GameBoard by setting their values to 0. + * + * This function uses a random number generator to select and remove two non-empty tiles + * from the given GameBoard by setting their values to 0. The process continues until + * exactly two tiles are removed. + * + * @param gb The GameBoard object from which tiles will be removed. + */ +void removeTiles(GameBoard& gb) { + // Seed with a real random value, if available + std::random_device rd; + std::mt19937 gen(rd()); + + auto& [playsize, tiles] = gb.gbda; + std::uniform_int_distribution<> dis(0, tiles.size() - 1); + + int tiles_to_remove = 2; + while (tiles_to_remove > 0) { + int random_index = dis(gen); + auto& tile = tiles[random_index]; + + if (tile.value != 0) { // Ensure it's not already an empty tile + tile.value = 0; // Remove the tile + --tiles_to_remove; + } + } } } // namespace Game diff --git a/src/gameboard.cpp b/src/gameboard.cpp index 757e2e28..d6c6295d 100644 --- a/src/gameboard.cpp +++ b/src/gameboard.cpp @@ -67,6 +67,19 @@ bool getTileBlockedOnGameboardDataArray(gameboard_data_array_t gbda, return gameboard_data_point_t{}(gbda, pt).blocked; } +/** + * @brief Generates a string representation of the game board data array. + * + * This function creates a formatted string that represents the current state of the game board. + * It includes the tile values and their blocked status for each position on the board. + * The string representation ends with a "[" character to indicate the end of the data. + * + * @param gbda The game board data array to be printed. + * @return std::string A formatted string representing the game board state. + * + * @note Changes in the new version: + * - Added a "[" character at the end of the string to indicate the end of the data. + */ std::string printStateOfGameBoardDataArray(gameboard_data_array_t gbda) { const int playsize = getPlaySizeOfGameboardDataArray(gbda); std::ostringstream os; @@ -78,6 +91,7 @@ std::string printStateOfGameBoardDataArray(gameboard_data_array_t gbda) { } os << "\n"; } + os << "["; // Indicates the end of the game board data return os.str(); } @@ -135,7 +149,9 @@ bool canMoveOnGameboardDataArray(gameboard_data_array_t gbda) { const auto offset_check = { current_point + offset, // Positive adjacent check current_point - offset}; // Negative adjacent Check - for (const auto current_offset : offset_check) { + + // Use reference to avoid unnecessary copying of complex structures + for (const auto ¤t_offset : offset_check) { if (is_point_in_board_play_area(current_offset, playsize)) { return getTileValueOnGameboardDataArray(gbda, current_offset) == current_point_value; diff --git a/src/headers/game-input.hpp b/src/headers/game-input.hpp index 5c94c1e7..90b37c28 100644 --- a/src/headers/game-input.hpp +++ b/src/headers/game-input.hpp @@ -46,7 +46,8 @@ enum { CODE_HOTKEY_QUIT_ENDLESS_MODE = 'X', CODE_HOTKEY_CHOICE_NO = 'N', CODE_HOTKEY_CHOICE_YES = 'Y', - CODE_HOTKEY_PREGAMEMENU_BACK_TO_MAINMENU = 0 + CODE_HOTKEY_PREGAMEMENU_BACK_TO_MAINMENU = 0, + CODE_RETURN_TO_MENU = 'M' // Press 'M' to return to menu }; } // namespace Code diff --git a/src/headers/game-pregamemenu.hpp b/src/headers/game-pregamemenu.hpp index 0ada726e..f462ecad 100644 --- a/src/headers/game-pregamemenu.hpp +++ b/src/headers/game-pregamemenu.hpp @@ -1,10 +1,14 @@ #ifndef GAMEPREGAMEMENU_H #define GAMEPREGAMEMENU_H +#include + namespace Game { namespace PreGameSetup { + void SetUpNewGame(); -void ContinueOldGame(); +void ContinueOldGame(const std::string& filename); + } // namespace PreGameSetup } // namespace Game diff --git a/src/headers/game.hpp b/src/headers/game.hpp index ea1bd1d6..64019c0e 100644 --- a/src/headers/game.hpp +++ b/src/headers/game.hpp @@ -1,15 +1,18 @@ #ifndef GAME_H #define GAME_H +#include + namespace Game { struct GameBoard; enum class PlayGameFlag { BrandNewGame, ContinuePreviousGame }; void playGame(PlayGameFlag cont, GameBoard gb, unsigned long long userInput_PlaySize = 1); - void startGame(); -void continueGame(); +void continueGame(const std::string& filename); +void removeTiles(GameBoard& gb); + }; // namespace Game #endif diff --git a/src/headers/loadresource.hpp b/src/headers/loadresource.hpp index c4d42ec1..9d765aa4 100644 --- a/src/headers/loadresource.hpp +++ b/src/headers/loadresource.hpp @@ -14,6 +14,7 @@ load_gameboard_status_t load_GameBoard_data_from_file(std::string filename); // [decltype(gameboard.moveCount)]} std::tuple> load_game_stats_from_file(std::string filename); +bool load_game(std::string filename, GameBoard& gb); } // namespace Loader } // namespace Game diff --git a/src/headers/menu.hpp b/src/headers/menu.hpp index f3c94901..2f4206f4 100644 --- a/src/headers/menu.hpp +++ b/src/headers/menu.hpp @@ -1,6 +1,46 @@ #ifndef MENU_H #define MENU_H +#include +#include "gameboard.hpp" + +/** + * @enum MainMenuStatusFlag + * @brief Enumeration of possible main menu status flags. + * + * These flags represent various states and actions that can be triggered from the main menu. + */ +enum MainMenuStatusFlag { + FLAG_NULL, /**< No action */ + FLAG_START_GAME, /**< Start a new game */ + FLAG_START_MENU, /**< Start menu (new flag for starting menu) */ + FLAG_CONTINUE_GAME, /**< Continue an existing game */ + FLAG_DISPLAY_HIGHSCORES, /**< Display the high scores */ + FLAG_EXIT_GAME, /**< Exit the game */ + MAX_NO_MAIN_MENU_STATUS_FLAGS /**< Maximum number of main menu status flags */ +}; + +/** + * @typedef mainmenustatus_t + * @brief Array type for main menu status flags. + * + * This type represents an array of boolean values corresponding to each main menu status flag. + */ +using mainmenustatus_t = std::array; + +/** + * @var mainmenustatus + * @brief Global variable to track the status of main menu flags. + * + * This variable is used to store the current status of each main menu flag, allowing + * the game's main menu state to be accessed and modified globally. + */ +extern mainmenustatus_t mainmenustatus; + +std::vector listSavedGameStates(const std::string& directory); + +std::string chooseGameState(const std::vector& gamestate); + namespace Menu { void startMenu(); } // namespace Menu diff --git a/src/headers/saveresource.hpp b/src/headers/saveresource.hpp index 7a23db82..8a98100e 100644 --- a/src/headers/saveresource.hpp +++ b/src/headers/saveresource.hpp @@ -8,7 +8,8 @@ namespace Game { struct GameBoard; namespace Saver { -void saveGamePlayState(GameBoard gb); +void saveGamePlayState(GameBoard gb, const std::string& filename); + } // namespace Saver } // namespace Game diff --git a/src/loadresource.cpp b/src/loadresource.cpp index c3c6eff7..299dc3a7 100644 --- a/src/loadresource.cpp +++ b/src/loadresource.cpp @@ -14,27 +14,74 @@ namespace Game { namespace Loader { namespace { -int GetLines(std::string filename) { +/** + * @brief Counts the number of lines in a file until a line containing '[' is found. + * + * This function opens a file specified by the given filename and counts the number + * of lines until it encounters a line that contains the character '['. If the file + * cannot be opened, it prints an error message and returns -1. + * + * @param filename The name of the file to be read. + * @return The number of lines read before encountering a line with '['. Returns -1 if the file cannot be opened. + */ +int GetLines(std::string filename) +{ std::ifstream stateFile(filename); - using iter = std::istreambuf_iterator; - const auto noOfLines = std::count(iter{stateFile}, iter{}, '\n'); + if (!stateFile) { + std::cerr << "Error. Cannot open the file: " << filename << std::endl; + return -1; // Return -1 to indicate an error + } + + std::string tempLine; + int noOfLines = 0; + + while (std::getline(stateFile, tempLine)) { + if (tempLine.find('[') != std::string::npos) { + break; // Exit loop if '[' is found + } + noOfLines++; + } + return noOfLines; } +/** + * @brief Extracts tile data from a given input stream until a line containing '[' is encountered. + * + * This function reads lines from the provided input stream and extracts tile data + * formatted as comma-separated values. The process continues until either the maximum + * width (10 lines) is reached or a line containing the character '[' is found. When + * a line with '[' is encountered, the function stops reading further and processes + * the line up to, but not including, the '[' character. + * + * @param buf The input stream from which to read the tile data. + * @return A vector of strings containing the extracted tile data. + */ std::vector get_file_tile_data(std::istream &buf) { - std::vector tempbuffer; - enum { MAX_WIDTH = 10, MAX_HEIGHT = 10 }; - auto i{0}; - for (std::string tempLine; std::getline(buf, tempLine) && i < MAX_WIDTH; - i++) { - std::istringstream temp_filestream(tempLine); - auto j{0}; - for (std::string a_word; - std::getline(temp_filestream, a_word, ',') && j < MAX_HEIGHT; j++) { - tempbuffer.push_back(a_word); + std::vector tempbuffer; + enum { MAX_WIDTH = 10, MAX_HEIGHT = 10 }; + auto i{0}; + + for (std::string tempLine; std::getline(buf, tempLine) && i < MAX_WIDTH; i++) { + if (tempLine.find('[') != std::string::npos) { + // Remove the '[' character and stop reading further + tempLine = tempLine.substr(0, tempLine.find('[')); + std::istringstream temp_filestream(tempLine); + auto j{0}; + for (std::string a_word; std::getline(temp_filestream, a_word, ',') && j < MAX_HEIGHT; j++) { + tempbuffer.push_back(a_word); + } + break; // Stop the outer loop as the end of tile data is reached + } + + std::istringstream temp_filestream(tempLine); + auto j{0}; + for (std::string a_word; std::getline(temp_filestream, a_word, ',') && j < MAX_HEIGHT; j++) { + tempbuffer.push_back(a_word); + } } - } - return tempbuffer; + + return tempbuffer; } std::vector @@ -130,6 +177,64 @@ load_game_stats_from_file(std::string filename) { return get_and_process_game_stats_string_data(stats); } +/** + * @brief Loads game data from a specified file into a GameBoard object. + * + * This function opens a file specified by the given filename, reads the game board data, + * and initializes a GameBoard object with the read data. It first counts the number of lines + * until a line containing '[' is found to determine the size of the game board. Then, it reads + * the tile data from the file, processes it, and updates the GameBoard object. Finally, it reads + * the score and move count from the last relevant line containing these values. + * + * @param fileName The name of the file from which to read the game data. + * @param gb The GameBoard object to be initialized with the read data. + * @return true if the game data was successfully loaded; false otherwise. + */ +bool load_game(std::string fileName, GameBoard& gb) +{ + std::ifstream stateFile(fileName); + if(!stateFile.is_open()) + { + std::cerr << "Cannot open the file: " << fileName << std::endl; + return false; + } + + const ull savedBoardPlaySize = GetLines(fileName); + const auto file_tile_data = get_file_tile_data(stateFile); + const auto processed_tile_data = process_file_tile_string_data(file_tile_data); + + gb = GameBoard(savedBoardPlaySize, processed_tile_data); + + stateFile.clear(); + stateFile.seekg(0, std::ios::beg); + + std::string current_line; + std::string last_line; + + while (std::getline(stateFile, current_line)) + { + if (!current_line.empty() && current_line.front() == '[') + { + last_line = current_line; + break; + } + } + + size_t opening_square_bracket = last_line.find('['); + size_t colon = last_line.find(':'); + size_t closing_square_bracket = last_line.find(']'); + + if (opening_square_bracket != std::string::npos && colon != std::string::npos && closing_square_bracket != std::string::npos) + { + std::string score = last_line.substr(opening_square_bracket + 1, colon - opening_square_bracket - 1); + std::string moveCount = last_line.substr(colon + 1, closing_square_bracket - colon - 1); + gb.score = std::stoull(score); + gb.moveCount = std::stoll(moveCount) - 1; + } + + return true; +} + } // namespace Loader } // namespace Game diff --git a/src/menu.cpp b/src/menu.cpp index eecdab27..30a82d22 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -2,6 +2,7 @@ #include "color.hpp" #include "game-graphics.hpp" #include "game.hpp" +#include "gameboard.hpp" // To iterate over all saved Games #include "global.hpp" #include "menu-graphics.hpp" #include "scores-graphics.hpp" @@ -12,29 +13,110 @@ #include #include #include +#include +#include //To use std::filestream -namespace { +mainmenustatus_t mainmenustatus{}; -enum MainMenuStatusFlag { - FLAG_NULL, - FLAG_START_GAME, - FLAG_CONTINUE_GAME, - FLAG_DISPLAY_HIGHSCORES, - FLAG_EXIT_GAME, - MAX_NO_MAIN_MENU_STATUS_FLAGS -}; +/** + * @brief Lists all saved game states in the specified directory. + * + * This function scans the specified directory for regular files and returns their names + * as a list of strings. It checks if the directory exists before attempting to list the files. + * + * @param directory The directory to search for saved game state files. + * @return std::vector A vector containing the names of the saved game state files. + */ +std::vector listSavedGameStates(const std::string& directory) +{ + std::vector gameStates; -using mainmenustatus_t = std::array; + if(!std::filesystem::exists(directory)) + { + std::cout << "Directory does not exists." << std::endl; + return gameStates; + } + + for(auto& data : std::filesystem::directory_iterator(directory)) + { + if(data.is_regular_file()) + { + gameStates.push_back(data.path().filename().string()); + } + } + + return gameStates; +} + +/** + * @brief Prompts the user to choose a game state from a list. + * + * This function displays the available game states and prompts the user to select one. + * If no game states are found, or if the user's choice is invalid, appropriate messages are displayed. + * + * @param gamestate A vector containing the names of available game states. + * @return std::string The name of the chosen game state, or an empty string if the choice is invalid. + */ +std::string chooseGameState(const std::vector& gamestate) +{ + if(gamestate.empty()) + { + std::cout << "No saved games found." << std::endl; + return ""; + } + + std::cout << "Saved games are:" << std::endl; + for (std::size_t i = 0; i < gamestate.size(); i++) + { + std::cout << i + 1 << ". " << gamestate[i] << std::endl; + } + + unsigned int index; + std::cout << "Choose game state:" << std::endl; + std::cout << std::endl; + std::cin >> index; + + if(index < 1 || index > gamestate.size()) + { + std::cout << "Invalid choice." << std::endl; + return ""; + } + + return gamestate[index - 1]; +} + +namespace { -mainmenustatus_t mainmenustatus{}; bool FlagInputErrornousChoice{}; void startGame() { Game::startGame(); } -void continueGame() { - Game::continueGame(); +/** + * @brief Continues a previously saved game. + * + * This function prompts the user to choose a saved game state from a specified directory. + * If a valid saved game state is selected, it continues the game using that state. + * If no valid saved game state is selected, it notifies the user that the file is empty. + * + * @note Changes in the new version: + * - Added functionality to list and choose a saved game state from the specified directory. + * - Validates the chosen game state file and continues the game using the selected file. + * - Prints a message if the selected file is empty. + */ +void continueGame() +{ + std::string directory_state = "../data/SavedGameFiles/"; + std::string file_gb_state = chooseGameState(listSavedGameStates(directory_state)); + if (!file_gb_state.empty()) + { + Game::continueGame(directory_state + file_gb_state); + } + else + { + std::cout << "The file is empty" << std::endl; + } } Scoreboard::Graphics::scoreboard_display_data_list_t @@ -146,9 +228,30 @@ bool soloLoop() { return FlagInputErrornousChoice; } +/** + * @brief Runs the endless loop until the game is exited. + * + * This function continuously runs the solo game loop until the exit flag is set in the main menu status. + * Initially, it sets the start menu flag to display the menu. If the start menu flag is set, it calls the solo loop + * function to handle the menu interactions. + * + * @note Changes in the new version: + * - Added a condition to exit the loop when the exit flag is set in the main menu status. + * - Integrated the menu start state and menu interaction within the loop. + */ void endlessLoop() { - while (soloLoop()) - ; + // As long as the exit option is not selected in the menu + while (!mainmenustatus[FLAG_EXIT_GAME]) + { + // Initial state is Menu + mainmenustatus[FLAG_START_MENU] = true; + // If the menu flag is set, we enter... + if (mainmenustatus[FLAG_START_MENU] == true) + { + // ... the soloLoop() function, where we navigate the menu + soloLoop(); + } + } } } // namespace diff --git a/src/saveresource.cpp b/src/saveresource.cpp index 566203e6..7b28402d 100644 --- a/src/saveresource.cpp +++ b/src/saveresource.cpp @@ -1,47 +1,104 @@ #include "saveresource.hpp" #include "gameboard.hpp" #include +#include namespace Game { namespace Saver { namespace { + +/** + * @brief Generates a file from the previous game's statistics data. + * + * This function writes the score and move count of the given game board to the provided output stream. + * The data is formatted as "score:moveCount]" to indicate the end of the statistics data. + * + * @param os The output stream where the statistics data will be written. + * @param gb The game board containing the statistics to be saved. + * @return bool Returns true after successfully writing the statistics data. + * + * @note The ']' character is used to signify the end of the statistics data. + */ bool generateFilefromPreviousGameStatisticsData(std::ostream &os, const GameBoard &gb) { - os << gb.score << ":" << MoveCountOnGameBoard(gb); + os << gb.score << ":" << MoveCountOnGameBoard(gb) << "]" << std::endl; return true; } +/** + * @brief Generates a file from the previous game's state data. + * + * This function writes the state of the given game board to the provided output stream. + * The data is formatted using the printStateOfGameBoard function. + * + * @param os The output stream where the state data will be written. + * @param gb The game board containing the state to be saved. + * @return bool Returns true after successfully writing the state data. + */ bool generateFilefromPreviousGameStateData(std::ostream &os, const GameBoard &gb) { os << printStateOfGameBoard(gb); return true; } +/** + * @brief Saves the previous game state and statistics to a file. + * + * This function creates or appends to a file specified by the filename, saving both the game state and statistics. + * The game state is written first, followed by the game statistics. + * + * @param filename The name of the file where the game state and statistics will be saved. + * @param gb The game board containing the state and statistics to be saved. + */ void saveToFilePreviousGameStateData(std::string filename, const GameBoard &gb) { std::ofstream stateFile(filename, std::ios_base::app); generateFilefromPreviousGameStateData(stateFile, gb); + generateFilefromPreviousGameStatisticsData(stateFile, gb); + } -void saveToFilePreviousGameStatisticsData(std::string filename, +// This function can be deleted because it is no longer needed. +// The functionality to save game statistics has been integrated +// into the saveToFilePreviousGameStateData function, making this +// function redundant. +/*void saveToFilePreviousGameStatisticsData(std::string filename, const GameBoard &gb) { std::ofstream stats(filename, std::ios_base::app); generateFilefromPreviousGameStatisticsData(stats, gb); -} +}*/ } // namespace -void saveGamePlayState(GameBoard gb) { - // Currently two datafiles for now. - // Will be merged into one datafile in a future PR. - constexpr auto filename_game_data_state = "../data/previousGame"; - constexpr auto filename_game_data_statistics = "../data/previousGameStats"; - std::remove(filename_game_data_state); - std::remove(filename_game_data_statistics); +/** + * @brief Saves the current game state to a file. + * + * This function checks for the existence of the directory and creates it if necessary. + * It then removes any existing file with the specified filename before saving the + * current state and statistics of the provided game board to a new file. This ensures + * that only the most recent game state is saved. + * + * @param gb The game board object containing the current state to be saved. + * @param filename The name of the file where the game state will be saved. This file will + * be located in the ../data/SavedGameFiles/ directory. If a file with this + * name already exists, it will be deleted before saving the new state. + * + * @note If the directory does not exist, it will be created. Ensure that the application + * has the necessary permissions to write to the specified location. + */ +void saveGamePlayState(GameBoard gb, const std::string& filename) { + std::filesystem::path directory_path = "../data/SavedGameFiles/"; + + if (!std::filesystem::exists(directory_path)) + { + std::filesystem::create_directories(directory_path); + } + + const auto path_to_file_gd_state = "../data/SavedGameFiles/" + filename; + std::remove(path_to_file_gd_state.c_str()); - saveToFilePreviousGameStateData(filename_game_data_state, gb); - saveToFilePreviousGameStatisticsData(filename_game_data_statistics, gb); + saveToFilePreviousGameStateData(path_to_file_gd_state, gb); } } // namespace Saver diff --git a/src/scores-graphics.cpp b/src/scores-graphics.cpp index 6a1a1425..09dfbe15 100644 --- a/src/scores-graphics.cpp +++ b/src/scores-graphics.cpp @@ -56,7 +56,8 @@ std::string ScoreboardOverlay(scoreboard_display_data_list_t sbddl) { << "\n"; }; - for (const auto s : sbddl) { + // Use reference to avoid unnecessary copying of complex structures + for (const auto& s : sbddl) { print_score_stat(s); } str_os << sp << bottom_border_text << "\n";