#pragma once #include #include #include #include #include #include #include #include "../core/Game.h" // For PieceType enums and gravity table helpers // Cooperative two-player session with a shared 20-column board split into halves. // This is an early scaffold: rules and rendering hooks will be iterated in follow-up passes. class CoopGame { public: enum class PlayerSide { Left, Right }; static constexpr int COLS = 20; static constexpr int ROWS = Game::ROWS; static constexpr int TILE = Game::TILE; struct Piece { PieceType type{PIECE_COUNT}; int rot{0}; int x{0}; int y{-2}; }; struct Cell { int value{0}; // 0 empty else color index (1..7) PlayerSide owner{PlayerSide::Left}; bool occupied{false}; }; struct RowHalfState { bool leftFull{false}; bool rightFull{false}; }; struct PlayerState { PlayerSide side{PlayerSide::Left}; Piece cur{}; Piece hold{}; Piece next{}; uint64_t pieceSeq{0}; bool canHold{true}; bool softDropping{false}; bool toppedOut{false}; double fallAcc{0.0}; double lockAcc{0.0}; int score{0}; int lines{0}; int level{0}; int tetrisesMade{0}; int currentCombo{0}; int maxCombo{0}; int comboCount{0}; std::vector bag{}; // 7-bag queue std::mt19937 rng{ std::random_device{}() }; }; explicit CoopGame(int startLevel = 0); using SoundCallback = std::function; using LevelUpCallback = std::function; void setSoundCallback(SoundCallback cb) { soundCallback = cb; } void setLevelUpCallback(LevelUpCallback cb) { levelUpCallback = cb; } void reset(int startLevel = 0); void resetDeterministic(int startLevel, uint32_t seed); void tickGravity(double frameMs); void updateVisualEffects(double frameMs); // Determinism / desync detection uint64_t computeStateHash() const; // Per-player inputs ----------------------------------------------------- void setSoftDropping(PlayerSide side, bool on); void move(PlayerSide side, int dx); void rotate(PlayerSide side, int dir); // +1 cw, -1 ccw void hardDrop(PlayerSide side); void holdCurrent(PlayerSide side); // Accessors ------------------------------------------------------------- const std::array& boardRef() const { return board; } const Piece& current(PlayerSide s) const { return player(s).cur; } const Piece& next(PlayerSide s) const { return player(s).next; } const Piece& held(PlayerSide s) const { return player(s).hold; } bool canHold(PlayerSide s) const { return player(s).canHold; } bool isGameOver() const { return gameOver; } int score() const { return _score; } int score(PlayerSide s) const { return player(s).score; } int lines() const { return _lines; } int lines(PlayerSide s) const { return player(s).lines; } int level() const { return _level; } int level(PlayerSide s) const { return player(s).level; } int comboCount() const { return _comboCount; } int maxCombo() const { return _maxCombo; } int tetrisesMade() const { return _tetrisesMade; } int elapsed() const { return static_cast(elapsedMs / 1000.0); } int elapsed(PlayerSide) const { return elapsed(); } int startLevelBase() const { return startLevel; } double getGravityMs() const { return gravityMs; } double getFallAccumulator(PlayerSide s) const { return player(s).fallAcc; } bool isSoftDropping(PlayerSide s) const { return player(s).softDropping; } uint64_t currentPieceSequence(PlayerSide s) const { return player(s).pieceSeq; } const std::vector& getCompletedLines() const { return completedLines; } bool hasCompletedLines() const { return !completedLines.empty(); } void clearCompletedLines(); const std::array& rowHalfStates() const { return rowStates; } // Simple visual-effect compatibility (stubbed for now) bool hasHardDropShake() const { return hardDropShakeTimerMs > 0.0; } double hardDropShakeStrength() const; const std::vector& getHardDropCells() const { return hardDropCells; } uint32_t getHardDropFxId() const { return hardDropFxId; } static bool cellFilled(const Piece& p, int cx, int cy); private: static constexpr double LOCK_DELAY_MS = 500.0; void resetInternal(int startLevel_, const std::optional& seedOpt); std::array board{}; std::array rowStates{}; PlayerState left{}; PlayerState right{ PlayerSide::Right }; int _score{0}; int _lines{0}; int _level{1}; int _tetrisesMade{0}; int _currentCombo{0}; int _maxCombo{0}; int _comboCount{0}; int startLevel{0}; double gravityMs{800.0}; double gravityGlobalMultiplier{1.0}; bool gameOver{false}; double elapsedMs{0.0}; std::vector completedLines; // Impact FX double hardDropShakeTimerMs{0.0}; static constexpr double HARD_DROP_SHAKE_DURATION_MS = 320.0; std::vector hardDropCells; uint32_t hardDropFxId{0}; uint64_t pieceSequence{0}; SoundCallback soundCallback; LevelUpCallback levelUpCallback; // Helpers --------------------------------------------------------------- PlayerState& player(PlayerSide s) { return s == PlayerSide::Left ? left : right; } const PlayerState& player(PlayerSide s) const { return s == PlayerSide::Left ? left : right; } void refillBag(PlayerState& ps); Piece drawFromBag(PlayerState& ps); void spawn(PlayerState& ps); bool collides(const PlayerState& ps, const Piece& p) const; bool tryMove(PlayerState& ps, int dx, int dy); void lock(PlayerState& ps); void findCompletedLines(); void clearLinesInternal(); void updateRowStates(); void applyLineClearRewards(PlayerState& creditPlayer, int cleared); double gravityMsForLevel(int level) const; int columnMin(PlayerSide s) const { return s == PlayerSide::Left ? 0 : 10; } int columnMax(PlayerSide s) const { return s == PlayerSide::Left ? 9 : 19; } };