feat: implement textured line clear effects and refine UI alignment

- **Visual Effects**: Upgraded line clear particles to use the game's block texture instead of simple circles, matching the reference web game's aesthetic.
- **Particle Physics**: Tuned particle velocity, gravity, and fade rates for a more dynamic explosion effect.
- **Rendering Integration**: Updated [main.cpp](cci:7://file:///d:/Sites/Work/tetris/src/main.cpp:0:0-0:0) and `GameRenderer` to pass the block texture to the effect system and correctly trigger animations upon line completion.
- **Menu UI**: Fixed [MenuState](cci:1://file:///d:/Sites/Work/tetris/src/states/MenuState.cpp:19:0-19:55) layout calculations to use fixed logical dimensions (1200x1000), ensuring consistent centering and alignment of the logo, buttons, and settings icon across different window sizes.
- **Code Cleanup**: Refactored `PlayingState` to delegate effect triggering to the rendering layer where correct screen coordinates are available.
This commit is contained in:
2025-11-21 21:19:14 +01:00
parent b5ef9172b3
commit 66099809e0
47 changed files with 5547 additions and 267 deletions

116
src/gameplay/core/Game.h Normal file
View File

@ -0,0 +1,116 @@
// Game.h - Core Tetris game logic (board, piece mechanics, scoring events only)
#pragma once
#include <array>
#include <vector>
#include <random>
#include <cstdint>
#include <functional>
#include <memory>
#include "../../core/GravityManager.h"
enum PieceType { I, O, T, S, Z, J, L, PIECE_COUNT };
using Shape = std::array<uint16_t, 4>; // four rotation bitmasks
class Game {
public:
static constexpr int COLS = 10;
static constexpr int ROWS = 20;
static constexpr int TILE = 28; // logical cell size in pixels (render layer decides use)
struct Piece { PieceType type{PIECE_COUNT}; int rot{0}; int x{3}; int y{-2}; };
explicit Game(int startLevel = 0) { reset(startLevel); }
void reset(int startLevel = 0);
// Simulation -----------------------------------------------------------
void tickGravity(double frameMs); // advance gravity accumulator & drop
void softDropBoost(double frameMs); // accelerate fall while held
void hardDrop(); // instant drop & lock
void setSoftDropping(bool on) { softDropping = on; } // mark if player holds Down
void move(int dx); // horizontal move
void rotate(int dir); // +1 cw, -1 ccw (simple wall-kick)
void holdCurrent(); // swap with hold (once per spawn)
// Accessors -----------------------------------------------------------
const std::array<int, COLS*ROWS>& boardRef() const { return board; }
const Piece& current() const { return cur; }
const Piece& next() const { return nextPiece; }
const Piece& held() const { return hold; }
bool canHoldPiece() const { return canHold; }
bool isGameOver() const { return gameOver; }
bool isPaused() const { return paused; }
void setPaused(bool p) { paused = p; }
int score() const { return _score; }
int lines() const { return _lines; }
int level() const { return _level; }
int startLevelBase() const { return startLevel; }
double elapsed() const { return _elapsedSec; }
void addElapsed(double frameMs) { if (!paused) _elapsedSec += frameMs/1000.0; }
// Block statistics
const std::array<int, PIECE_COUNT>& getBlockCounts() const { return blockCounts; }
// Line clearing effects support
bool hasCompletedLines() const { return !completedLines.empty(); }
const std::vector<int>& getCompletedLines() const { return completedLines; }
void clearCompletedLines(); // Actually remove the lines from the board
// Sound effect callbacks
using SoundCallback = std::function<void(int)>; // Callback for line clear sounds (number of lines)
using LevelUpCallback = std::function<void(int)>; // Callback for level up sounds
void setSoundCallback(SoundCallback callback) { soundCallback = callback; }
void setLevelUpCallback(LevelUpCallback callback) { levelUpCallback = callback; }
// Shape helper --------------------------------------------------------
static bool cellFilled(const Piece& p, int cx, int cy);
// Gravity tuning accessors (public API for HUD/tuning)
void setGravityGlobalMultiplier(double m) { gravityGlobalMultiplier = m; }
double getGravityGlobalMultiplier() const;
double getGravityMs() const;
void setLevelGravityMultiplier(int level, double m);
private:
std::array<int, COLS*ROWS> board{}; // 0 empty else color index
Piece cur{}, hold{}, nextPiece{}; // current, held & next piece
bool canHold{true};
bool paused{false};
std::vector<PieceType> bag; // 7-bag randomizer
std::mt19937 rng{ std::random_device{}() };
std::array<int, PIECE_COUNT> blockCounts{}; // Count of each piece type used
int _score{0};
int _lines{0};
int _level{1};
double gravityMs{800.0};
double fallAcc{0.0};
double _elapsedSec{0.0};
bool gameOver{false};
int startLevel{0};
bool softDropping{false}; // true while player holds Down key
// Line clearing support
std::vector<int> completedLines; // Rows that are complete and ready for effects
// Sound effect callbacks
SoundCallback soundCallback;
LevelUpCallback levelUpCallback;
// Gravity tuning -----------------------------------------------------
// Global multiplier applied to all level timings (use to slow/speed whole-game gravity)
double gravityGlobalMultiplier{1.0};
// Gravity manager encapsulates frames table, multipliers and conversions
GravityManager gravityMgr;
// Backwards-compatible accessors (delegate to gravityMgr)
double computeGravityMsForLevel(int level) const;
// Internal helpers ----------------------------------------------------
void refillBag();
void spawn();
bool collides(const Piece& p) const;
void lockPiece();
int checkLines(); // Find completed lines and store them
void actualClearLines(); // Actually remove lines from board
bool tryMoveDown(); // one-row fall; returns true if moved
// Gravity tuning helpers (public API declared above)
};