208 lines
9.5 KiB
C++
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)
|
|
};
|