diff --git a/src/statistics-graphics.cpp b/src/statistics-graphics.cpp index 1c0cb2e..a65b80d 100644 --- a/src/statistics-graphics.cpp +++ b/src/statistics-graphics.cpp @@ -8,89 +8,83 @@ namespace Statistics { namespace Graphics { std::string AskForPlayerNamePrompt() { - constexpr auto score_prompt_text = - "Please enter your name to save this score: "; - constexpr auto sp = " "; - std::ostringstream score_prompt_richtext; - score_prompt_richtext << bold_on << sp << score_prompt_text << bold_off; - return score_prompt_richtext.str(); + constexpr auto score_prompt_text = "Please enter your name to save this score: "; + constexpr auto sp = " "; + std::ostringstream score_prompt_richtext; + score_prompt_richtext << bold_on << sp << score_prompt_text << bold_off; + return score_prompt_richtext.str(); } std::string MessageScoreSavedPrompt() { - constexpr auto score_saved_text = "Score saved!"; - constexpr auto sp = " "; - std::ostringstream score_saved_richtext; - score_saved_richtext << "\n" - << green << bold_on << sp << score_saved_text << bold_off - << def << "\n"; - return score_saved_richtext.str(); + constexpr auto score_saved_text = "Score saved!"; + constexpr auto sp = " "; + std::ostringstream score_saved_richtext; + score_saved_richtext << "\n" + << green << bold_on << sp << score_saved_text << bold_off + << def << "\n"; + return score_saved_richtext.str(); } std::string TotalStatisticsOverlay(total_stats_display_data_t tsdd) { - constexpr auto stats_title_text = "STATISTICS"; - constexpr auto divider_text = "──────────"; - constexpr auto header_border_text = "┌────────────────────┬─────────────┐"; - constexpr auto footer_border_text = "└────────────────────┴─────────────┘"; - const auto stats_attributes_text = {"Best Score", "Game Count", - "Number of Wins", "Total Moves Played", - "Total Duration"}; - constexpr auto no_save_text = "No saved statistics."; - constexpr auto any_key_exit_text = - "Press any key to return to the main menu... "; - constexpr auto sp = " "; + constexpr auto stats_title_text = "STATISTICS"; + constexpr auto divider_text = "──────────"; + constexpr auto header_border_text = "┌────────────────────┬─────────────┐"; + constexpr auto footer_border_text = "└────────────────────┴─────────────┘"; + + constexpr auto stats_attributes_text = std::array{"Best Score", "Game Count", + "Number of Wins", "Total Moves Played", + "Total Duration"}; + constexpr auto no_save_text = "No saved statistics."; + constexpr auto any_key_exit_text = "Press any key to return to the main menu... "; + constexpr auto sp = " "; - enum TotalStatsDisplayDataFields { - IDX_DATA_AVAILABLE, - IDX_BEST_SCORE, - IDX_GAME_COUNT, - IDX_GAME_WIN_COUNT, - IDX_TOTAL_MOVE_COUNT, - IDX_TOTAL_DURATION, - MAX_TOTALSTATSDISPLAYDATA_INDEXES - }; - - std::ostringstream stats_richtext; + enum TotalStatsDisplayDataFields { + IDX_DATA_AVAILABLE, + IDX_BEST_SCORE, + IDX_GAME_COUNT, + IDX_GAME_WIN_COUNT, + IDX_TOTAL_MOVE_COUNT, + IDX_TOTAL_DURATION, + MAX_TOTALSTATSDISPLAYDATA_INDEXES + }; - const auto stats_file_loaded = std::get(tsdd); - if (stats_file_loaded) { - constexpr auto num_of_stats_attributes_text = 5; - auto data_stats = std::array{}; - data_stats = {std::get(tsdd), - std::get(tsdd), - std::get(tsdd), - std::get(tsdd), - std::get(tsdd)}; + std::ostringstream stats_richtext; + const auto stats_file_loaded = std::get(tsdd); + + if (stats_file_loaded) { + constexpr auto num_of_stats_attributes_text = stats_attributes_text.size(); + auto data_stats = std::array{}; - auto counter{0}; - const auto populate_stats_info = [=, &counter, - &stats_richtext](const std::string) { - stats_richtext << sp << "│ " << bold_on << std::left << std::setw(18) - << std::begin(stats_attributes_text)[counter] << bold_off - << " │ " << std::right << std::setw(11) - << data_stats[counter] << " │" - << "\n"; - counter++; - }; + // Load data stats from the input tuple + data_stats = { + std::get(tsdd), + std::get(tsdd), + std::get(tsdd), + std::get(tsdd), + std::get(tsdd) + }; - stats_richtext << green << bold_on << sp << stats_title_text << bold_off - << def << "\n"; - stats_richtext << green << bold_on << sp << divider_text << bold_off << def - << "\n"; - stats_richtext << sp << header_border_text << "\n"; + stats_richtext << green << bold_on << sp << stats_title_text << bold_off << def << "\n"; + stats_richtext << green << bold_on << sp << divider_text << bold_off << def << "\n"; + stats_richtext << sp << header_border_text << "\n"; - for (const auto s : stats_attributes_text) { - populate_stats_info(s); + // Use structured binding for better readability + for (size_t i = 0; i < num_of_stats_attributes_text; ++i) { + stats_richtext << sp << "│ " << bold_on << std::left << std::setw(18) + << stats_attributes_text[i] << bold_off + << " │ " << std::right << std::setw(11) + << data_stats[i] << " │\n"; + } + + stats_richtext << sp << footer_border_text << "\n"; + } else { + stats_richtext << sp << no_save_text << "\n"; } - stats_richtext << sp << footer_border_text << "\n"; - - } else { - stats_richtext << sp << no_save_text << "\n"; - } - stats_richtext << "\n\n\n"; - stats_richtext << sp << any_key_exit_text; + stats_richtext << "\n\n\n"; + stats_richtext << sp << any_key_exit_text; - return stats_richtext.str(); + return stats_richtext.str(); } } // namespace Graphics diff --git a/src/statistics.cpp b/src/statistics.cpp index 65ba4af..34c711b 100644 --- a/src/statistics.cpp +++ b/src/statistics.cpp @@ -12,105 +12,134 @@ namespace Statistics { namespace { +// Helper function to receive player name input std::string receive_input_player_name(std::istream &is) { - std::string name; - is >> name; - return name; + std::string name; + is >> name; + + // Validate input + if (is.fail()) { + throw std::runtime_error("Invalid player name input."); + } + + return name; } +// Generate total game stats from input data total_game_stats_t generateStatsFromInputData(std::istream &is) { - total_game_stats_t stats; - is >> stats; - return stats; + total_game_stats_t stats; + is >> stats; + + // Validate input + if (is.fail()) { + throw std::runtime_error("Invalid statistics input data."); + } + + return stats; } -bool generateFilefromStatsData(std::ostream &os, total_game_stats_t stats) { - os << stats; - return true; +// Save statistics data to the output stream +bool generateFilefromStatsData(std::ostream &os, const total_game_stats_t &stats) { + os << stats; + return os.good(); // Check if the write was successful } -bool saveToFileEndGameStatistics(std::string filename, total_game_stats_t s) { - std::ofstream filedata(filename); - return generateFilefromStatsData(filedata, s); +// Save end game statistics to a file +bool saveToFileEndGameStatistics(const std::string &filename, const total_game_stats_t &s) { + std::ofstream filedata(filename); + if (!filedata) { + throw std::runtime_error("Could not open file for saving statistics."); + } + return generateFilefromStatsData(filedata, s); } -Scoreboard::Graphics::finalscore_display_data_t -make_finalscore_display_data(Scoreboard::Score finalscore) { - const auto fsdd = std::make_tuple( - std::to_string(finalscore.score), std::to_string(finalscore.largestTile), - std::to_string(finalscore.moveCount), secondsFormat(finalscore.duration)); - return fsdd; -}; +// Create final score display data +Scoreboard::Graphics::finalscore_display_data_t make_finalscore_display_data(const Scoreboard::Score &finalscore) { + return std::make_tuple( + std::to_string(finalscore.score), + std::to_string(finalscore.largestTile), + std::to_string(finalscore.moveCount), + secondsFormat(finalscore.duration) + ); +} } // namespace -load_stats_status_t loadFromFileStatistics(std::string filename) { - std::ifstream statistics(filename); - if (statistics) { +// Load statistics from a file +load_stats_status_t loadFromFileStatistics(const std::string &filename) { + std::ifstream statistics(filename); + if (!statistics) { + return load_stats_status_t{false, total_game_stats_t{}}; + } + total_game_stats_t stats = generateStatsFromInputData(statistics); return load_stats_status_t{true, stats}; - } - return load_stats_status_t{false, total_game_stats_t{}}; } +// Load the best score from game statistics ull load_game_best_score() { - total_game_stats_t stats; - bool stats_file_loaded{}; - ull tempscore{0}; - std::tie(stats_file_loaded, stats) = - loadFromFileStatistics("../data/statistics.txt"); - if (stats_file_loaded) { - tempscore = stats.bestScore; - } - return tempscore; -} + total_game_stats_t stats; + bool stats_file_loaded{}; + ull tempscore{0}; -void saveEndGameStats(Scoreboard::Score finalscore) { - total_game_stats_t stats; - // Need some sort of stats data values only. - // No need to care if file loaded successfully or not... - std::tie(std::ignore, stats) = - loadFromFileStatistics("../data/statistics.txt"); - stats.bestScore = - stats.bestScore < finalscore.score ? finalscore.score : stats.bestScore; - stats.gameCount++; - stats.winCount = finalscore.win ? stats.winCount + 1 : stats.winCount; - stats.totalMoveCount += finalscore.moveCount; - stats.totalDuration += finalscore.duration; - - saveToFileEndGameStatistics("../data/statistics.txt", stats); -} + std::tie(stats_file_loaded, stats) = loadFromFileStatistics("../data/statistics.txt"); + if (stats_file_loaded) { + tempscore = stats.bestScore; + } -void CreateFinalScoreAndEndGameDataFile(std::ostream &os, std::istream &is, - Scoreboard::Score finalscore) { - const auto finalscore_display_data = make_finalscore_display_data(finalscore); - DrawAlways(os, DataSuppliment(finalscore_display_data, - Scoreboard::Graphics::EndGameStatisticsPrompt)); + return tempscore; +} - DrawAlways(os, Graphics::AskForPlayerNamePrompt); - const auto name = receive_input_player_name(is); - finalscore.name = name; +// Save end game statistics +void saveEndGameStats(const Scoreboard::Score &finalscore) { + total_game_stats_t stats; + std::tie(std::ignore, stats) = loadFromFileStatistics("../data/statistics.txt"); + + stats.bestScore = std::max(stats.bestScore, finalscore.score); + stats.gameCount++; + stats.winCount += finalscore.win ? 1 : 0; + stats.totalMoveCount += finalscore.moveCount; + stats.totalDuration += finalscore.duration; + + saveToFileEndGameStatistics("../data/statistics.txt", stats); +} - Scoreboard::saveScore(finalscore); - saveEndGameStats(finalscore); - DrawAlways(os, Graphics::MessageScoreSavedPrompt); +// Create final score and end game data file +void CreateFinalScoreAndEndGameDataFile(std::ostream &os, std::istream &is, Scoreboard::Score finalscore) { + const auto finalscore_display_data = make_finalscore_display_data(finalscore); + DrawAlways(os, DataSuppliment(finalscore_display_data, Scoreboard::Graphics::EndGameStatisticsPrompt)); + + DrawAlways(os, Graphics::AskForPlayerNamePrompt); + finalscore.name = receive_input_player_name(is); + + Scoreboard::saveScore(finalscore); + saveEndGameStats(finalscore); + DrawAlways(os, Graphics::MessageScoreSavedPrompt); } } // namespace Statistics using namespace Statistics; +// Overload operator to read total game stats std::istream &operator>>(std::istream &is, total_game_stats_t &s) { - is >> s.bestScore >> s.gameCount >> s.winCount >> s.totalMoveCount >> - s.totalDuration; - return is; + is >> s.bestScore >> s.gameCount >> s.winCount >> s.totalMoveCount >> s.totalDuration; + + // Validate stream state after reading + if (is.fail()) { + throw std::runtime_error("Failed to read total game stats."); + } + + return is; } -std::ostream &operator<<(std::ostream &os, total_game_stats_t &s) { - os << s.bestScore << "\n" - << s.gameCount << "\n" - << s.winCount << "\n" - << s.totalMoveCount << "\n" - << s.totalDuration; - return os; +// Overload operator to write total game stats +std::ostream &operator<<(std::ostream &os, const total_game_stats_t &s) { + os << s.bestScore << "\n" + << s.gameCount << "\n" + << s.winCount << "\n" + << s.totalMoveCount << "\n" + << s.totalDuration; + + return os; }