fixed highscores
This commit is contained in:
@ -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());
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;});
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user