Files
spacetris/src/gameplay/core/Game.h
2025-12-20 20:17:43 +01:00

208 lines
9.5 KiB
C++

// 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 <optional>
#include <SDL3/SDL.h>
#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
// Game runtime mode
enum class GameMode { Endless, Challenge };
// Special obstacle blocks used by Challenge mode
enum class AsteroidType : uint8_t { Normal = 0, Armored = 1, Falling = 2, Core = 3 };
struct AsteroidCell {
AsteroidType type{AsteroidType::Normal};
uint8_t hitsRemaining{1};
bool gravityEnabled{false};
uint8_t visualState{0};
};
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, GameMode mode = GameMode::Endless) : mode(mode) { reset(startLevel); }
void reset(int startLevel = 0);
void startChallengeRun(int startingLevel = 1); // resets stats and starts challenge level 1 (or provided)
void beginNextChallengeLevel(); // advances to the next challenge level preserving score/time
// 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); // 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);
GameMode getMode() const { return mode; }
void setMode(GameMode m) { mode = m; }
int score() const { return _score; }
int lines() const { return _lines; }
int level() const { return _level; }
int challengeLevel() const { return challengeLevelIndex; }
int asteroidsRemaining() const { return asteroidsRemainingCount; }
int asteroidsTotal() const { return asteroidsTotalThisLevel; }
bool isChallengeComplete() const { return challengeComplete; }
bool isChallengeLevelActive() const { return challengeLevelActive; }
bool isChallengeAdvanceQueued() const { return challengeAdvanceQueued; }
int queuedChallengeLevel() const { return challengeQueuedLevel; }
int consumeQueuedChallengeLevel(); // returns next level if queued, else 0
int startLevelBase() const { return startLevel; }
double elapsed() const; // Now calculated from start time
void updateElapsedTime(); // Update elapsed time from system clock
bool isSoftDropping() const { return softDropping; }
const std::array<std::optional<AsteroidCell>, COLS*ROWS>& asteroidCells() const { return asteroidGrid; }
const std::vector<SDL_Point>& getRecentAsteroidExplosions() const { return recentAsteroidExplosions; }
void clearRecentAsteroidExplosions() { recentAsteroidExplosions.clear(); }
// 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
using AsteroidDestroyedCallback = std::function<void(AsteroidType)>; // Callback when an asteroid is fully destroyed
void setSoundCallback(SoundCallback callback) { soundCallback = callback; }
void setLevelUpCallback(LevelUpCallback callback) { levelUpCallback = callback; }
void setAsteroidDestroyedCallback(AsteroidDestroyedCallback callback) { asteroidDestroyedCallback = 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;
double getFallAccumulator() const { return fallAcc; } // Debug: time accumulated toward next drop
void setLevelGravityMultiplier(int level, double m);
// Visual effect hooks
void updateVisualEffects(double frameMs);
bool hasHardDropShake() const { return hardDropShakeTimerMs > 0.0; }
double hardDropShakeStrength() const;
const std::vector<SDL_Point>& getHardDropCells() const { return hardDropCells; }
uint32_t getHardDropFxId() const { return hardDropFxId; }
uint64_t getCurrentPieceSequence() const { return pieceSequence; }
// Additional stats
int tetrisesMade() const { return _tetrisesMade; }
int maxCombo() const { return _maxCombo; }
int comboCount() const { return _comboCount; }
private:
static constexpr int ASTEROID_BASE = 100; // sentinel offset for board encoding
static constexpr int ASTEROID_MAX_LEVEL = 100;
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};
int _tetrisesMade{0};
int _currentCombo{0};
int _maxCombo{0};
int _comboCount{0};
double gravityMs{800.0};
double fallAcc{0.0};
Uint64 _startTime{0}; // Performance counter at game start
Uint64 _pausedTime{0}; // Time spent paused (in performance counter ticks)
Uint64 _lastPauseStart{0}; // When the current pause started
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;
AsteroidDestroyedCallback asteroidDestroyedCallback;
// 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;
// Impact FX timers
double hardDropShakeTimerMs{0.0};
static constexpr double HARD_DROP_SHAKE_DURATION_MS = 320.0;
std::vector<SDL_Point> hardDropCells;
uint32_t hardDropFxId{0};
uint64_t pieceSequence{0};
// Challenge mode state -------------------------------------------------
GameMode mode{GameMode::Endless};
int challengeLevelIndex{1};
int asteroidsRemainingCount{0};
int asteroidsTotalThisLevel{0};
bool challengeComplete{false};
std::array<std::optional<AsteroidCell>, COLS*ROWS> asteroidGrid{};
uint32_t challengeSeedBase{0};
std::mt19937 challengeRng{ std::random_device{}() };
bool challengeLevelActive{false};
bool challengeAdvanceQueued{false};
int challengeQueuedLevel{0};
// Asteroid SFX latency mitigation
std::optional<AsteroidType> pendingAsteroidDestroyType;
bool asteroidDestroySoundPreplayed{false};
// Recent asteroid explosion positions (grid coords) for renderer FX
std::vector<SDL_Point> recentAsteroidExplosions;
// 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
void clearAsteroidGrid();
void setupChallengeLevel(int level, bool preserveStats);
void placeAsteroidsForLevel(int level);
AsteroidType chooseAsteroidTypeForLevel(int level);
AsteroidCell makeAsteroidForType(AsteroidType t) const;
void handleAsteroidsOnClearedRows(const std::vector<int>& clearedRows,
std::array<int, COLS*ROWS>& outBoard,
std::array<std::optional<AsteroidCell>, COLS*ROWS>& outAsteroids);
void applyAsteroidGravity();
// Gravity tuning helpers (public API declared above)
};