fixed level selector

This commit is contained in:
2025-08-16 21:58:19 +02:00
parent 5ca03fb689
commit eddd1a24b2
5 changed files with 163 additions and 74 deletions

View File

@ -9,6 +9,12 @@ elseif(EXISTS "C:/Users/Gregor Klevže/vcpkg/scripts/buildsystems/vcpkg.cmake")
set(CMAKE_TOOLCHAIN_FILE "C:/Users/Gregor Klevže/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") set(CMAKE_TOOLCHAIN_FILE "C:/Users/Gregor Klevže/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "")
endif() endif()
# The toolchain file must be set before calling project()
if(DEFINED CMAKE_TOOLCHAIN_FILE)
message(STATUS "Using vcpkg toolchain: ${CMAKE_TOOLCHAIN_FILE}")
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" CACHE STRING "" FORCE)
endif()
project(tetris_sdl3 LANGUAGES CXX) project(tetris_sdl3 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)

View File

@ -1,22 +1,27 @@
# I-Piece Spawn Position Fix # I-Piece Spawn Position Fix
## Overview ## Overview
Fixed the I-piece (straight line tetromino) to spawn one row higher than other pieces, addressing the issue where the I-piece only occupied one line when spawning vertically, making its entry appear less natural. Fixed the I-piece (straight line tetromino) to spawn one row higher than other pieces, addressing the issue where the I-piece only occupied one line when spawning vertically, making its entry appear less natural.
## Problem Analysis ## Problem Analysis
### Issue Identified ### Issue Identified
The I-piece has unique spawn behavior because: The I-piece has unique spawn behavior because:
- **Vertical I-piece**: When spawning in vertical orientation (rotation 0), it only occupies one row - **Vertical I-piece**: When spawning in vertical orientation (rotation 0), it only occupies one row
- **Visual Impact**: This made the I-piece appear to "pop in" at the top grid line rather than naturally entering - **Visual Impact**: This made the I-piece appear to "pop in" at the top grid line rather than naturally entering
- **Player Experience**: Less time to react and plan placement compared to other pieces - **Player Experience**: Less time to react and plan placement compared to other pieces
### Other Pieces ### Other Pieces
All other pieces (O, T, S, Z, J, L) were working perfectly at their current spawn position (`y = -1`) and should remain unchanged. All other pieces (O, T, S, Z, J, L) were working perfectly at their current spawn position (`y = -1`) and should remain unchanged.
## Solution Implemented ## Solution Implemented
### Targeted I-Piece Fix ### Targeted I-Piece Fix
Instead of changing all pieces, implemented piece-type-specific spawn logic: Instead of changing all pieces, implemented piece-type-specific spawn logic:
```cpp ```cpp
@ -26,7 +31,8 @@ int spawnY = (pieceType == I) ? -2 : -1;
### Code Changes ### Code Changes
#### 1. Updated spawn() function: #### 1. Updated spawn() function
```cpp ```cpp
void Game::spawn() { void Game::spawn() {
if (bag.empty()) refillBag(); if (bag.empty()) refillBag();
@ -48,7 +54,8 @@ void Game::spawn() {
} }
``` ```
#### 2. Updated holdCurrent() function: #### 2. Updated holdCurrent() function
```cpp ```cpp
// Apply I-piece specific positioning in hold mechanics // Apply I-piece specific positioning in hold mechanics
int holdSpawnY = (hold.type == I) ? -2 : -1; int holdSpawnY = (hold.type == I) ? -2 : -1;
@ -58,23 +65,26 @@ int currentSpawnY = (temp.type == I) ? -2 : -1;
## Technical Benefits ## Technical Benefits
### 1. Piece-Specific Optimization ### 1. Piece-Specific Optimization
- **I-Piece**: Spawns at `y = -2` for natural vertical entry - **I-Piece**: Spawns at `y = -2` for natural vertical entry
- **Other Pieces**: Remain at `y = -1` for optimal gameplay feel - **Other Pieces**: Remain at `y = -1` for optimal gameplay feel
- **Consistency**: Each piece type gets appropriate spawn positioning - **Consistency**: Each piece type gets appropriate spawn positioning
### 2. Enhanced Gameplay ### 2. Enhanced Gameplay
- **I-Piece Visibility**: More natural entry animation and player reaction time - **I-Piece Visibility**: More natural entry animation and player reaction time
- **Preserved Balance**: Other pieces maintain their optimized spawn positions - **Preserved Balance**: Other pieces maintain their optimized spawn positions
- **Strategic Depth**: I-piece placement becomes more strategic with better entry timing - **Strategic Depth**: I-piece placement becomes more strategic with better entry timing
### 3. Code Quality ### 3. Code Quality
- **Targeted Solution**: Minimal code changes addressing specific issue - **Targeted Solution**: Minimal code changes addressing specific issue
- **Maintainable Logic**: Clear piece-type conditionals - **Maintainable Logic**: Clear piece-type conditionals
- **Extensible Design**: Easy to adjust other pieces if needed in future - **Extensible Design**: Easy to adjust other pieces if needed in future
## Spawn Position Matrix ## Spawn Position Matrix
``` ```text
Piece Type | Spawn Y | Reasoning Piece Type | Spawn Y | Reasoning
-----------|---------|------------------------------------------ -----------|---------|------------------------------------------
I-piece | -2 | Vertical orientation needs extra height I-piece | -2 | Vertical orientation needs extra height
@ -88,8 +98,9 @@ L-piece | -1 | Optimal L-shape entry
## Visual Comparison ## Visual Comparison
### I-Piece Spawn Behavior: ### I-Piece Spawn Behavior
```
```text
BEFORE (y = -1): AFTER (y = -2): BEFORE (y = -1): AFTER (y = -2):
[ ] [ ] [ ] [ ]
[ █ ] ← I-piece here [ ] [ █ ] ← I-piece here [ ]
@ -99,8 +110,9 @@ BEFORE (y = -1): AFTER (y = -2):
Problem: Abrupt entry Solution: Natural entry Problem: Abrupt entry Solution: Natural entry
``` ```
### Other Pieces (Unchanged): ### Other Pieces (Unchanged)
```
```text
T-Piece Example (y = -1): T-Piece Example (y = -1):
[ ] [ ]
[ ███ ] ← T-piece entry (perfect) [ ███ ] ← T-piece entry (perfect)
@ -113,18 +125,21 @@ Status: No change needed ✅
## Testing Results ## Testing Results
### 1. I-Piece Verification ### 1. I-Piece Verification
**Vertical Spawn**: I-piece now appears naturally from above **Vertical Spawn**: I-piece now appears naturally from above
**Entry Animation**: Smooth transition into visible grid area **Entry Animation**: Smooth transition into visible grid area
**Player Reaction**: More time to plan I-piece placement **Player Reaction**: More time to plan I-piece placement
**Hold Function**: I-piece maintains correct positioning when held/swapped **Hold Function**: I-piece maintains correct positioning when held/swapped
### 2. Other Pieces Validation ### 2. Other Pieces Validation
**O-Piece**: Maintains perfect 2x2 positioning **O-Piece**: Maintains perfect 2x2 positioning
**T-Piece**: Optimal T-shape entry preserved **T-Piece**: Optimal T-shape entry preserved
**S/Z-Pieces**: Natural zigzag entry unchanged **S/Z-Pieces**: Natural zigzag entry unchanged
**J/L-Pieces**: L-shape entry timing maintained **J/L-Pieces**: L-shape entry timing maintained
### 3. Game Mechanics ### 3. Game Mechanics
**Spawn Consistency**: Each piece type uses appropriate spawn height **Spawn Consistency**: Each piece type uses appropriate spawn height
**Hold System**: Piece-specific positioning applied correctly **Hold System**: Piece-specific positioning applied correctly
**Bag Randomizer**: Next piece preview uses correct spawn height **Bag Randomizer**: Next piece preview uses correct spawn height
@ -133,16 +148,19 @@ Status: No change needed ✅
## Benefits Summary ## Benefits Summary
### 1. Improved I-Piece Experience ### 1. Improved I-Piece Experience
- **Natural Entry**: I-piece now enters the play area smoothly - **Natural Entry**: I-piece now enters the play area smoothly
- **Better Timing**: More reaction time for strategic placement - **Better Timing**: More reaction time for strategic placement
- **Visual Polish**: Professional appearance matching commercial Tetris games - **Visual Polish**: Professional appearance matching commercial Tetris games
### 2. Preserved Gameplay Balance ### 2. Preserved Gameplay Balance
- **Other Pieces Unchanged**: Maintain optimal spawn positions for 6 other piece types - **Other Pieces Unchanged**: Maintain optimal spawn positions for 6 other piece types
- **Consistent Feel**: Each piece type gets appropriate treatment - **Consistent Feel**: Each piece type gets appropriate treatment
- **Strategic Depth**: I-piece becomes more strategic without affecting other pieces - **Strategic Depth**: I-piece becomes more strategic without affecting other pieces
### 3. Technical Excellence ### 3. Technical Excellence
- **Minimal Changes**: Targeted fix without broad system changes - **Minimal Changes**: Targeted fix without broad system changes
- **Future-Proof**: Easy to adjust individual piece spawn behavior - **Future-Proof**: Easy to adjust individual piece spawn behavior
- **Code Quality**: Clear, maintainable piece-type logic - **Code Quality**: Clear, maintainable piece-type logic

View File

@ -1,14 +1,17 @@
# Gameplay Layout Improvements - Enhancement Report # Gameplay Layout Improvements - Enhancement Report
## Overview ## Overview
Enhanced the gameplay state visual layout by repositioning the next piece preview higher above the main grid and adding subtle grid lines to improve gameplay visibility. Enhanced the gameplay state visual layout by repositioning the next piece preview higher above the main grid and adding subtle grid lines to improve gameplay visibility.
## Changes Made ## Changes Made
### 1. Next Piece Preview Repositioning ### 1. Next Piece Preview Repositioning
**Problem**: The next piece preview was positioned too close to the main game grid, causing visual overlap and crowded appearance. **Problem**: The next piece preview was positioned too close to the main game grid, causing visual overlap and crowded appearance.
**Solution**: **Solution**:
```cpp ```cpp
// BEFORE: Limited space for next piece // BEFORE: Limited space for next piece
const float NEXT_PIECE_HEIGHT = 80.0f; // Space reserved for next piece preview const float NEXT_PIECE_HEIGHT = 80.0f; // Space reserved for next piece preview
@ -17,15 +20,18 @@ const float NEXT_PIECE_HEIGHT = 80.0f; // Space reserved for next piece preview
const float NEXT_PIECE_HEIGHT = 120.0f; // Space reserved for next piece preview (increased) const float NEXT_PIECE_HEIGHT = 120.0f; // Space reserved for next piece preview (increased)
``` ```
**Result**: ### Result
-**50% more space** above the main grid (80px → 120px) -**50% more space** above the main grid (80px → 120px)
-**Clear separation** between next piece and main game area -**Clear separation** between next piece and main game area
-**Better visual hierarchy** in the gameplay layout -**Better visual hierarchy** in the gameplay layout
### 2. Main Grid Visual Enhancement ### 2. Main Grid Visual Enhancement
**Problem**: The main game grid lacked visual cell boundaries, making it difficult to precisely judge piece placement. **Problem**: The main game grid lacked visual cell boundaries, making it difficult to precisely judge piece placement.
**Solution**: Added subtle grid lines to show cell boundaries **Solution**: Added subtle grid lines to show cell boundaries
```cpp ```cpp
// Draw grid lines (subtle lines to show cell boundaries) // Draw grid lines (subtle lines to show cell boundaries)
SDL_SetRenderDrawColor(renderer, 40, 45, 60, 255); // Slightly lighter than background SDL_SetRenderDrawColor(renderer, 40, 45, 60, 255); // Slightly lighter than background
@ -43,7 +49,8 @@ for (int y = 1; y < Game::ROWS; ++y) {
} }
``` ```
**Grid Line Properties**: ### Grid Line Properties
- **Color**: `RGB(40, 45, 60)` - Barely visible, subtle enhancement - **Color**: `RGB(40, 45, 60)` - Barely visible, subtle enhancement
- **Coverage**: Complete 10×20 grid with proper cell boundaries - **Coverage**: Complete 10×20 grid with proper cell boundaries
- **Performance**: Minimal overhead with simple line drawing - **Performance**: Minimal overhead with simple line drawing
@ -52,6 +59,7 @@ for (int y = 1; y < Game::ROWS; ++y) {
## Technical Details ## Technical Details
### Layout Calculation System ### Layout Calculation System
The responsive layout system maintains perfect centering while accommodating the increased next piece space: The responsive layout system maintains perfect centering while accommodating the increased next piece space:
```cpp ```cpp
@ -64,6 +72,7 @@ The responsive layout system maintains perfect centering while accommodating the
``` ```
### Grid Line Implementation ### Grid Line Implementation
- **Rendering Order**: Background → Grid Lines → Game Blocks → UI Elements - **Rendering Order**: Background → Grid Lines → Game Blocks → UI Elements
- **Line Style**: Single pixel lines with subtle contrast - **Line Style**: Single pixel lines with subtle contrast
- **Integration**: Seamlessly integrated with existing rendering pipeline - **Integration**: Seamlessly integrated with existing rendering pipeline
@ -72,24 +81,28 @@ The responsive layout system maintains perfect centering while accommodating the
## Visual Benefits ## Visual Benefits
### 1. Improved Gameplay Precision ### 1. Improved Gameplay Precision
- **Grid Boundaries**: Clear visual cell separation for accurate piece placement - **Grid Boundaries**: Clear visual cell separation for accurate piece placement
- **Alignment Reference**: Easy to judge piece positioning and rotation - **Alignment Reference**: Easy to judge piece positioning and rotation
- **Professional Appearance**: Matches modern Tetris game standards - **Professional Appearance**: Matches modern Tetris game standards
### 2. Enhanced Layout Flow ### 2. Enhanced Layout Flow
- **Next Piece Visibility**: Higher positioning prevents overlap with main game - **Next Piece Visibility**: Higher positioning prevents overlap with main game
- **Visual Balance**: Better proportions between UI elements - **Visual Balance**: Better proportions between UI elements
- **Breathing Room**: More comfortable spacing throughout the interface - **Breathing Room**: More comfortable spacing throughout the interface
### 3. Accessibility Improvements ### 3. Accessibility Improvements
- **Visual Clarity**: Subtle grid helps players with visual impairments - **Visual Clarity**: Subtle grid helps players with visual impairments
- **Reduced Eye Strain**: Better element separation reduces visual confusion - **Reduced Eye Strain**: Better element separation reduces visual confusion
- **Gameplay Flow**: Smoother visual transitions between game areas - **Gameplay Flow**: Smoother visual transitions between game areas
## Comparison: Before vs After ## Comparison: Before vs After
### Next Piece Positioning: ### Next Piece Positioning
```
```text
BEFORE: [Next Piece] BEFORE: [Next Piece]
[Main Grid ] ← Too close, visual overlap [Main Grid ] ← Too close, visual overlap
@ -98,8 +111,9 @@ AFTER: [Next Piece]
[Main Grid ] ← Proper separation, clear hierarchy [Main Grid ] ← Proper separation, clear hierarchy
``` ```
### Grid Visibility: ### Grid Visibility
```
```text
BEFORE: Solid background, no cell boundaries BEFORE: Solid background, no cell boundaries
■■■■■■■■■■ ■■■■■■■■■■
■■■■■■■■■■ ← Difficult to judge placement ■■■■■■■■■■ ← Difficult to judge placement
@ -111,6 +125,7 @@ AFTER: Subtle grid lines show cell boundaries
``` ```
## Testing Results ## Testing Results
**Build Success**: Clean compilation with no errors **Build Success**: Clean compilation with no errors
**Visual Layout**: Next piece properly positioned above grid **Visual Layout**: Next piece properly positioned above grid
**Grid Lines**: Subtle, barely visible lines enhance gameplay **Grid Lines**: Subtle, barely visible lines enhance gameplay
@ -121,24 +136,29 @@ AFTER: Subtle grid lines show cell boundaries
## User Experience Impact ## User Experience Impact
### 1. Gameplay Improvements ### 1. Gameplay Improvements
- **Precision**: Easier to place pieces exactly where intended - **Precision**: Easier to place pieces exactly where intended
- **Speed**: Faster visual assessment of game state - **Speed**: Faster visual assessment of game state
- **Confidence**: Clear visual references reduce placement errors - **Confidence**: Clear visual references reduce placement errors
### 2. Visual Polish ### 2. Visual Polish
- **Professional**: Matches commercial Tetris game standards - **Professional**: Matches commercial Tetris game standards
- **Modern**: Clean, organized interface layout - **Modern**: Clean, organized interface layout
- **Consistent**: Maintains established visual design language - **Consistent**: Maintains established visual design language
### 3. Accessibility ### 3. Accessibility
- **Clarity**: Better visual separation for all users - **Clarity**: Better visual separation for all users
- **Readability**: Improved layout hierarchy - **Readability**: Improved layout hierarchy
- **Comfort**: Reduced visual strain during extended play - **Comfort**: Reduced visual strain during extended play
## Conclusion ## Conclusion
The layout improvements successfully address the visual overlap issue with the next piece preview and add essential grid lines for better gameplay precision. The changes maintain the responsive design system while providing a more polished and professional gaming experience. The layout improvements successfully address the visual overlap issue with the next piece preview and add essential grid lines for better gameplay precision. The changes maintain the responsive design system while providing a more polished and professional gaming experience.
## Status: ✅ COMPLETED ## Status: ✅ COMPLETED
- Next piece repositioned higher above main grid - Next piece repositioned higher above main grid
- Subtle grid lines added to main game board - Subtle grid lines added to main game board
- Layout maintains responsive design and perfect centering - Layout maintains responsive design and perfect centering

View File

@ -3,10 +3,12 @@
This document lists recommended code, architecture, tooling, and runtime upgrades for the native SDL3 Tetris project. Items are grouped, prioritized, and mapped to target files and effort estimates so you can plan incremental work. This document lists recommended code, architecture, tooling, and runtime upgrades for the native SDL3 Tetris project. Items are grouped, prioritized, and mapped to target files and effort estimates so you can plan incremental work.
## Short plan ## Short plan
- Audit surface area and break tasks into small, testable PRs. - Audit surface area and break tasks into small, testable PRs.
- Start with low-risk safety and build improvements, then refactors (Gravity/Scoring/Board), then tests/CI, then UX polish and optional features. - Start with low-risk safety and build improvements, then refactors (Gravity/Scoring/Board), then tests/CI, then UX polish and optional features.
## Checklist (high level) ## Checklist (high level)
- [x] Make NES gravity table constant (constexpr) and move per-level multipliers out of a mutable global - [x] Make NES gravity table constant (constexpr) and move per-level multipliers out of a mutable global
- Note: FRAMES_TABLE is now immutable inside `GravityManager`; per-level multipliers are instance-scoped in `GravityManager`. - Note: FRAMES_TABLE is now immutable inside `GravityManager`; per-level multipliers are instance-scoped in `GravityManager`.
- [x] Extract GravityManager (SRP) - [x] Extract GravityManager (SRP)
@ -27,6 +29,7 @@ This document lists recommended code, architecture, tooling, and runtime upgrade
--- ---
## Rationale & Goals ## Rationale & Goals
- Improve maintainability: split `Game` responsibilities into focused classes (SRP) so logic is testable. - Improve maintainability: split `Game` responsibilities into focused classes (SRP) so logic is testable.
- Improve safety: remove mutable shared state (globals/`extern`) and minimize hidden side-effects. - Improve safety: remove mutable shared state (globals/`extern`) and minimize hidden side-effects.
- Improve tuning: per-level and global gravity tuning must be instance-local and debug-friendly. - Improve tuning: per-level and global gravity tuning must be instance-local and debug-friendly.
@ -37,6 +40,7 @@ This document lists recommended code, architecture, tooling, and runtime upgrade
## Detailed tasks (prioritized) ## Detailed tasks (prioritized)
### 1) Safety & small win (Low risk — 13 hours) ### 1) Safety & small win (Low risk — 13 hours)
- Make the NES frames-per-cell table `constexpr` and immutable. - Make the NES frames-per-cell table `constexpr` and immutable.
- File: `src/Game.cpp` (reverse any mutable anonymous namespace table changes) - File: `src/Game.cpp` (reverse any mutable anonymous namespace table changes)
- Why: avoids accidental global mutation; makes compute deterministic. - Why: avoids accidental global mutation; makes compute deterministic.
@ -48,18 +52,21 @@ This document lists recommended code, architecture, tooling, and runtime upgrade
Deliverable: small patch to `Game.h`/`Game.cpp` and a rebuild verification. Deliverable: small patch to `Game.h`/`Game.cpp` and a rebuild verification.
### 2) Replace ad-hoc logging (Low risk — 0.51 hour) ### 2) Replace ad-hoc logging (Low risk — 0.51 hour)
- Replace `printf` debug prints with `SDL_Log` or an injected `Logger` interface. - Replace `printf` debug prints with `SDL_Log` or an injected `Logger` interface.
- Prefer a build-time `#ifdef DEBUG` or runtime verbosity flag so release builds are quiet. - Prefer a build-time `#ifdef DEBUG` or runtime verbosity flag so release builds are quiet.
Files: `src/Game.cpp`, any file using printf for debug. Files: `src/Game.cpp`, any file using printf for debug.
### 3) Remove fragile globals / externs (Low risk — 12 hours) ### 3) Remove fragile globals / externs (Low risk — 12 hours)
- Ensure all textures, fonts and shared resources are passed via `StateContext& ctx`. - Ensure all textures, fonts and shared resources are passed via `StateContext& ctx`.
- Remove leftover `extern SDL_Texture* backgroundTex` or similar; make call-sites accept `SDL_Texture*` or read from `ctx`. - Remove leftover `extern SDL_Texture* backgroundTex` or similar; make call-sites accept `SDL_Texture*` or read from `ctx`.
Files: `src/main.cpp`, `src/states/MenuState.cpp`, other states. Files: `src/main.cpp`, `src/states/MenuState.cpp`, other states.
### 4) Extract GravityManager (Medium risk — 26 hours) ### 4) Extract GravityManager (Medium risk — 26 hours)
- Create `src/core/GravityManager.h|cpp` that encapsulates - Create `src/core/GravityManager.h|cpp` that encapsulates
- the `constexpr` NES frames table (read-only) - the `constexpr` NES frames table (read-only)
- per-instance multipliers - per-instance multipliers
@ -69,6 +76,7 @@ Files: `src/main.cpp`, `src/states/MenuState.cpp`, other states.
Benefits: easier testing and isolated behavior change for future gravity models. Benefits: easier testing and isolated behavior change for future gravity models.
### 5) Extract Board/Scoring responsibilities (Medium — 48 hours) ### 5) Extract Board/Scoring responsibilities (Medium — 48 hours)
- Split `Game` into smaller collaborators: - Split `Game` into smaller collaborators:
- `Board` — raw grid, collision, line detection/clear operations - `Board` — raw grid, collision, line detection/clear operations
- `Scorer` — scoring rules, combo handling, level progression thresholds - `Scorer` — scoring rules, combo handling, level progression thresholds
@ -78,6 +86,7 @@ Benefits: easier testing and isolated behavior change for future gravity models.
Files: `src/Board.*`, `src/Scorer.*`, `src/PieceController.*`, adjust `Game` to compose them. Files: `src/Board.*`, `src/Scorer.*`, `src/PieceController.*`, adjust `Game` to compose them.
### 6) Unit tests & test infrastructure (Medium — 36 hours) ### 6) Unit tests & test infrastructure (Medium — 36 hours)
- Add Catch2 or GoogleTest to `vcpkg.json` or as `FetchContent` in CMake. - Add Catch2 or GoogleTest to `vcpkg.json` or as `FetchContent` in CMake.
- Add tests: - Add tests:
- Gravity conversion tests (frames→ms, per-level multipliers, global multiplier) - Gravity conversion tests (frames→ms, per-level multipliers, global multiplier)
@ -86,6 +95,7 @@ Files: `src/Board.*`, `src/Scorer.*`, `src/PieceController.*`, adjust `Game` to
- Add a `tests/` CMake target and basic CI integration (see next section). - Add a `tests/` CMake target and basic CI integration (see next section).
### 7) CI / Build checks (Medium — 24 hours) ### 7) CI / Build checks (Medium — 24 hours)
- Add GitHub Actions to build Debug/Release on Windows and run tests. - Add GitHub Actions to build Debug/Release on Windows and run tests.
- Add static analysis: clang-tidy/clang-format or cpplint configured for the project style. - Add static analysis: clang-tidy/clang-format or cpplint configured for the project style.
- Add a Pre-commit hook to run format checks. - Add a Pre-commit hook to run format checks.
@ -93,6 +103,7 @@ Files: `src/Board.*`, `src/Scorer.*`, `src/PieceController.*`, adjust `Game` to
Files: `.github/workflows/build.yml`, `.clang-format`, optional `clang-tidy` config. Files: `.github/workflows/build.yml`, `.clang-format`, optional `clang-tidy` config.
### 8) UX and input correctness (LowMedium — 13 hours) ### 8) UX and input correctness (LowMedium — 13 hours)
- Update level popup hit-testing to use the same computed button rectangles used for drawing. - Update level popup hit-testing to use the same computed button rectangles used for drawing.
- Expose a function that returns vector<SDL_FRect> of button bounds from the popup draw logic. - Expose a function that returns vector<SDL_FRect> of button bounds from the popup draw logic.
- Ensure popup background texture is stretched to the logical viewport and that overlay alpha is constant across window sizes. - Ensure popup background texture is stretched to the logical viewport and that overlay alpha is constant across window sizes.
@ -101,6 +112,7 @@ Files: `.github/workflows/build.yml`, `.clang-format`, optional `clang-tidy` con
Files: `src/main.cpp`, `src/states/MenuState.cpp`. Files: `src/main.cpp`, `src/states/MenuState.cpp`.
### 9) Runtime debug knobs (Low — 1 hour) ### 9) Runtime debug knobs (Low — 1 hour)
- Add keys to increase/decrease `gravityGlobalMultiplier` (e.g., `[` and `]`) and reset to 1.0. - Add keys to increase/decrease `gravityGlobalMultiplier` (e.g., `[` and `]`) and reset to 1.0.
- Show `gravityGlobalMultiplier` and per-level effective ms on HUD (already partly implemented). - Show `gravityGlobalMultiplier` and per-level effective ms on HUD (already partly implemented).
- Persist tuning to a small JSON file `settings/debug_tuning.json` (optional). - Persist tuning to a small JSON file `settings/debug_tuning.json` (optional).
@ -108,6 +120,7 @@ Files: `src/main.cpp`, `src/states/MenuState.cpp`.
Files: `src/Game.h/cpp`, `src/main.cpp` HUD code. Files: `src/Game.h/cpp`, `src/main.cpp` HUD code.
### 10) Defensive & correctness improvements (Low — 24 hours) ### 10) Defensive & correctness improvements (Low — 24 hours)
- Add null checks for SDL_CreateTextureFromSurface and related APIs; log and fallback gracefully. - Add null checks for SDL_CreateTextureFromSurface and related APIs; log and fallback gracefully.
- Convert raw SDL_Texture* ownership to `std::unique_ptr` with custom deleter where appropriate, or centralize lifetime in a `ResourceManager`. - Convert raw SDL_Texture* ownership to `std::unique_ptr` with custom deleter where appropriate, or centralize lifetime in a `ResourceManager`.
- Add `const` qualifiers to getters where possible. - Add `const` qualifiers to getters where possible.
@ -116,6 +129,7 @@ Files: `src/Game.h/cpp`, `src/main.cpp` HUD code.
Files: various (`src/*.cpp`, `src/*.h`). Files: various (`src/*.cpp`, `src/*.h`).
### 11) Packaging & build improvements (Low — 12 hours) ### 11) Packaging & build improvements (Low — 12 hours)
- Verify `build-production.ps1` copies all required DLLs for SDL3 and SDL3_ttf from `vcpkg_installed` paths. - Verify `build-production.ps1` copies all required DLLs for SDL3 and SDL3_ttf from `vcpkg_installed` paths.
- Add an automated packaging job to CI that creates a ZIP artifact on release tags. - Add an automated packaging job to CI that creates a ZIP artifact on release tags.
@ -124,6 +138,7 @@ Files: `build-production.ps1`, `.github/workflows/release.yml`.
--- ---
## Suggested incremental PR plan ## Suggested incremental PR plan
1. Small safety PR: make frames table `constexpr`, move multipliers into `Game` instance, clamp values, and add SDL_Log usage. (13 hours) 1. Small safety PR: make frames table `constexpr`, move multipliers into `Game` instance, clamp values, and add SDL_Log usage. (13 hours)
2. Global cleanup PR: remove `extern` textures and ensure all resource access goes through `StateContext`. (12 hours) 2. Global cleanup PR: remove `extern` textures and ensure all resource access goes through `StateContext`. (12 hours)
3. Add debug knobs & HUD display improvements. (1 hour) 3. Add debug knobs & HUD display improvements. (1 hour)
@ -135,6 +150,7 @@ Files: `build-production.ps1`, `.github/workflows/release.yml`.
--- ---
## Recommended quick wins (apply immediately) ## Recommended quick wins (apply immediately)
- Convert NES frames table to `constexpr` and clamp gravity to >= 1ms. - Convert NES frames table to `constexpr` and clamp gravity to >= 1ms.
- Replace `printf` with `SDL_Log` for debug output. - Replace `printf` with `SDL_Log` for debug output.
- Pass `SDL_Texture*` via `StateContext` instead of `extern` globals (you already did this; check for leftovers with `grep` for "extern .*backgroundTex"). - Pass `SDL_Texture*` via `StateContext` instead of `extern` globals (you already did this; check for leftovers with `grep` for "extern .*backgroundTex").
@ -143,6 +159,7 @@ Files: `build-production.ps1`, `.github/workflows/release.yml`.
--- ---
## Example: gravity computation (reference) ## Example: gravity computation (reference)
```cpp ```cpp
// gravity constants // gravity constants
constexpr double NES_FPS = 60.0988; constexpr double NES_FPS = 60.0988;
@ -162,9 +179,11 @@ double GravityManager::msForLevel(int level) const {
--- ---
## Estimated total effort ## Estimated total effort
- Conservative: 1636 hours depending on how far you split `Game` and add tests/CI. - Conservative: 1636 hours depending on how far you split `Game` and add tests/CI.
## Next steps I can take for you now ## Next steps I can take for you now
- Create a PR that converts the frames table to `constexpr` and moves multipliers into `Game` instance (small patch + build). (I can do this.) - Create a PR that converts the frames table to `constexpr` and moves multipliers into `Game` instance (small patch + build). (I can do this.)
- Add a unit-test harness using Catch2 and a small GravityManager test. - Add a unit-test harness using Catch2 and a small GravityManager test.

View File

@ -71,6 +71,9 @@ static void drawRect(SDL_Renderer *r, float x, float y, float w, float h, SDL_Co
SDL_RenderFillRect(r, &fr); SDL_RenderFillRect(r, &fr);
} }
// Hover state for level popup ( -1 = none, 0..19 = hovered level )
static int hoveredLevel = -1;
// ...existing code... // ...existing code...
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -229,10 +232,9 @@ static void drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, Piece
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static void drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel) { static void drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel) {
// Popup dims scale with logical size for responsiveness // Popup dims scale with logical size for responsiveness
float popupW = std::min(760.0f, LOGICAL_W * 0.75f); float popupW = 400, popupH = 300;
float popupH = std::min(520.0f, LOGICAL_H * 0.7f); float popupX = (LOGICAL_W - popupW) / 2;
float popupX = (LOGICAL_W - popupW) / 2.0f; float popupY = (LOGICAL_H - popupH) / 2;
float popupY = (LOGICAL_H - popupH) / 2.0f;
// Draw the background picture stretched to full logical viewport if available // Draw the background picture stretched to full logical viewport if available
if (bgTex) { if (bgTex) {
@ -279,6 +281,13 @@ static void drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL
SDL_Color fg = isSelected ? SDL_Color{0, 0, 0, 255} : SDL_Color{240, 240, 245, 255}; SDL_Color fg = isSelected ? SDL_Color{0, 0, 0, 255} : SDL_Color{240, 240, 245, 255};
// Button background // Button background
// Hover highlight
bool isHovered = (level == hoveredLevel);
if (isHovered && !isSelected) {
// slightly brighter hover background
SDL_Color hoverBg = SDL_Color{95, 120, 170, 255};
drawRect(renderer, cx + 8 - 2, cy - 2, cellW - 12, cellH + 4, hoverBg);
}
drawRect(renderer, cx + 8, cy, cellW - 16, cellH, bg); drawRect(renderer, cx + 8, cy, cellW - 16, cellH, bg);
// Level label centered // Level label centered
@ -561,6 +570,9 @@ int main(int, char **)
const float LEVEL_FADE_DURATION = 3500.0f; // ms for fade transition (3.5s) const float LEVEL_FADE_DURATION = 3500.0f; // ms for fade transition (3.5s)
float levelFadeElapsed = 0.0f; float levelFadeElapsed = 0.0f;
// Default start level selection: 0 (declare here so it's in scope for all handlers)
int startLevelSelection = 0;
// Load blocks texture using native SDL BMP loading // Load blocks texture using native SDL BMP loading
SDL_Texture *blocksTex = nullptr; SDL_Texture *blocksTex = nullptr;
SDL_Surface* blocksSurface = SDL_LoadBMP("assets/images/blocks90px_001.bmp"); SDL_Surface* blocksSurface = SDL_LoadBMP("assets/images/blocks90px_001.bmp");
@ -574,46 +586,24 @@ int main(int, char **)
// No global exposure of blocksTex; states receive textures via StateContext. // No global exposure of blocksTex; states receive textures via StateContext.
if (!blocksTex) { if (!blocksTex) {
(void)0;
// Create a 630x90 texture (7 blocks * 90px each) // Create a 630x90 texture (7 blocks * 90px each)
blocksTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 630, 90); blocksTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 630, 90);
if (blocksTex) {
// Set texture as render target and draw colored blocks // Generate blocks by drawing colored rectangles to texture
SDL_SetRenderTarget(renderer, blocksTex); SDL_SetRenderTarget(renderer, blocksTex);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
// Draw each block type with its color
for (int i = 0; i < PIECE_COUNT; ++i) { for (int i = 0; i < PIECE_COUNT; ++i) {
SDL_Color color = COLORS[i + 1]; SDL_Color c = COLORS[i + 1];
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255); SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
SDL_FRect rect = {float(i * 90 + 4), 4, 82, 82}; // 4px padding, 82x82 block SDL_FRect rect{(float)(i * 90), 0, 90, 90};
SDL_RenderFillRect(renderer, &rect); SDL_RenderFillRect(renderer, &rect);
// Add a highlight effect
SDL_SetRenderDrawColor(renderer,
(Uint8)std::min(255, color.r + 30),
(Uint8)std::min(255, color.g + 30),
(Uint8)std::min(255, color.b + 30), 255);
SDL_FRect highlight = {float(i * 90 + 4), 4, 82, 20};
SDL_RenderFillRect(renderer, &highlight);
} }
// Reset render target
SDL_SetRenderTarget(renderer, nullptr); SDL_SetRenderTarget(renderer, nullptr);
(void)0;
} else {
std::fprintf(stderr, "Failed to create programmatic texture: %s\n", SDL_GetError());
}
} else {
(void)0;
} }
// Provide the blocks sheet to the fireworks system through StateContext (no globals).
// Default start level selection: 0
int startLevelSelection = 0;
Game game(startLevelSelection); Game game(startLevelSelection);
// Initialize sound effects system // Initialize sound effects system
@ -843,31 +833,38 @@ int main(int, char **)
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale; float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
if (showLevelPopup) { if (showLevelPopup) {
// Handle level selection popup clicks // Handle level selection popup clicks (use same math as drawLevelSelectionPopup)
float popupW = 400, popupH = 300; float popupW = 400, popupH = 300;
float popupX = (LOGICAL_W - popupW) / 2; float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2; float popupY = (LOGICAL_H - popupH) / 2;
int cols = 4, rows = 5;
float padding = 24.0f;
float gridW = popupW - padding * 2;
float gridH = popupH - 120.0f;
float cellW = gridW / cols;
float cellH = std::min(80.0f, gridH / rows - 12.0f);
float gridStartX = popupX + padding;
float gridStartY = popupY + 70;
if (lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH) { if (lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH) {
// Click inside popup - check level grid // Click inside popup - check level grid
float gridStartX = popupX + 50;
float gridStartY = popupY + 70;
float cellW = 70, cellH = 35;
if (lx >= gridStartX && ly >= gridStartY) { if (lx >= gridStartX && ly >= gridStartY) {
int col = int((lx - gridStartX) / cellW); int col = int((lx - gridStartX) / cellW);
int row = int((ly - gridStartY) / cellH); int row = int((ly - gridStartY) / (cellH + 12.0f));
if (col >= 0 && col < 4 && row >= 0 && row < 5) { if (col >= 0 && col < cols && row >= 0 && row < rows) {
int selectedLevel = row * 4 + col; int selectedLevel = row * cols + col;
if (selectedLevel < 20) { if (selectedLevel < 20) {
startLevelSelection = selectedLevel; startLevelSelection = selectedLevel;
showLevelPopup = false; showLevelPopup = false;
hoveredLevel = -1;
} }
} }
} }
} else { } else {
// Click outside popup - close it // Click outside popup - close it
showLevelPopup = false; showLevelPopup = false;
hoveredLevel = -1;
} }
} else if (showSettingsPopup) { } else if (showSettingsPopup) {
// Click anywhere closes settings popup // Click anywhere closes settings popup
@ -960,7 +957,7 @@ int main(int, char **)
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h) if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h)
{ {
float lx = (mx - logicalVP.x) / logicalScale, ly = (my - logicalVP.y) / logicalScale; float lx = (mx - logicalVP.x) / logicalScale, ly = (my - logicalVP.y) / logicalScale;
if (state == AppState::Menu && !showLevelPopup && !showSettingsPopup) if (state == AppState::Menu && !showSettingsPopup)
{ {
// Compute content offsets and responsive buttons (match MenuState) // Compute content offsets and responsive buttons (match MenuState)
float contentW = LOGICAL_W * logicalScale; float contentW = LOGICAL_W * logicalScale;
@ -976,11 +973,40 @@ int main(int, char **)
SDL_FRect playBtn{btnCX - btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH}; SDL_FRect playBtn{btnCX - btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
SDL_FRect levelBtn{btnCX + btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH}; SDL_FRect levelBtn{btnCX + btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
// If level popup is not shown, clear hoveredLevel; otherwise compute it
hoveredButton = -1; hoveredButton = -1;
if (!showLevelPopup) {
if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h) if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h)
hoveredButton = 0; hoveredButton = 0;
else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h) else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h)
hoveredButton = 1; hoveredButton = 1;
hoveredLevel = -1;
} else {
// compute hover over popup grid using same math as drawLevelSelectionPopup
float popupW = 400, popupH = 300;
float popupX = (LOGICAL_W - popupW) / 2;
float popupY = (LOGICAL_H - popupH) / 2;
int cols = 4, rows = 5;
float padding = 24.0f;
float gridW = popupW - padding * 2;
float gridH = popupH - 120.0f;
float cellW = gridW / cols;
float cellH = std::min(80.0f, gridH / rows - 12.0f);
float gridStartX = popupX + padding;
float gridStartY = popupY + 70;
hoveredLevel = -1;
if (lx >= popupX && lx <= popupX + popupW && ly >= popupY && ly <= popupY + popupH) {
if (lx >= gridStartX && ly >= gridStartY) {
int col = int((lx - gridStartX) / cellW);
int row = int((ly - gridStartY) / (cellH + 12.0f));
if (col >= 0 && col < cols && row >= 0 && row < rows) {
int sel = row * cols + col;
if (sel < 20) hoveredLevel = sel;
}
}
}
}
} }
} }
} }