From d4fb6ce2cbfb09ecb86a8dcd388f755a42c1f0d0 Mon Sep 17 00:00:00 2001 From: Aman Menda Date: Mon, 6 May 2024 17:04:35 +0100 Subject: [PATCH] feat: get current month games, errors handling --- include/App/Logic.hpp | 14 +++- include/UI/GamesPlayedPage.hpp | 2 + src/App/Logic.cpp | 137 ++++++++++++++++++--------------- src/UI/GamesPlayedPage.cpp | 106 ++++++++++++++++++++----- 4 files changed, 177 insertions(+), 82 deletions(-) diff --git a/include/App/Logic.hpp b/include/App/Logic.hpp index 8e436e2..6f67b58 100644 --- a/include/App/Logic.hpp +++ b/include/App/Logic.hpp @@ -31,11 +31,21 @@ namespace Stuckfish struct Response { - int errorCode; - std::string errorMessage; + int statusCode; + std::string message; std::vector gamesData; }; + enum ResponseCode + { + OK = 0, + NO_GAMES_FOUND, + DATA_EXTRACTION_ERROR, + MISSING_FIELD_ERROR, + PARSING_ERROR, + NETWORK_ERROR + }; + class Logic { public: diff --git a/include/UI/GamesPlayedPage.hpp b/include/UI/GamesPlayedPage.hpp index 5c76d92..9695efe 100644 --- a/include/UI/GamesPlayedPage.hpp +++ b/include/UI/GamesPlayedPage.hpp @@ -8,6 +8,7 @@ #pragma once #include "Stuckfish.hpp" +#include namespace Stuckfish { @@ -27,6 +28,7 @@ namespace Stuckfish Logic& _logic; UserData& _userdata; Response _res; + GamesData _selectedGameData; bool _hasRetrievedGames = false; bool _isFirstElement = true; diff --git a/src/App/Logic.cpp b/src/App/Logic.cpp index 3384ac7..08cc0c9 100644 --- a/src/App/Logic.cpp +++ b/src/App/Logic.cpp @@ -53,59 +53,76 @@ void Logic::GetInfosFromListOfGamesPlayed(const std::string& username, const Document& doc) { // Check if parsing succeeded - if (!doc.HasParseError()) { - if (doc.HasMember("games") && doc["games"].IsArray()) { - const Value& gamesArray = doc["games"]; - - if (gamesArray.Empty()) { - // No popup, just display a a message on the screen - std::cerr << "No games found !\n"; - return; - } - // Iterate over the array and do whatever you need with each game object - for (SizeType i = 0; i < gamesArray.Size(); ++i) { - const Value& game = gamesArray[i]; - GamesData gameData; - - // get white username and rating - if ((gameData.whiteUsername = extractString(game["white"], "username")) == "") - std::cerr << "Unable to get white username\n"; - if ((gameData.whiteRating = extractInt(game["white"], "rating")) == "") - std::cerr << "Unable to get white rating\n"; - - // get black username and rating - if ((gameData.blackUsername = extractString(game["black"], "username")) == "") - std::cerr << "Unable to get black username\n"; - if ((gameData.blackRating = extractInt(game["black"], "rating")) == "") - std::cerr << "Unable to get black rating\n"; - - // get time_class - if ((gameData.timeClass = extractString(game, "time_class")) == "") - std::cerr << "Unable to get time class\n"; - - //get pgn - if ((gameData.pgn = extractString(game, "pgn")) == "") - std::cerr << "Unable to get png\n"; - - // find if player has played as white or black and display if he won or lost the game. - if (gameData.whiteUsername == username) - gameData.result = game["white"]["result"] != "win" ? "Lost" : "Won"; - else - gameData.result = game["black"]["result"] != "win" ? "Lost" : "Won"; - - // insert this game data in the vector - _gamesData.push_back(gameData); - } - } - else { - std::cerr << "JSON does not contain a valid 'games' array field." << std::endl; - return; - } + if (doc.HasParseError()) + { + _res.statusCode = ResponseCode::PARSING_ERROR; + _res.message = "Parsing failed with error code " + std::string(GetParseError_En(doc.GetParseError())); + return; } - else { - std::cerr << "Parsing failed with error code " << GetParseError_En(doc.GetParseError()) << std::endl; + + if (!doc.HasMember("games") || !doc["games"].IsArray()) { + _res.statusCode = ResponseCode::MISSING_FIELD_ERROR; + _res.message = "JSON does not contain a valid 'games' array field.\n"; return; } + + const Value& gamesArray = doc["games"]; + + if (gamesArray.Empty()) { + // No popup, just display a a message on the screen + _res.statusCode = ResponseCode::NO_GAMES_FOUND; + _res.message = "No games played yet !"; + return; + } + // Iterate over the array and do whatever you need with each game object + for (SizeType i = 0; i < gamesArray.Size(); ++i) { + const Value& game = gamesArray[i]; + GamesData gameData; + + // get white username and rating + if ((gameData.whiteUsername = extractString(game["white"], "username")) == "") + { + _res.statusCode = ResponseCode::DATA_EXTRACTION_ERROR; + _res.message = "Unable to get white username\n"; + } + if ((gameData.whiteRating = extractInt(game["white"], "rating")) == "") + { + _res.statusCode = ResponseCode::DATA_EXTRACTION_ERROR; + _res.message = "Unable to get white rating\n"; + } + // get black username and rating + if ((gameData.blackUsername = extractString(game["black"], "username")) == "") + { + _res.statusCode = ResponseCode::DATA_EXTRACTION_ERROR; + _res.message = "Unable to get black username\n"; + } + if ((gameData.blackRating = extractInt(game["black"], "rating")) == "") + { + _res.statusCode = ResponseCode::DATA_EXTRACTION_ERROR; + _res.message = "Unable to get black rating\n"; + } + // get time_class + if ((gameData.timeClass = extractString(game, "time_class")) == "") + { + _res.statusCode = ResponseCode::DATA_EXTRACTION_ERROR; + _res.message = "Unable to get time class\n"; + } + //get pgn + if ((gameData.pgn = extractString(game, "pgn")) == "") + { + _res.statusCode = ResponseCode::DATA_EXTRACTION_ERROR; + _res.message = "Unable to get pgn\n"; + } + // find if player has played as white or black and display if he won or lost the game. + if (gameData.whiteUsername == username) + gameData.result = game["white"]["result"] != "win" ? "Lost" : "Won"; + else + gameData.result = game["black"]["result"] != "win" ? "Lost" : "Won"; + + // insert this game data in the vector + _gamesData.push_back(gameData); + } + _res.statusCode = ResponseCode::OK; } //------------------------------------------------------------------------------ @@ -116,18 +133,18 @@ Logic::GamesPlayedWithinPeriod(const std::string& username, const std::string& y { std::string url = "https://api.chess.com/pub/player/" + to_lower(username) + "/games/" + year + '/' + month; cpr::Response res = cpr::Get(cpr::Url{ url }); + Document doc; - if (res.status_code == cpr::status::HTTP_OK) { - Document doc; - - doc.Parse(res.text.c_str()); - GetInfosFromListOfGamesPlayed(username, doc); - _res.errorCode = EXIT_SUCCESS; - _res.gamesData = _gamesData; - } else { - _res.errorCode = res.status_code; - _res.errorMessage = res.error.message; + if (res.status_code == cpr::status::HTTP_NOT_FOUND) + { + _res.statusCode = NO_GAMES_FOUND; + _res.message = "No games played yet !"; + return _res; } + + doc.Parse(res.text.c_str()); + GetInfosFromListOfGamesPlayed(username, doc); + _res.gamesData = _gamesData; return _res; } diff --git a/src/UI/GamesPlayedPage.cpp b/src/UI/GamesPlayedPage.cpp index 6447650..7f834c7 100644 --- a/src/UI/GamesPlayedPage.cpp +++ b/src/UI/GamesPlayedPage.cpp @@ -5,6 +5,42 @@ #include "../../include/UI/GamesPlayedPage.hpp" + +static +std::string getCurrentMonth(void) +{ + std::time_t currentTime; + std::tm currentTm; + std::time(¤tTime); + + if (localtime_s(¤tTm, ¤tTime) != 0) { + std::cerr << "Error: Unable to get the current time." << std::endl; + return ""; + } + + int currentMonth = currentTm.tm_mon + 1; + + if (currentMonth >= 1 && currentMonth <= 9) + { + return "0" + std::to_string(currentMonth); + } + return std::to_string(currentMonth); +} + +static +std::string getCurrentYear(void) +{ + std::time_t currentTime; + std::tm currentTm; + std::time(¤tTime); + + if (localtime_s(¤tTm, ¤tTime) != 0) { + std::cerr << "Error: Unable to get the current time." << std::endl; + return ""; + } + return std::to_string(currentTm.tm_year + 1900); +} + namespace Stuckfish { //------------------------------------------------------------------------------ @@ -14,6 +50,11 @@ void GamesPlayedPage::OnUIRender() { float windowPosX = 20; float windowPosY = 20; + std::string month = getCurrentMonth(); + std::string year = getCurrentYear(); + + // raise an error if year/month is empty. + ImVec2 windowPos(windowPosX, windowPosY); ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always); @@ -26,29 +67,44 @@ void GamesPlayedPage::OnUIRender() // page title ImGui::PushFont(_app._robotoFontHeader); ImGui::SetCursorPos(ImVec2(windowMiddlePos.x - 170, windowMiddlePos.y - 340)); - ImGui::Text("Games played: April 2024"); // TODO: get the current month using a C++ library + std::string pageTitleLabel = "Games played: " + month + '/' + year; + ImGui::Text(pageTitleLabel.c_str()); // TODO: get the current year using a C++ library ImGui::PopFont(); - // list of all games played in the specified month from which we can extract: - // the player and his ELO and same for the opponent, the game status for the player (win/lose/draw) - // the game time control (rapid, blitz, bullet) - ImVec2 buttonSize(confirmButtonSizeX*5, confirmButtonSizeY); + ImVec2 buttonSize(confirmButtonSizeX * 5, confirmButtonSizeY); + ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); if (!_hasRetrievedGames) { - _res = _logic.GamesPlayedWithinPeriod(_userdata.username, "2024", "05"); - - // handle every errors here - if (_res.errorCode == EXIT_FAILURE) - { - _errorOccured = true; - _errorMessage = _res.errorMessage; - } + // just use the current month automatically for now; + _res = _logic.GamesPlayedWithinPeriod(_userdata.username, year, month); _hasRetrievedGames = true; } - // Begin a scrollable region - ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); - for (auto& game : _res.gamesData) { + // only when switch between months will be supported. + if (_res.statusCode == ResponseCode::NO_GAMES_FOUND) + { + ImGui::PushFont(_app._robotoFontHeader); + ImGui::SetCursorPos(ImVec2(windowMiddlePos.x - 170, windowMiddlePos.y)); + ImGui::Text("No game found for the specified period !"); + ImGui::PopFont(); + ImGui::EndChild(); + ImGui::End(); + return; + } + + // every other errors. + if (_res.statusCode != ResponseCode::OK) + { + ImGui::PushFont(_app._robotoFontHeader); + ImGui::SetCursorPos(ImVec2(windowMiddlePos.x - 170, windowMiddlePos.y)); + ImGui::Text("Unexpected error occured while fetching your games !"); + ImGui::PopFont(); + ImGui::EndChild(); + ImGui::End(); + return; + } + + for (const auto& game : _res.gamesData) { std::string buttonLabel = game.whiteUsername + "(" + game.whiteRating + ") vs " + game.blackUsername + "(" + game.blackRating + ") - " + game.result + " - " + game.timeClass; @@ -56,8 +112,8 @@ void GamesPlayedPage::OnUIRender() ImVec2 buttonPos = ImVec2((ImGui::GetWindowSize().x - buttonSize.x) / 2.0f, ImGui::GetCursorPosY()); ImGui::SetCursorPos(buttonPos); if (ImGui::Button(buttonLabel.c_str(), buttonSize)) { - // link to the next page maybe ? - std::cout << game.pgn; + _selectedGameData = game; + OnUpdate(); } } ImGui::EndChild(); @@ -68,7 +124,9 @@ void GamesPlayedPage::OnUIRender() /** */ void GamesPlayedPage::OnUpdate() -{} +{ + OnDetach(); +} //------------------------------------------------------------------------------ /** @@ -78,8 +136,16 @@ void GamesPlayedPage::OnAttach() //------------------------------------------------------------------------------ /** +* Consider writing OnDetach() directly in Page.hpp */ void GamesPlayedPage::OnDetach() -{} +{ + std::vector>& pageStack = _app.GetPageStack(); + + if (!pageStack.empty()) + pageStack.erase(pageStack.begin()); + + return; +} } \ No newline at end of file