168 lines
6.2 KiB
C++
168 lines
6.2 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <optional>
|
|
#include <random>
|
|
#include <functional>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <SDL3/SDL.h>
|
|
|
|
#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<PieceType> bag{}; // 7-bag queue
|
|
std::mt19937 rng{ std::random_device{}() };
|
|
};
|
|
|
|
explicit CoopGame(int startLevel = 0);
|
|
using SoundCallback = std::function<void(int)>;
|
|
using LevelUpCallback = std::function<void(int)>;
|
|
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<Cell, COLS * ROWS>& 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<int>(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<int>& getCompletedLines() const { return completedLines; }
|
|
bool hasCompletedLines() const { return !completedLines.empty(); }
|
|
void clearCompletedLines();
|
|
const std::array<RowHalfState, ROWS>& rowHalfStates() const { return rowStates; }
|
|
|
|
// Simple visual-effect compatibility (stubbed for now)
|
|
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; }
|
|
|
|
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<uint32_t>& seedOpt);
|
|
|
|
std::array<Cell, COLS * ROWS> board{};
|
|
std::array<RowHalfState, ROWS> 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<int> completedLines;
|
|
|
|
// Impact FX
|
|
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};
|
|
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; }
|
|
};
|