Files
spacetris/UPGRADES.md
Gregor Klevze 2afaea7fd3
Some checks failed
Build and Package Tetris / build-windows (push) Has been cancelled
Build and Package Tetris / build-linux (push) Has been cancelled
Updated game structure
2025-08-16 12:10:19 +02:00

9.2 KiB
Raw Blame History

Tetris — Upgrade Roadmap

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

  • 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.

Checklist (high level)

  • 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.
  • Extract GravityManager (SRP)
    • Note: src/core/GravityManager.{h,cpp} implemented and Game delegates gravity computation to it.
  • Replace globals / extern texture usage with StateContext-passed resources
    • Note: popup/background and menu wrappers now accept textures via StateContext; the temporary file_blocksTex bridge was removed. Run grep -R "extern .*SDL_Texture" src/ to confirm no remaining externs.
  • [~] Add runtime knobs (gravity multiplier +/-, HUD display) and clamp ranges
    • Note: HUD displays gravity ms/fps; global/level multipliers are exposed via API but runtime keys/persistence are still pending.
  • Replace ad-hoc printf with SDL_Log or injected Logger service
    • Note: majority of printf/fprintf debug prints were replaced with SDL_Log* calls; a quick grep audit is recommended to find any remaining ad-hoc prints.
  • Add unit tests (gravity conversion, level progression, line clear behavior)
    • Note: a small test runner (tests/GravityTests.cpp) and the tetris_tests CMake target were added and run locally; gravity tests pass (see build-msvc test run). Converting to broader Catch2 suites is optional.
  • Add CI (build + tests) and code style checks
  • Improve input hit-testing for level popup and scalable UI
  • Add defensive guards (clamps, null checks) and const-correctness
  • Improve resource management (RAII, smart pointers), and error handling for SDL API calls

Rationale & Goals

  • 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 tuning: per-level and global gravity tuning must be instance-local and debug-friendly.
  • Improve developer experience: add unit tests, CI, and runtime debug knobs for quick tuning.

Detailed tasks (prioritized)

1) Safety & small win (Low risk — 13 hours)

  • Make the NES frames-per-cell table constexpr and immutable.
    • File: src/Game.cpp (reverse any mutable anonymous namespace table changes)
    • Why: avoids accidental global mutation; makes compute deterministic.
  • Move per-level multiplier array from global static to Game instance:
    • Add std::array<double, 30> levelMultipliers inside Game (default 1.0).
    • Change setLevelGravityMultiplier to update instance array and recompute gravityMs.
  • Clamp gravity values to a minimum (e.g., 1.0 ms) and clamp multipliers to sensible range (0.1..10.0).

Deliverable: small patch to Game.h/Game.cpp and a rebuild verification.

2) Replace ad-hoc logging (Low risk — 0.51 hour)

  • 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.

Files: src/Game.cpp, any file using printf for debug.

3) Remove fragile globals / externs (Low risk — 12 hours)

  • 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.

Files: src/main.cpp, src/states/MenuState.cpp, other states.

4) Extract GravityManager (Medium risk — 26 hours)

  • Create src/core/GravityManager.h|cpp that encapsulates
    • the constexpr NES frames table (read-only)
    • per-instance multipliers
    • conversion helpers: frames->ms and ms->fps
  • Game will hold GravityManager gravity; and call gravity.getMs(level).

Benefits: easier testing and isolated behavior change for future gravity models.

5) Extract Board/Scoring responsibilities (Medium — 48 hours)

  • Split Game into smaller collaborators:
    • Board — raw grid, collision, line detection/clear operations
    • Scorer — scoring rules, combo handling, level progression thresholds
    • PieceController — piece spawn, rotation, kicks
  • Keep Game as an orchestrator that composes these objects.

Files: src/Board.*, src/Scorer.*, src/PieceController.*, adjust Game to compose them.

6) Unit tests & test infrastructure (Medium — 36 hours)

  • Add Catch2 or GoogleTest to vcpkg.json or as FetchContent in CMake.
  • Add tests:
    • Gravity conversion tests (frames→ms, per-level multipliers, global multiplier)
    • Board behavior: place blocks, clear lines, gravity after clear
    • Level progression increment logic and resulting gravity changes
  • Add a tests/ CMake target and basic CI integration (see next section).

7) CI / Build checks (Medium — 24 hours)

  • 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 a Pre-commit hook to run format checks.

Files: .github/workflows/build.yml, .clang-format, optional clang-tidy config.

8) UX and input correctness (LowMedium — 13 hours)

  • 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.
  • Ensure popup background texture is stretched to the logical viewport and that overlay alpha is constant across window sizes.
  • Add keyboard navigation for popup (arrow keys + Enter) and mouse hover effects.

Files: src/main.cpp, src/states/MenuState.cpp.

9) Runtime debug knobs (Low — 1 hour)

  • 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).
  • Persist tuning to a small JSON file settings/debug_tuning.json (optional).

Files: src/Game.h/cpp, src/main.cpp HUD code.

10) Defensive & correctness improvements (Low — 24 hours)

  • 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.
  • Add const qualifiers to getters where possible.
  • Remove magic numbers; define named constexpr constants for UI sizes, softDrop multiplier, DAS/ARR etc.

Files: various (src/*.cpp, src/*.h).

11) Packaging & build improvements (Low — 12 hours)

  • 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.

Files: build-production.ps1, .github/workflows/release.yml.


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)
  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)
  4. Add tests and CMake test target. (36 hours)
  5. Extract GravityManager and write unit tests for it. (24 hours)
  6. Extract Board and Scorer (bigger refactor, add tests). (48 hours)
  7. CI + packaging + formatting. (24 hours)

  • Convert NES frames table to constexpr and clamp gravity to >= 1ms.
  • 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").
  • Add simple unit test that asserts gravityMs(level0) == FRAME_MS * 48 * multiplier.

Example: gravity computation (reference)

// gravity constants
constexpr double NES_FPS = 60.0988;
constexpr double FRAME_MS = 1000.0 / NES_FPS;
constexpr int FRAMES_TABLE[30] = {48,43,38,33,28,23,18,13,8,6,5,5,5,4,4,4,3,3,3,2,2,2,2,2,2,2,2,2,1};

// per-instance multipliers in Game
std::array<double,30> levelMultipliers; // default 1.0

double GravityManager::msForLevel(int level) const {
    int idx = std::clamp(level, 0, 29);
    double frames = FRAMES_TABLE[idx] * levelMultipliers[idx];
    return std::max(1.0, frames * FRAME_MS * globalMultiplier);
}

Estimated total effort

  • Conservative: 1636 hours depending on how far you split Game and add tests/CI.

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.)
  • Add a unit-test harness using Catch2 and a small GravityManager test.

Tell me which of the next steps above you'd like me to implement now and I will start the code changes.