Merge branch 'feature/FixingGameplayKeys' into develop

This commit is contained in:
2025-08-16 19:40:49 +02:00
7 changed files with 212 additions and 3 deletions

11
build-debug-and-run.bat Normal file
View File

@ -0,0 +1,11 @@
@echo off
REM Build and run debug executable for the Tetris project
SETLOCAL
cd /d "%~dp0"
cmake --build build-msvc --config Debug
if errorlevel 1 (
echo Build failed.
exit /b %ERRORLEVEL%
)
"%~dp0build-msvc\Debug\tetris.exe"
ENDLOCAL

34
build-debug-and-run.ps1 Normal file
View File

@ -0,0 +1,34 @@
param(
[switch]$NoRun
)
# Ensure script runs from repo root (where this script lives)
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $root
Write-Host "Working directory: $PWD"
# Build Debug configuration
Write-Host "Running: cmake --build build-msvc --config Debug"
$proc = Start-Process -FilePath cmake -ArgumentList '--build','build-msvc','--config','Debug' -NoNewWindow -Wait -PassThru
if ($proc.ExitCode -ne 0) {
Write-Error "Build failed with exit code $($proc.ExitCode)"
exit $proc.ExitCode
}
if ($NoRun) {
Write-Host "Build succeeded; skipping run because -NoRun was specified."
exit 0
}
$exePath = Join-Path $root "build-msvc\Debug\tetris.exe"
if (-not (Test-Path $exePath)) {
Write-Error "Executable not found: $exePath"
exit 1
}
Write-Host "Launching: $exePath"
# Launch the executable and wait for it to exit so the caller sees its output.
& $exePath
exit $LASTEXITCODE

View File

@ -19,3 +19,18 @@ void FontAtlas::draw(SDL_Renderer* r, float x, float y, const std::string& text,
if (tex) { SDL_FRect dst{ x, y, (float)surf->w, (float)surf->h }; SDL_RenderTexture(r, tex, nullptr, &dst); SDL_DestroyTexture(tex); } if (tex) { SDL_FRect dst{ x, y, (float)surf->w, (float)surf->h }; SDL_RenderTexture(r, tex, nullptr, &dst); SDL_DestroyTexture(tex); }
SDL_DestroySurface(surf); SDL_DestroySurface(surf);
} }
void FontAtlas::measure(const std::string& text, float scale, int& outW, int& outH) {
outW = 0; outH = 0;
if (scale <= 0) return;
int pt = int(baseSize * scale);
if (pt < 1) pt = 1;
TTF_Font* f = getSized(pt);
if (!f) return;
// Use render-to-surface measurement to avoid dependency on specific TTF_* measurement API variants
SDL_Color dummy = {255,255,255,255};
SDL_Surface* surf = TTF_RenderText_Blended(f, text.c_str(), text.length(), dummy);
if (!surf) return;
outW = surf->w; outH = surf->h;
SDL_DestroySurface(surf);
}

View File

@ -10,6 +10,8 @@ public:
bool init(const std::string& path, int basePt); bool init(const std::string& path, int basePt);
void shutdown(); void shutdown();
void draw(SDL_Renderer* r, float x, float y, const std::string& text, float scale, SDL_Color color); void draw(SDL_Renderer* r, float x, float y, const std::string& text, float scale, SDL_Color color);
// Measure rendered text size in pixels for a given scale
void measure(const std::string& text, float scale, int& outW, int& outH);
private: private:
std::string fontPath; std::string fontPath;
int baseSize{24}; int baseSize{24};

View File

@ -71,6 +71,8 @@ static void drawRect(SDL_Renderer *r, float x, float y, float w, float h, SDL_Co
SDL_RenderFillRect(r, &fr); SDL_RenderFillRect(r, &fr);
} }
// ...existing code...
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Enhanced Button Drawing // Enhanced Button Drawing
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -340,6 +342,7 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi
static double logoAnimCounter = 0.0; static double logoAnimCounter = 0.0;
static bool showLevelPopup = false; static bool showLevelPopup = false;
static bool showSettingsPopup = false; static bool showSettingsPopup = false;
static bool showExitConfirmPopup = false;
static bool musicEnabled = true; static bool musicEnabled = true;
static int hoveredButton = -1; // -1 = none, 0 = play, 1 = level, 2 = settings static int hoveredButton = -1; // -1 = none, 0 = play, 1 = level, 2 = settings
@ -707,6 +710,8 @@ int main(int, char **)
// Prepare shared context for states // Prepare shared context for states
StateContext ctx{}; StateContext ctx{};
// Allow states to access the state manager for transitions
ctx.stateManager = &stateMgr;
ctx.game = &game; ctx.game = &game;
ctx.scores = &scores; ctx.scores = &scores;
ctx.starfield = &starfield; ctx.starfield = &starfield;
@ -725,6 +730,7 @@ int main(int, char **)
ctx.hoveredButton = &hoveredButton; ctx.hoveredButton = &hoveredButton;
ctx.showLevelPopup = &showLevelPopup; ctx.showLevelPopup = &showLevelPopup;
ctx.showSettingsPopup = &showSettingsPopup; ctx.showSettingsPopup = &showSettingsPopup;
ctx.showExitConfirmPopup = &showExitConfirmPopup;
// Instantiate state objects // Instantiate state objects
auto loadingState = std::make_unique<LoadingState>(ctx); auto loadingState = std::make_unique<LoadingState>(ctx);
@ -899,6 +905,48 @@ int main(int, char **)
state = AppState::Menu; state = AppState::Menu;
stateMgr.setState(state); stateMgr.setState(state);
} }
else if (state == AppState::Playing && showExitConfirmPopup) {
// Convert mouse to logical coordinates and to content-local coords
float lx = (mx - logicalVP.x) / logicalScale;
float ly = (my - logicalVP.y) / logicalScale;
// Compute content offsets (same as in render path)
float contentW = LOGICAL_W * logicalScale;
float contentH = LOGICAL_H * logicalScale;
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
// Map to content-local logical coords (what drawing code uses)
float localX = lx - contentOffsetX;
float localY = ly - contentOffsetY;
// Popup rect in logical coordinates (content-local)
float popupW = 420, popupH = 180;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
if (localX >= popupX && localX <= popupX + popupW && localY >= popupY && localY <= popupY + popupH) {
// Inside popup: two buttons Yes / No
float btnW = 140, btnH = 46;
float yesX = popupX + popupW * 0.25f - btnW/2.0f;
float noX = popupX + popupW * 0.75f - btnW/2.0f;
float btnY = popupY + popupH - 60;
if (localX >= yesX && localX <= yesX + btnW && localY >= btnY && localY <= btnY + btnH) {
// Yes -> go back to menu
showExitConfirmPopup = false;
game.reset(startLevelSelection);
state = AppState::Menu;
stateMgr.setState(state);
}
else if (localX >= noX && localX <= noX + btnW && localY >= btnY && localY <= btnY + btnH) {
// No -> close popup and resume
showExitConfirmPopup = false;
game.setPaused(false);
}
} else {
// Click outside popup: cancel
showExitConfirmPopup = false;
game.setPaused(false);
}
}
} }
} }
else if (e.type == SDL_EVENT_MOUSE_MOTION) else if (e.type == SDL_EVENT_MOUSE_MOTION)
@ -1608,8 +1656,8 @@ int main(int, char **)
drawSmallPiece(renderer, blocksTex, game.held().type, statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f); drawSmallPiece(renderer, blocksTex, game.held().type, statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
} }
// Pause overlay // Pause overlay: don't draw pause UI when the exit-confirm popup is showing
if (game.isPaused()) { if (game.isPaused() && !showExitConfirmPopup) {
// Semi-transparent overlay // Semi-transparent overlay
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
SDL_FRect pauseOverlay{0, 0, LOGICAL_W, LOGICAL_H}; SDL_FRect pauseOverlay{0, 0, LOGICAL_W, LOGICAL_H};
@ -1620,6 +1668,66 @@ int main(int, char **)
pixelFont.draw(renderer, LOGICAL_W * 0.5f - 120, LOGICAL_H * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255}); pixelFont.draw(renderer, LOGICAL_W * 0.5f - 120, LOGICAL_H * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255});
} }
// Exit confirmation popup (modal)
if (showExitConfirmPopup) {
// Compute content offsets for consistent placement across window sizes
float contentW = LOGICAL_W * logicalScale;
float contentH = LOGICAL_H * logicalScale;
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
float popupW = 420, popupH = 180;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
// Dim entire window (use window coordinates so it always covers 100% of the target)
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
SDL_FRect fullWin{0.f, 0.f, (float)winW, (float)winH};
SDL_RenderFillRect(renderer, &fullWin);
// Restore logical viewport for drawing content-local popup
SDL_SetRenderViewport(renderer, &logicalVP);
// Draw popup box (drawRect will apply contentOffset internally)
drawRect(popupX - 4, popupY - 4, popupW + 8, popupH + 8, {60, 70, 90, 255});
drawRect(popupX, popupY, popupW, popupH, {20, 22, 28, 240});
// Center title and body text inside popup (use pixelFont for retro P2 font)
const std::string title = "Exit game?";
const std::string line1 = "Are you sure you want to";
const std::string line2 = "leave the current game?";
int wTitle=0,hTitle=0; pixelFont.measure( title, 1.6f, wTitle, hTitle);
int wL1=0,hL1=0; pixelFont.measure( line1, 0.9f, wL1, hL1);
int wL2=0,hL2=0; pixelFont.measure( line2, 0.9f, wL2, hL2);
float titleX = popupX + (popupW - (float)wTitle) * 0.5f;
float l1X = popupX + (popupW - (float)wL1) * 0.5f;
float l2X = popupX + (popupW - (float)wL2) * 0.5f;
pixelFont.draw(renderer, titleX + contentOffsetX, popupY + contentOffsetY + 20, title, 1.6f, {255, 220, 0, 255});
pixelFont.draw(renderer, l1X + contentOffsetX, popupY + contentOffsetY + 60, line1, 0.9f, SDL_Color{220,220,230,255});
pixelFont.draw(renderer, l2X + contentOffsetX, popupY + contentOffsetY + 84, line2, 0.9f, SDL_Color{220,220,230,255});
// Buttons (center labels inside buttons) - use pixelFont for labels
float btnW = 140, btnH = 46;
float yesX = popupX + popupW * 0.25f - btnW/2.0f;
float noX = popupX + popupW * 0.75f - btnW/2.0f;
float btnY = popupY + popupH - 60;
drawRect(yesX - 2, btnY - 2, btnW + 4, btnH + 4, {100, 120, 140, 255});
drawRect(yesX, btnY, btnW, btnH, {200, 60, 60, 255});
const std::string yes = "YES";
int wy=0,hy=0; pixelFont.measure( yes, 1.0f, wy, hy);
pixelFont.draw(renderer, yesX + (btnW - (float)wy) * 0.5f + contentOffsetX, btnY + (btnH - (float)hy) * 0.5f + contentOffsetY, yes, 1.0f, {255,255,255,255});
drawRect(noX - 2, btnY - 2, btnW + 4, btnH + 4, {100, 120, 140, 255});
drawRect(noX, btnY, btnW, btnH, {80, 140, 80, 255});
const std::string no = "NO";
int wn=0,hn=0; pixelFont.measure( no, 1.0f, wn, hn);
pixelFont.draw(renderer, noX + (btnW - (float)wn) * 0.5f + contentOffsetX, btnY + (btnH - (float)hn) * 0.5f + contentOffsetY, no, 1.0f, {255,255,255,255});
}
// Controls hint at bottom // Controls hint at bottom
font.draw(renderer, 20, LOGICAL_H - 30, "ARROWS=Move Z/X=Rotate C=Hold SPACE=Drop P=Pause ESC=Menu", 1.0f, {150, 150, 170, 255}); font.draw(renderer, 20, LOGICAL_H - 30, "ARROWS=Move Z/X=Rotate C=Hold SPACE=Drop P=Pause ESC=Menu", 1.0f, {150, 150, 170, 255});
} }

View File

@ -1,4 +1,5 @@
#include "PlayingState.h" #include "PlayingState.h"
#include "core/StateManager.h"
#include "gameplay/Game.h" #include "gameplay/Game.h"
#include "gameplay/LineEffect.h" #include "gameplay/LineEffect.h"
#include "persistence/Scores.h" #include "persistence/Scores.h"
@ -14,14 +15,45 @@ void PlayingState::onExit() {
} }
void PlayingState::handleEvent(const SDL_Event& e) { void PlayingState::handleEvent(const SDL_Event& e) {
// We keep short-circuited input here; main still handles mouse UI // We keep short-circuited input here; main still owns mouse UI
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) { if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
if (!ctx.game) return; if (!ctx.game) return;
// Pause toggle (P) // Pause toggle (P)
if (e.key.scancode == SDL_SCANCODE_P) { if (e.key.scancode == SDL_SCANCODE_P) {
bool paused = ctx.game->isPaused(); bool paused = ctx.game->isPaused();
ctx.game->setPaused(!paused); ctx.game->setPaused(!paused);
return;
} }
// If exit-confirm popup is visible, handle shortcuts here
if (ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup) {
// Confirm with Enter (main or keypad)
if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
*ctx.showExitConfirmPopup = false;
// Reset game and return to menu
ctx.game->reset(false);
if (ctx.stateManager) ctx.stateManager->setState(AppState::Menu);
return;
}
// Cancel with Esc
if (e.key.scancode == SDL_SCANCODE_ESCAPE) {
*ctx.showExitConfirmPopup = false;
ctx.game->setPaused(false);
return;
}
// While modal is open, suppress other gameplay keys
return;
}
// ESC key - open confirmation popup
if (e.key.scancode == SDL_SCANCODE_ESCAPE) {
if (ctx.showExitConfirmPopup) {
if (ctx.game) ctx.game->setPaused(true);
*ctx.showExitConfirmPopup = true;
}
return;
}
// Other gameplay keys already registered by main's Playing handler for now // Other gameplay keys already registered by main's Playing handler for now
} }
} }

View File

@ -12,6 +12,10 @@ class Starfield3D;
class FontAtlas; class FontAtlas;
class LineEffect; class LineEffect;
// Forward declare StateManager so StateContext can hold a pointer without
// including the StateManager header here.
class StateManager;
// Shared context passed to states so they can access common resources // Shared context passed to states so they can access common resources
struct StateContext { struct StateContext {
// Core subsystems (may be null if not available) // Core subsystems (may be null if not available)
@ -41,6 +45,9 @@ struct StateContext {
// Menu popups (exposed from main) // Menu popups (exposed from main)
bool* showLevelPopup = nullptr; bool* showLevelPopup = nullptr;
bool* showSettingsPopup = nullptr; bool* showSettingsPopup = nullptr;
bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing
// Pointer to the application's StateManager so states can request transitions
StateManager* stateManager = nullptr;
}; };
class State { class State {