diff --git a/src/app/TetrisApp.cpp b/src/app/TetrisApp.cpp index 0b0db60..db0b29e 100644 --- a/src/app/TetrisApp.cpp +++ b/src/app/TetrisApp.cpp @@ -685,6 +685,13 @@ void TetrisApp::Impl::runLoop() challengeClearFxOrder.push_back(idx); } } + // Seed FX RNG deterministically from the game's challenge seed so animations + // are reproducible per-run and per-level. Fall back to a random seed if game absent. + if (game) { + challengeClearFxRng.seed(game->getChallengeSeedBase() + static_cast(nextLevel)); + } else { + challengeClearFxRng.seed(std::random_device{}()); + } std::shuffle(challengeClearFxOrder.begin(), challengeClearFxOrder.end(), challengeClearFxRng); challengeClearFxElapsedMs = 0.0; diff --git a/src/gameplay/core/Game.cpp b/src/gameplay/core/Game.cpp index aab6c90..8a560a0 100644 --- a/src/gameplay/core/Game.cpp +++ b/src/gameplay/core/Game.cpp @@ -278,7 +278,7 @@ void Game::placeAsteroidsForLevel(int level) { } AsteroidType type = chooseAsteroidTypeForLevel(level); AsteroidCell cell = makeAsteroidForType(type); - board[idx] = ASTEROID_BASE + static_cast(type); + board[idx] = asteroidBoardValue(type); asteroidGrid[idx] = cell; ++asteroidsRemainingCount; ++asteroidsTotalThisLevel; @@ -289,6 +289,22 @@ void Game::placeAsteroidsForLevel(int level) { } } +// Helper implementations for asteroid board encoding +bool Game::isAsteroidValue(int boardValue) { + return boardValue >= ASTEROID_BASE; +} + +AsteroidType Game::asteroidTypeFromValue(int boardValue) { + int idx = boardValue - ASTEROID_BASE; + if (idx < 0) return AsteroidType::Normal; + if (idx > static_cast(AsteroidType::Core)) idx = static_cast(AsteroidType::Core); + return static_cast(idx); +} + +int Game::asteroidBoardValue(AsteroidType t) { + return ASTEROID_BASE + static_cast(t); +} + double Game::elapsed() const { if (!_startTime) return 0.0; @@ -519,11 +535,8 @@ int Game::checkLines() { for (int y : completedLines) { for (int x = 0; x < COLS; ++x) { int idx = y * COLS + x; - if (board[idx] >= ASTEROID_BASE) { - int typeIdx = board[idx] - ASTEROID_BASE; - if (typeIdx >= 0 && typeIdx <= static_cast(AsteroidType::Core)) { - foundType = static_cast(typeIdx); - } + if (isAsteroidValue(board[idx])) { + foundType = asteroidTypeFromValue(board[idx]); } else if (idx >= 0 && idx < static_cast(asteroidGrid.size()) && asteroidGrid[idx].has_value()) { foundType = asteroidGrid[idx]->type; } @@ -643,7 +656,7 @@ void Game::handleAsteroidsOnClearedRows(const std::vector& clearedRows, continue; // off the board after collapse } int destIdx = destY * COLS + x; - outBoard[destIdx] = ASTEROID_BASE + static_cast(cell.type); + outBoard[destIdx] = asteroidBoardValue(cell.type); outAsteroids[destIdx] = cell; } else { int destY = y + clearedBelow[y]; diff --git a/src/gameplay/core/Game.h b/src/gameplay/core/Game.h index 920ff97..4899f06 100644 --- a/src/gameplay/core/Game.h +++ b/src/gameplay/core/Game.h @@ -185,6 +185,15 @@ private: // Recent asteroid explosion positions (grid coords) for renderer FX std::vector recentAsteroidExplosions; + + // Expose the internal challenge seed base for deterministic FX/RNG coordination +public: + uint32_t getChallengeSeedBase() const { return challengeSeedBase; } + + // Helpers for board encoding of asteroids + static bool isAsteroidValue(int boardValue); + static AsteroidType asteroidTypeFromValue(int boardValue); + static int asteroidBoardValue(AsteroidType t); // Internal helpers ---------------------------------------------------- void refillBag();