fixed gameplay
This commit is contained in:
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user