diff --git a/src/app/TetrisApp.cpp b/src/app/TetrisApp.cpp index 9a60d4f..589e797 100644 --- a/src/app/TetrisApp.cpp +++ b/src/app/TetrisApp.cpp @@ -2054,21 +2054,15 @@ void TetrisApp::Impl::runLoop() pixelFont.draw(renderer, boxX + (boxW - sW) * 0.5f + contentOffsetX, boxY + 100 + contentOffsetY, scoreStr, 1.2f, {255, 255, 255, 255}); if (isNewHighScore) { - const char* enterName = "ENTER NAME:"; + const bool isCoopEntry = (game && game->getMode() == GameMode::Cooperate && coopGame); + const char* enterName = isCoopEntry ? "ENTER NAMES:" : "ENTER NAME:"; int enW=0, enH=0; pixelFont.measure(enterName, 1.0f, enW, enH); pixelFont.draw(renderer, boxX + (boxW - enW) * 0.5f + contentOffsetX, boxY + 160 + contentOffsetY, enterName, 1.0f, {200, 200, 220, 255}); - float inputW = 300.0f; - float inputH = 40.0f; - float inputX = boxX + (boxW - inputW) * 0.5f; - float inputY = boxY + 200.0f; - - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_FRect inputRect{inputX + contentOffsetX, inputY + contentOffsetY, inputW, inputH}; - SDL_RenderFillRect(renderer, &inputRect); - - SDL_SetRenderDrawColor(renderer, 255, 220, 0, 255); - SDL_RenderRect(renderer, &inputRect); + const float inputW = isCoopEntry ? 260.0f : 300.0f; + const float inputH = 40.0f; + const float inputX = boxX + (boxW - inputW) * 0.5f; + const float inputY = boxY + 200.0f; const float nameScale = 1.2f; const bool showCursor = ((SDL_GetTicks() / 500) % 2) == 0; @@ -2077,34 +2071,67 @@ void TetrisApp::Impl::runLoop() pixelFont.measure("A", nameScale, metricsW, metricsH); if (metricsH == 0) metricsH = 24; - int nameW = 0, nameH = 0; - if (!playerName.empty()) { - pixelFont.measure(playerName, nameScale, nameW, nameH); + // Single name entry (non-coop) --- keep original behavior + if (!isCoopEntry) { + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_FRect inputRect{inputX + contentOffsetX, inputY + contentOffsetY, inputW, inputH}; + SDL_RenderFillRect(renderer, &inputRect); + SDL_SetRenderDrawColor(renderer, 255, 220, 0, 255); + SDL_RenderRect(renderer, &inputRect); + + int nameW = 0, nameH = 0; + if (!playerName.empty()) pixelFont.measure(playerName, nameScale, nameW, nameH); + else nameH = metricsH; + + float textX = inputX + (inputW - static_cast(nameW)) * 0.5f + contentOffsetX; + float textY = inputY + (inputH - static_cast(metricsH)) * 0.5f + contentOffsetY; + + if (!playerName.empty()) pixelFont.draw(renderer, textX, textY, playerName, nameScale, {255,255,255,255}); + + if (showCursor) { + int cursorW = 0, cursorH = 0; pixelFont.measure("_", nameScale, cursorW, cursorH); + float cursorX = playerName.empty() ? inputX + (inputW - static_cast(cursorW)) * 0.5f + contentOffsetX : textX + static_cast(nameW); + float cursorY = inputY + (inputH - static_cast(cursorH)) * 0.5f + contentOffsetY; + pixelFont.draw(renderer, cursorX, cursorY, "_", nameScale, {255,255,255,255}); + } + + const char* hint = "PRESS ENTER TO SUBMIT"; + int hW=0, hH=0; pixelFont.measure(hint, 0.8f, hW, hH); + pixelFont.draw(renderer, boxX + (boxW - hW) * 0.5f + contentOffsetX, boxY + 280 + contentOffsetY, hint, 0.8f, {150, 150, 150, 255}); } else { - nameH = metricsH; + // Coop: prompt sequentially. First ask Player 1, then ask Player 2 after Enter. + const bool askingP1 = (highScoreEntryIndex == 0); + const char* label = askingP1 ? "PLAYER 1:" : "PLAYER 2:"; + int labW=0, labH=0; pixelFont.measure(label, 1.0f, labW, labH); + pixelFont.draw(renderer, boxX + (boxW - labW) * 0.5f + contentOffsetX, boxY + 160 + contentOffsetY, label, 1.0f, {200,200,220,255}); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_FRect rect{inputX + contentOffsetX, inputY + contentOffsetY, inputW, inputH}; + SDL_RenderFillRect(renderer, &rect); + SDL_SetRenderDrawColor(renderer, 255, 220, 0, 255); + SDL_RenderRect(renderer, &rect); + + const std::string &activeName = askingP1 ? playerName : player2Name; + int nameW = 0, nameH = 0; + if (!activeName.empty()) pixelFont.measure(activeName, nameScale, nameW, nameH); + else nameH = metricsH; + + float textX = inputX + (inputW - static_cast(nameW)) * 0.5f + contentOffsetX; + float textY = inputY + (inputH - static_cast(metricsH)) * 0.5f + contentOffsetY; + if (!activeName.empty()) pixelFont.draw(renderer, textX, textY, activeName, nameScale, {255,255,255,255}); + + if (showCursor) { + int cursorW=0, cursorH=0; pixelFont.measure("_", nameScale, cursorW, cursorH); + float cursorX = activeName.empty() ? inputX + (inputW - static_cast(cursorW)) * 0.5f + contentOffsetX : textX + static_cast(nameW); + float cursorY = inputY + (inputH - static_cast(cursorH)) * 0.5f + contentOffsetY; + pixelFont.draw(renderer, cursorX, cursorY, "_", nameScale, {255,255,255,255}); + } + + const char* hint = askingP1 ? "PRESS ENTER FOR NEXT NAME" : "PRESS ENTER TO SUBMIT"; + int hW=0, hH=0; pixelFont.measure(hint, 0.8f, hW, hH); + pixelFont.draw(renderer, boxX + (boxW - hW) * 0.5f + contentOffsetX, boxY + 300 + contentOffsetY, hint, 0.8f, {150, 150, 150, 255}); } - float textX = inputX + (inputW - static_cast(nameW)) * 0.5f + contentOffsetX; - float textY = inputY + (inputH - static_cast(metricsH)) * 0.5f + contentOffsetY; - - if (!playerName.empty()) { - pixelFont.draw(renderer, textX, textY, playerName, nameScale, {255, 255, 255, 255}); - } - - if (showCursor) { - int cursorW = 0, cursorH = 0; - pixelFont.measure("_", nameScale, cursorW, cursorH); - float cursorX = playerName.empty() - ? inputX + (inputW - static_cast(cursorW)) * 0.5f + contentOffsetX - : textX + static_cast(nameW); - float cursorY = inputY + (inputH - static_cast(cursorH)) * 0.5f + contentOffsetY; - pixelFont.draw(renderer, cursorX, cursorY, "_", nameScale, {255, 255, 255, 255}); - } - - const char* hint = "PRESS ENTER TO SUBMIT"; - int hW=0, hH=0; pixelFont.measure(hint, 0.8f, hW, hH); - pixelFont.draw(renderer, boxX + (boxW - hW) * 0.5f + contentOffsetX, boxY + 280 + contentOffsetY, hint, 0.8f, {150, 150, 150, 255}); - } else { char linesStr[64]; snprintf(linesStr, sizeof(linesStr), "LINES: %d", game->lines()); diff --git a/src/network/supabase_client.cpp b/src/network/supabase_client.cpp index 1b43630..51e8bbe 100644 --- a/src/network/supabase_client.cpp +++ b/src/network/supabase_client.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include using json = nlohmann::json; @@ -35,6 +36,13 @@ static CurlInit g_curl_init; namespace supabase { +static bool g_verbose = false; + +void SetVerbose(bool enabled) { + g_verbose = enabled; +} + + void SubmitHighscoreAsync(const ScoreEntry &entry) { std::thread([entry]() { try { @@ -68,18 +76,21 @@ void SubmitHighscoreAsync(const ScoreEntry &entry) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); - // Debug: print outgoing request - std::cerr << "[Supabase] POST " << url << "\n"; - std::cerr << "[Supabase] Body: " << body << "\n"; + if (g_verbose) { + std::cerr << "[Supabase] POST " << url << "\n"; + std::cerr << "[Supabase] Body: " << body << "\n"; + } CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { - std::cerr << "[Supabase] POST error: " << curl_easy_strerror(res) << "\n"; + if (g_verbose) std::cerr << "[Supabase] POST error: " << curl_easy_strerror(res) << "\n"; } else { long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - std::cerr << "[Supabase] POST response code: " << http_code << " body_len=" << resp.size() << "\n"; - if (!resp.empty()) std::cerr << "[Supabase] POST response: " << resp << "\n"; + if (g_verbose) { + std::cerr << "[Supabase] POST response code: " << http_code << " body_len=" << resp.size() << "\n"; + if (!resp.empty()) std::cerr << "[Supabase] POST response: " << resp << "\n"; + } } curl_slist_free_all(headers); @@ -97,15 +108,17 @@ std::vector FetchHighscores(const std::string &gameType, int limit) if (!curl) return out; std::string path = "highscores"; + // Clamp limit to max 10 to keep payloads small + int l = std::clamp(limit, 1, 10); std::string query; if (!gameType.empty()) { if (gameType == "challenge") { - query = "?game_type=eq." + gameType + "&order=level.desc,time_sec.asc&limit=" + std::to_string(limit); + query = "?game_type=eq." + gameType + "&order=level.desc,time_sec.asc&limit=" + std::to_string(l); } else { - query = "?game_type=eq." + gameType + "&order=score.desc&limit=" + std::to_string(limit); + query = "?game_type=eq." + gameType + "&order=score.desc&limit=" + std::to_string(l); } } else { - query = "?order=score.desc&limit=" + std::to_string(limit); + query = "?order=score.desc&limit=" + std::to_string(l); } std::string url = buildUrl(path) + query; @@ -123,15 +136,16 @@ std::vector FetchHighscores(const std::string &gameType, int limit) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); - // Debug: print outgoing GET - std::cerr << "[Supabase] GET " << url << "\n"; + if (g_verbose) std::cerr << "[Supabase] GET " << url << "\n"; CURLcode res = curl_easy_perform(curl); if (res == CURLE_OK) { long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - std::cerr << "[Supabase] GET response code: " << http_code << " body_len=" << resp.size() << "\n"; - if (!resp.empty()) std::cerr << "[Supabase] GET response: " << resp << "\n"; + if (g_verbose) { + std::cerr << "[Supabase] GET response code: " << http_code << " body_len=" << resp.size() << "\n"; + if (!resp.empty()) std::cerr << "[Supabase] GET response: " << resp << "\n"; + } try { auto j = json::parse(resp); if (j.is_array()) { @@ -151,10 +165,10 @@ std::vector FetchHighscores(const std::string &gameType, int limit) } } } catch (...) { - std::cerr << "[Supabase] GET parse error" << std::endl; + if (g_verbose) std::cerr << "[Supabase] GET parse error" << std::endl; } } else { - std::cerr << "[Supabase] GET error: " << curl_easy_strerror(res) << "\n"; + if (g_verbose) std::cerr << "[Supabase] GET error: " << curl_easy_strerror(res) << "\n"; } curl_slist_free_all(headers); diff --git a/src/network/supabase_client.h b/src/network/supabase_client.h index 82e9fde..93b5b22 100644 --- a/src/network/supabase_client.h +++ b/src/network/supabase_client.h @@ -11,4 +11,7 @@ void SubmitHighscoreAsync(const ScoreEntry &entry); // Fetch highscores for a game type. If gameType is empty, fetch all (limited). std::vector FetchHighscores(const std::string &gameType, int limit); +// Enable or disable verbose logging to stderr. Disabled by default. +void SetVerbose(bool enabled); + } // namespace supabase diff --git a/src/persistence/Scores.cpp b/src/persistence/Scores.cpp index 04602d9..5aa8229 100644 --- a/src/persistence/Scores.cpp +++ b/src/persistence/Scores.cpp @@ -27,7 +27,8 @@ void ScoreManager::load() { // Try to load from Supabase first try { - auto fetched = supabase::FetchHighscores("", static_cast(maxEntries)); + // Request only 10 records from Supabase to keep payload small + auto fetched = supabase::FetchHighscores("", 10); if (!fetched.empty()) { scores = fetched; std::sort(scores.begin(), scores.end(), [](auto&a,auto&b){return a.score>b.score;}); diff --git a/src/states/MenuState.cpp b/src/states/MenuState.cpp index c486b84..54f8b6d 100644 --- a/src/states/MenuState.cpp +++ b/src/states/MenuState.cpp @@ -174,9 +174,9 @@ void MenuState::onEnter() { try { std::thread([this]() { try { - auto c_classic = supabase::FetchHighscores("classic", 12); - auto c_coop = supabase::FetchHighscores("cooperate", 12); - auto c_challenge = supabase::FetchHighscores("challenge", 12); + auto c_classic = supabase::FetchHighscores("classic", 10); + auto c_coop = supabase::FetchHighscores("cooperate", 10); + auto c_challenge = supabase::FetchHighscores("challenge", 10); std::vector combined; combined.reserve(c_classic.size() + c_coop.size() + c_challenge.size()); combined.insert(combined.end(), c_classic.begin(), c_classic.end()); @@ -801,7 +801,9 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi // Move the whole block slightly up to better match the main screen overlay framing. float menuYOffset = LOGICAL_H * 0.03f; // same offset used for buttons float scoresYOffset = -LOGICAL_H * 0.05f; - float topPlayersY = LOGICAL_H * 0.20f + contentOffsetY - panelDelta + menuYOffset + scoresYOffset; + // Move logo and highscores upward by ~10% of logical height for better vertical balance + float upwardShift = LOGICAL_H * 0.08f; + float topPlayersY = LOGICAL_H * 0.20f + contentOffsetY - panelDelta + menuYOffset + scoresYOffset - upwardShift; float scoresStartY = topPlayersY; if (useFont) { // Preferred logo texture (full) if present, otherwise the small logo