fixed gameplay

This commit is contained in:
2025-12-20 15:17:35 +01:00
parent 9a3c1a0688
commit ad014e1de0
10 changed files with 176 additions and 25 deletions

View File

@ -200,6 +200,14 @@ struct TetrisApp::Impl {
bool countdownAdvancesChallenge = false;
double gameplayBackgroundClockMs = 0.0;
// Challenge clear FX (celebratory board explosion before countdown)
bool challengeClearFxActive = false;
double challengeClearFxElapsedMs = 0.0;
double challengeClearFxDurationMs = 0.0;
int challengeClearFxNextLevel = 0;
std::vector<int> challengeClearFxOrder;
std::mt19937 challengeClearFxRng{std::random_device{}()};
std::unique_ptr<StateManager> stateMgr;
StateContext ctx{};
std::unique_ptr<LoadingState> loadingState;
@ -386,6 +394,10 @@ int TetrisApp::Impl::init()
suppressLineVoiceForLevelUp = true;
});
game->setAsteroidDestroyedCallback([](AsteroidType /*type*/) {
SoundEffectManager::instance().playSound("asteroid_destroy", 0.9f);
});
state = AppState::Loading;
loadingProgress = 0.0;
loadStart = SDL_GetTicks();
@ -448,6 +460,10 @@ int TetrisApp::Impl::init()
ctx.exitPopupSelectedButton = &exitPopupSelectedButton;
ctx.gameplayCountdownActive = &gameplayCountdownActive;
ctx.menuPlayCountdownArmed = &menuPlayCountdownArmed;
ctx.challengeClearFxActive = &challengeClearFxActive;
ctx.challengeClearFxElapsedMs = &challengeClearFxElapsedMs;
ctx.challengeClearFxDurationMs = &challengeClearFxDurationMs;
ctx.challengeClearFxOrder = &challengeClearFxOrder;
ctx.playerName = &playerName;
ctx.fullscreenFlag = &isFullscreen;
ctx.applyFullscreen = [this](bool enable) {
@ -568,6 +584,37 @@ void TetrisApp::Impl::runLoop()
}
};
auto startChallengeClearFx = [this](int nextLevel) {
challengeClearFxOrder.clear();
const auto& boardRef = game->boardRef();
const auto& asteroidRef = game->asteroidCells();
for (int idx = 0; idx < Game::COLS * Game::ROWS; ++idx) {
if (boardRef[idx] != 0 || asteroidRef[idx].has_value()) {
challengeClearFxOrder.push_back(idx);
}
}
if (challengeClearFxOrder.empty()) {
challengeClearFxOrder.reserve(Game::COLS * Game::ROWS);
for (int idx = 0; idx < Game::COLS * Game::ROWS; ++idx) {
challengeClearFxOrder.push_back(idx);
}
}
std::shuffle(challengeClearFxOrder.begin(), challengeClearFxOrder.end(), challengeClearFxRng);
challengeClearFxElapsedMs = 0.0;
challengeClearFxDurationMs = std::clamp(800.0 + static_cast<double>(challengeClearFxOrder.size()) * 8.0, 900.0, 2600.0);
challengeClearFxNextLevel = nextLevel;
challengeClearFxActive = true;
gameplayCountdownActive = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
menuPlayCountdownArmed = false;
if (game) {
game->setPaused(true);
}
SoundEffectManager::instance().playSound("challenge_clear", 0.8f);
};
while (running)
{
if (!ctx.scores && scoresLoadComplete.load(std::memory_order_acquire)) {
@ -756,6 +803,8 @@ void TetrisApp::Impl::runLoop()
case ui::BottomMenuItem::Challenge:
if (game) {
game->setMode(GameMode::Challenge);
// Suppress the initial level-up jingle when starting Challenge from menu
skipNextLevelUpJingle = true;
game->startChallengeRun(1);
}
startMenuPlayTransition();
@ -876,6 +925,32 @@ void TetrisApp::Impl::runLoop()
if (frameMs > 100.0) frameMs = 100.0;
gameplayBackgroundClockMs += frameMs;
if (challengeClearFxActive) {
challengeClearFxElapsedMs += frameMs;
if (challengeClearFxElapsedMs >= challengeClearFxDurationMs) {
challengeClearFxElapsedMs = challengeClearFxDurationMs;
challengeClearFxActive = false;
if (challengeClearFxNextLevel > 0) {
// Advance to the next challenge level immediately so the countdown shows the new board/asteroids
if (game) {
game->beginNextChallengeLevel();
game->setPaused(true);
}
gameplayCountdownSource = CountdownSource::ChallengeLevel;
countdownLevel = challengeClearFxNextLevel;
countdownGoalAsteroids = challengeClearFxNextLevel;
countdownAdvancesChallenge = false; // already advanced
gameplayCountdownActive = true;
menuPlayCountdownArmed = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
SoundEffectManager::instance().playSound("new_level", 1.0f);
skipNextLevelUpJingle = true;
}
challengeClearFxNextLevel = 0;
}
}
const bool *ks = SDL_GetKeyboardState(nullptr);
bool left = state == AppState::Playing && ks[SDL_SCANCODE_LEFT];
bool right = state == AppState::Playing && ks[SDL_SCANCODE_RIGHT];
@ -1013,9 +1088,9 @@ void TetrisApp::Impl::runLoop()
SoundEffectManager::instance().init();
loadedTasks.fetch_add(1);
const std::vector<std::string> audioIds = {"clear_line","nice_combo","you_fire","well_played","keep_that_ryhtm","great_move","smooth_clear","impressive","triple_strike","amazing","you_re_unstoppable","boom_tetris","wonderful","lets_go","hard_drop","new_level"};
const std::vector<std::string> audioIds = {"clear_line","nice_combo","you_fire","well_played","keep_that_ryhtm","great_move","smooth_clear","impressive","triple_strike","amazing","you_re_unstoppable","boom_tetris","wonderful","lets_go","hard_drop","new_level","asteroid_destroy","challenge_clear"};
for (const auto &id : audioIds) {
std::string basePath = "assets/music/" + (id == "hard_drop" ? "hard_drop_001" : id);
std::string basePath = "assets/music/" + (id == "hard_drop" ? "hard_drop_001" : (id == "challenge_clear" ? "GONG0" : id));
{
std::lock_guard<std::mutex> lk(currentLoadingMutex);
currentLoadingFile = basePath;
@ -1224,20 +1299,10 @@ void TetrisApp::Impl::runLoop()
break;
}
if (state == AppState::Playing && game && game->getMode() == GameMode::Challenge && !gameplayCountdownActive) {
if (state == AppState::Playing && game && game->getMode() == GameMode::Challenge && !gameplayCountdownActive && !challengeClearFxActive) {
int queuedLevel = game->consumeQueuedChallengeLevel();
if (queuedLevel > 0) {
gameplayCountdownSource = CountdownSource::ChallengeLevel;
countdownLevel = queuedLevel;
countdownGoalAsteroids = queuedLevel;
countdownAdvancesChallenge = true;
gameplayCountdownActive = true;
menuPlayCountdownArmed = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
game->setPaused(true);
SoundEffectManager::instance().playSound("new_level", 1.0f);
skipNextLevelUpJingle = true;
startChallengeClearFx(queuedLevel);
}
}
@ -1339,6 +1404,14 @@ void TetrisApp::Impl::runLoop()
game->setPaused(false);
}
if (state != AppState::Playing && challengeClearFxActive) {
challengeClearFxActive = false;
challengeClearFxElapsedMs = 0.0;
challengeClearFxDurationMs = 0.0;
challengeClearFxNextLevel = 0;
challengeClearFxOrder.clear();
}
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);