fixed highscores

This commit is contained in:
2025-12-21 21:33:31 +01:00
parent fb82ac06d0
commit 70946fc720
5 changed files with 104 additions and 57 deletions

View File

@ -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}); pixelFont.draw(renderer, boxX + (boxW - sW) * 0.5f + contentOffsetX, boxY + 100 + contentOffsetY, scoreStr, 1.2f, {255, 255, 255, 255});
if (isNewHighScore) { 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); 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}); pixelFont.draw(renderer, boxX + (boxW - enW) * 0.5f + contentOffsetX, boxY + 160 + contentOffsetY, enterName, 1.0f, {200, 200, 220, 255});
float inputW = 300.0f; const float inputW = isCoopEntry ? 260.0f : 300.0f;
float inputH = 40.0f; const float inputH = 40.0f;
float inputX = boxX + (boxW - inputW) * 0.5f; const float inputX = boxX + (boxW - inputW) * 0.5f;
float inputY = boxY + 200.0f; const 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 nameScale = 1.2f; const float nameScale = 1.2f;
const bool showCursor = ((SDL_GetTicks() / 500) % 2) == 0; const bool showCursor = ((SDL_GetTicks() / 500) % 2) == 0;
@ -2077,34 +2071,67 @@ void TetrisApp::Impl::runLoop()
pixelFont.measure("A", nameScale, metricsW, metricsH); pixelFont.measure("A", nameScale, metricsW, metricsH);
if (metricsH == 0) metricsH = 24; if (metricsH == 0) metricsH = 24;
int nameW = 0, nameH = 0; // Single name entry (non-coop) --- keep original behavior
if (!playerName.empty()) { if (!isCoopEntry) {
pixelFont.measure(playerName, nameScale, nameW, nameH); 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<float>(nameW)) * 0.5f + contentOffsetX;
float textY = inputY + (inputH - static_cast<float>(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<float>(cursorW)) * 0.5f + contentOffsetX : textX + static_cast<float>(nameW);
float cursorY = inputY + (inputH - static_cast<float>(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 { } 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<float>(nameW)) * 0.5f + contentOffsetX;
float textY = inputY + (inputH - static_cast<float>(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<float>(cursorW)) * 0.5f + contentOffsetX : textX + static_cast<float>(nameW);
float cursorY = inputY + (inputH - static_cast<float>(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<float>(nameW)) * 0.5f + contentOffsetX;
float textY = inputY + (inputH - static_cast<float>(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<float>(cursorW)) * 0.5f + contentOffsetX
: textX + static_cast<float>(nameW);
float cursorY = inputY + (inputH - static_cast<float>(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 { } else {
char linesStr[64]; char linesStr[64];
snprintf(linesStr, sizeof(linesStr), "LINES: %d", game->lines()); snprintf(linesStr, sizeof(linesStr), "LINES: %d", game->lines());

View File

@ -3,6 +3,7 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <thread> #include <thread>
#include <iostream> #include <iostream>
#include <algorithm>
#include <cmath> #include <cmath>
using json = nlohmann::json; using json = nlohmann::json;
@ -35,6 +36,13 @@ static CurlInit g_curl_init;
namespace supabase { namespace supabase {
static bool g_verbose = false;
void SetVerbose(bool enabled) {
g_verbose = enabled;
}
void SubmitHighscoreAsync(const ScoreEntry &entry) { void SubmitHighscoreAsync(const ScoreEntry &entry) {
std::thread([entry]() { std::thread([entry]() {
try { try {
@ -68,18 +76,21 @@ void SubmitHighscoreAsync(const ScoreEntry &entry) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
// Debug: print outgoing request if (g_verbose) {
std::cerr << "[Supabase] POST " << url << "\n"; std::cerr << "[Supabase] POST " << url << "\n";
std::cerr << "[Supabase] Body: " << body << "\n"; std::cerr << "[Supabase] Body: " << body << "\n";
}
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) { 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 { } else {
long http_code = 0; long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
std::cerr << "[Supabase] POST response code: " << http_code << " body_len=" << resp.size() << "\n"; if (g_verbose) {
if (!resp.empty()) std::cerr << "[Supabase] POST response: " << resp << "\n"; 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); curl_slist_free_all(headers);
@ -97,15 +108,17 @@ std::vector<ScoreEntry> FetchHighscores(const std::string &gameType, int limit)
if (!curl) return out; if (!curl) return out;
std::string path = "highscores"; std::string path = "highscores";
// Clamp limit to max 10 to keep payloads small
int l = std::clamp(limit, 1, 10);
std::string query; std::string query;
if (!gameType.empty()) { if (!gameType.empty()) {
if (gameType == "challenge") { 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 { } 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 { } 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; std::string url = buildUrl(path) + query;
@ -123,15 +136,16 @@ std::vector<ScoreEntry> FetchHighscores(const std::string &gameType, int limit)
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
// Debug: print outgoing GET if (g_verbose) std::cerr << "[Supabase] GET " << url << "\n";
std::cerr << "[Supabase] GET " << url << "\n";
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK) { if (res == CURLE_OK) {
long http_code = 0; long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
std::cerr << "[Supabase] GET response code: " << http_code << " body_len=" << resp.size() << "\n"; if (g_verbose) {
if (!resp.empty()) std::cerr << "[Supabase] GET response: " << resp << "\n"; std::cerr << "[Supabase] GET response code: " << http_code << " body_len=" << resp.size() << "\n";
if (!resp.empty()) std::cerr << "[Supabase] GET response: " << resp << "\n";
}
try { try {
auto j = json::parse(resp); auto j = json::parse(resp);
if (j.is_array()) { if (j.is_array()) {
@ -151,10 +165,10 @@ std::vector<ScoreEntry> FetchHighscores(const std::string &gameType, int limit)
} }
} }
} catch (...) { } catch (...) {
std::cerr << "[Supabase] GET parse error" << std::endl; if (g_verbose) std::cerr << "[Supabase] GET parse error" << std::endl;
} }
} else { } 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); curl_slist_free_all(headers);

View File

@ -11,4 +11,7 @@ void SubmitHighscoreAsync(const ScoreEntry &entry);
// Fetch highscores for a game type. If gameType is empty, fetch all (limited). // Fetch highscores for a game type. If gameType is empty, fetch all (limited).
std::vector<ScoreEntry> FetchHighscores(const std::string &gameType, int limit); std::vector<ScoreEntry> FetchHighscores(const std::string &gameType, int limit);
// Enable or disable verbose logging to stderr. Disabled by default.
void SetVerbose(bool enabled);
} // namespace supabase } // namespace supabase

View File

@ -27,7 +27,8 @@ void ScoreManager::load() {
// Try to load from Supabase first // Try to load from Supabase first
try { try {
auto fetched = supabase::FetchHighscores("", static_cast<int>(maxEntries)); // Request only 10 records from Supabase to keep payload small
auto fetched = supabase::FetchHighscores("", 10);
if (!fetched.empty()) { if (!fetched.empty()) {
scores = fetched; scores = fetched;
std::sort(scores.begin(), scores.end(), [](auto&a,auto&b){return a.score>b.score;}); std::sort(scores.begin(), scores.end(), [](auto&a,auto&b){return a.score>b.score;});

View File

@ -174,9 +174,9 @@ void MenuState::onEnter() {
try { try {
std::thread([this]() { std::thread([this]() {
try { try {
auto c_classic = supabase::FetchHighscores("classic", 12); auto c_classic = supabase::FetchHighscores("classic", 10);
auto c_coop = supabase::FetchHighscores("cooperate", 12); auto c_coop = supabase::FetchHighscores("cooperate", 10);
auto c_challenge = supabase::FetchHighscores("challenge", 12); auto c_challenge = supabase::FetchHighscores("challenge", 10);
std::vector<ScoreEntry> combined; std::vector<ScoreEntry> combined;
combined.reserve(c_classic.size() + c_coop.size() + c_challenge.size()); combined.reserve(c_classic.size() + c_coop.size() + c_challenge.size());
combined.insert(combined.end(), c_classic.begin(), c_classic.end()); 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. // 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 menuYOffset = LOGICAL_H * 0.03f; // same offset used for buttons
float scoresYOffset = -LOGICAL_H * 0.05f; 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; float scoresStartY = topPlayersY;
if (useFont) { if (useFont) {
// Preferred logo texture (full) if present, otherwise the small logo // Preferred logo texture (full) if present, otherwise the small logo