updated gameplay

This commit is contained in:
2025-12-21 08:41:35 +01:00
parent eb9822dac7
commit a1f16a7d94
5 changed files with 144 additions and 38 deletions

Binary file not shown.

Binary file not shown.

View File

@ -267,6 +267,7 @@ struct TetrisApp::Impl {
int countdownLevel = 0; int countdownLevel = 0;
int countdownGoalAsteroids = 0; int countdownGoalAsteroids = 0;
bool countdownAdvancesChallenge = false; bool countdownAdvancesChallenge = false;
bool challengeCountdownWaitingForSpace = false;
double gameplayBackgroundClockMs = 0.0; double gameplayBackgroundClockMs = 0.0;
std::string challengeStoryText; std::string challengeStoryText;
int challengeStoryLevel = 0; int challengeStoryLevel = 0;
@ -1001,6 +1002,28 @@ void TetrisApp::Impl::runLoop()
} }
} }
} }
else if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
if (gameplayCountdownActive && gameplayCountdownSource == CountdownSource::ChallengeLevel && challengeCountdownWaitingForSpace) {
if (e.key.scancode == SDL_SCANCODE_SPACE) {
challengeCountdownWaitingForSpace = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
} else if (e.key.scancode == SDL_SCANCODE_ESCAPE) {
// Show quit popup, keep game paused, cancel countdown
if (!showExitConfirmPopup) {
showExitConfirmPopup = true;
exitPopupSelectedButton = 1; // default to NO
}
gameplayCountdownActive = false;
menuPlayCountdownArmed = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
countdownAdvancesChallenge = false;
challengeCountdownWaitingForSpace = false;
if (game) game->setPaused(true);
}
}
}
} }
} }
@ -1017,24 +1040,29 @@ void TetrisApp::Impl::runLoop()
challengeStoryClockMs = 0.0; challengeStoryClockMs = 0.0;
}; };
// Update challenge story fade/timeout // Update challenge story fade/timeout; during countdown wait we keep it fully visible
if (state == AppState::Playing && game && game->getMode() == GameMode::Challenge && !challengeStoryText.empty()) { if (state == AppState::Playing && game && game->getMode() == GameMode::Challenge && !challengeStoryText.empty()) {
const double fadeInMs = 320.0; if (gameplayCountdownActive && gameplayCountdownSource == CountdownSource::ChallengeLevel && challengeCountdownWaitingForSpace) {
const double holdMs = 3200.0; // Locked-visible while waiting
const double fadeOutMs = 900.0; challengeStoryAlpha = 1.0f;
const double totalMs = fadeInMs + holdMs + fadeOutMs;
challengeStoryClockMs += frameMs;
if (challengeStoryClockMs >= totalMs) {
clearChallengeStory();
} else { } else {
double a = 1.0; const double fadeInMs = 320.0;
if (challengeStoryClockMs < fadeInMs) { const double holdMs = 3200.0;
a = challengeStoryClockMs / fadeInMs; const double fadeOutMs = 900.0;
} else if (challengeStoryClockMs > fadeInMs + holdMs) { const double totalMs = fadeInMs + holdMs + fadeOutMs;
double t = challengeStoryClockMs - (fadeInMs + holdMs); challengeStoryClockMs += frameMs;
a = std::max(0.0, 1.0 - t / fadeOutMs); if (challengeStoryClockMs >= totalMs) {
clearChallengeStory();
} else {
double a = 1.0;
if (challengeStoryClockMs < fadeInMs) {
a = challengeStoryClockMs / fadeInMs;
} else if (challengeStoryClockMs > fadeInMs + holdMs) {
double t = challengeStoryClockMs - (fadeInMs + holdMs);
a = std::max(0.0, 1.0 - t / fadeOutMs);
}
challengeStoryAlpha = static_cast<float>(std::clamp(a, 0.0, 1.0));
} }
challengeStoryAlpha = static_cast<float>(std::clamp(a, 0.0, 1.0));
} }
} else { } else {
clearChallengeStory(); clearChallengeStory();
@ -1057,6 +1085,7 @@ void TetrisApp::Impl::runLoop()
captureChallengeStory(countdownLevel); captureChallengeStory(countdownLevel);
countdownAdvancesChallenge = false; // already advanced countdownAdvancesChallenge = false; // already advanced
gameplayCountdownActive = true; gameplayCountdownActive = true;
challengeCountdownWaitingForSpace = true;
menuPlayCountdownArmed = false; menuPlayCountdownArmed = false;
gameplayCountdownElapsed = 0.0; gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0; gameplayCountdownIndex = 0;
@ -1460,9 +1489,11 @@ void TetrisApp::Impl::runLoop()
countdownGoalAsteroids = countdownLevel; countdownGoalAsteroids = countdownLevel;
if (gameplayCountdownSource == CountdownSource::ChallengeLevel) { if (gameplayCountdownSource == CountdownSource::ChallengeLevel) {
captureChallengeStory(countdownLevel); captureChallengeStory(countdownLevel);
challengeCountdownWaitingForSpace = true;
} else { } else {
challengeStoryText.clear(); challengeStoryText.clear();
challengeStoryLevel = 0; challengeStoryLevel = 0;
challengeCountdownWaitingForSpace = false;
} }
countdownAdvancesChallenge = false; countdownAdvancesChallenge = false;
menuPlayCountdownArmed = true; menuPlayCountdownArmed = true;
@ -1475,6 +1506,7 @@ void TetrisApp::Impl::runLoop()
gameplayCountdownActive = false; gameplayCountdownActive = false;
gameplayCountdownIndex = 0; gameplayCountdownIndex = 0;
gameplayCountdownElapsed = 0.0; gameplayCountdownElapsed = 0.0;
challengeCountdownWaitingForSpace = false;
game->setPaused(false); game->setPaused(false);
} }
menuFadePhase = MenuFadePhase::FadeIn; menuFadePhase = MenuFadePhase::FadeIn;
@ -1499,9 +1531,11 @@ void TetrisApp::Impl::runLoop()
countdownGoalAsteroids = countdownLevel; countdownGoalAsteroids = countdownLevel;
if (gameplayCountdownSource == CountdownSource::ChallengeLevel) { if (gameplayCountdownSource == CountdownSource::ChallengeLevel) {
captureChallengeStory(countdownLevel); captureChallengeStory(countdownLevel);
challengeCountdownWaitingForSpace = true;
} else { } else {
challengeStoryText.clear(); challengeStoryText.clear();
challengeStoryLevel = 0; challengeStoryLevel = 0;
challengeCountdownWaitingForSpace = false;
} }
countdownAdvancesChallenge = false; countdownAdvancesChallenge = false;
gameplayCountdownActive = true; gameplayCountdownActive = true;
@ -1512,19 +1546,21 @@ void TetrisApp::Impl::runLoop()
} }
if (gameplayCountdownActive && state == AppState::Playing) { if (gameplayCountdownActive && state == AppState::Playing) {
gameplayCountdownElapsed += frameMs; if (!challengeCountdownWaitingForSpace || gameplayCountdownSource != CountdownSource::ChallengeLevel) {
if (gameplayCountdownElapsed >= GAMEPLAY_COUNTDOWN_STEP_MS) { gameplayCountdownElapsed += frameMs;
gameplayCountdownElapsed -= GAMEPLAY_COUNTDOWN_STEP_MS; if (gameplayCountdownElapsed >= GAMEPLAY_COUNTDOWN_STEP_MS) {
++gameplayCountdownIndex; gameplayCountdownElapsed -= GAMEPLAY_COUNTDOWN_STEP_MS;
if (gameplayCountdownIndex >= static_cast<int>(GAMEPLAY_COUNTDOWN_LABELS.size())) { ++gameplayCountdownIndex;
gameplayCountdownActive = false; if (gameplayCountdownIndex >= static_cast<int>(GAMEPLAY_COUNTDOWN_LABELS.size())) {
gameplayCountdownElapsed = 0.0; gameplayCountdownActive = false;
gameplayCountdownIndex = 0; gameplayCountdownElapsed = 0.0;
if (gameplayCountdownSource == CountdownSource::ChallengeLevel && countdownAdvancesChallenge && game) { gameplayCountdownIndex = 0;
game->beginNextChallengeLevel(); if (gameplayCountdownSource == CountdownSource::ChallengeLevel && countdownAdvancesChallenge && game) {
game->beginNextChallengeLevel();
}
countdownAdvancesChallenge = false;
game->setPaused(false);
} }
countdownAdvancesChallenge = false;
game->setPaused(false);
} }
} }
} }
@ -1535,6 +1571,7 @@ void TetrisApp::Impl::runLoop()
gameplayCountdownElapsed = 0.0; gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0; gameplayCountdownIndex = 0;
countdownAdvancesChallenge = false; countdownAdvancesChallenge = false;
challengeCountdownWaitingForSpace = false;
game->setPaused(false); game->setPaused(false);
} }
@ -1953,10 +1990,82 @@ void TetrisApp::Impl::runLoop()
float goalY = levelY + static_cast<float>(lvlH) + 14.0f; float goalY = levelY + static_cast<float>(lvlH) + 14.0f;
pixelFont.draw(renderer, goalX, goalY, goalBuf, goalScale, SDL_Color{220, 245, 255, 255}); pixelFont.draw(renderer, goalX, goalY, goalBuf, goalScale, SDL_Color{220, 245, 255, 255});
textY = goalY + static_cast<float>(goalH) + 38.0f; // Optional story/briefing line
if (!challengeStoryText.empty() && challengeStoryAlpha > 0.0f) {
SDL_Color storyColor{170, 230, 255, static_cast<Uint8>(std::lround(255.0f * challengeStoryAlpha))};
SDL_Color shadowColor{0, 0, 0, static_cast<Uint8>(std::lround(160.0f * challengeStoryAlpha))};
auto drawCenteredWrapped = [&](const std::string& text, float y, float maxWidth, float scale) {
std::istringstream iss(text);
std::string word;
std::string line;
float cursorY = y;
int lastH = 0;
while (iss >> word) {
std::string candidate = line.empty() ? word : (line + " " + word);
int candidateW = 0, candidateH = 0;
pixelFont.measure(candidate, scale, candidateW, candidateH);
if (candidateW > maxWidth && !line.empty()) {
int lineW = 0, lineH = 0;
pixelFont.measure(line, scale, lineW, lineH);
float lineX = (winW - static_cast<float>(lineW)) * 0.5f;
pixelFont.draw(renderer, lineX + 1.0f, cursorY + 1.0f, line, scale, shadowColor);
pixelFont.draw(renderer, lineX, cursorY, line, scale, storyColor);
cursorY += lineH + 6.0f;
line = word;
lastH = lineH;
} else {
line = candidate;
lastH = candidateH;
}
}
if (!line.empty()) {
int w = 0, h = 0;
pixelFont.measure(line, scale, w, h);
float lineX = (winW - static_cast<float>(w)) * 0.5f;
pixelFont.draw(renderer, lineX + 1.0f, cursorY + 1.0f, line, scale, shadowColor);
pixelFont.draw(renderer, lineX, cursorY, line, scale, storyColor);
cursorY += h + 6.0f;
}
return cursorY;
};
float storyStartY = goalY + static_cast<float>(goalH) + 22.0f;
float usedY = drawCenteredWrapped(challengeStoryText, storyStartY, std::min<float>(winW * 0.7f, 720.0f), 1.0f);
float promptY = usedY + 10.0f;
if (challengeCountdownWaitingForSpace) {
const char* prompt = "PRESS SPACE";
int pW = 0, pH = 0;
float pScale = 1.35f;
pixelFont.measure(prompt, pScale, pW, pH);
float px = (winW - static_cast<float>(pW)) * 0.5f;
pixelFont.draw(renderer, px + 2.0f, promptY + 2.0f, prompt, pScale, SDL_Color{0, 0, 0, 200});
pixelFont.draw(renderer, px, promptY, prompt, pScale, SDL_Color{255, 220, 40, 255});
promptY += pH + 14.0f;
}
textY = promptY + 10.0f;
} else {
if (challengeCountdownWaitingForSpace) {
const char* prompt = "PRESS SPACE";
int pW = 0, pH = 0;
float pScale = 1.35f;
pixelFont.measure(prompt, pScale, pW, pH);
float px = (winW - static_cast<float>(pW)) * 0.5f;
float py = goalY + static_cast<float>(goalH) + 18.0f;
pixelFont.draw(renderer, px + 2.0f, py + 2.0f, prompt, pScale, SDL_Color{0, 0, 0, 200});
pixelFont.draw(renderer, px, py, prompt, pScale, SDL_Color{255, 220, 40, 255});
textY = py + pH + 24.0f;
} else {
textY = goalY + static_cast<float>(goalH) + 38.0f;
}
}
} else {
textY = winH * 0.38f;
}
if (!(gameplayCountdownSource == CountdownSource::ChallengeLevel && challengeCountdownWaitingForSpace)) {
SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255};
pixelFont.draw(renderer, textX, textY, label, textScale, textColor);
} }
SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255};
pixelFont.draw(renderer, textX, textY, label, textScale, textColor);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
} }

View File

@ -46,7 +46,6 @@ bool SoundEffect::load(const std::string& filePath) {
} }
loaded = true; loaded = true;
//std::printf("[SoundEffect] Loaded: %s (%d channels, %d Hz, %zu samples)\n", filePath.c_str(), channels, sampleRate, pcmData.size());
return true; return true;
} }
@ -55,9 +54,7 @@ void SoundEffect::play(float volume) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Cannot play - loaded=%d, pcmData.size()=%zu", loaded, pcmData.size()); SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Cannot play - loaded=%d, pcmData.size()=%zu", loaded, pcmData.size());
return; return;
} }
//std::printf("[SoundEffect] Playing sound with %zu samples at volume %.2f\n", pcmData.size(), volume);
// Calculate final volume // Calculate final volume
float finalVolume = defaultVolume * volume; float finalVolume = defaultVolume * volume;
finalVolume = (std::max)(0.0f, (std::min)(1.0f, finalVolume)); finalVolume = (std::max)(0.0f, (std::min)(1.0f, finalVolume));

View File

@ -270,8 +270,8 @@ void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
challengeClearOrder, challengeClearOrder,
challengeClearElapsed, challengeClearElapsed,
challengeClearDuration, challengeClearDuration,
ctx.challengeStoryText, countdown ? nullptr : ctx.challengeStoryText,
ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f countdown ? 0.0f : (ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f)
); );
// Reset to screen // Reset to screen
@ -366,8 +366,8 @@ void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
challengeClearOrder, challengeClearOrder,
challengeClearElapsed, challengeClearElapsed,
challengeClearDuration, challengeClearDuration,
ctx.challengeStoryText, countdown ? nullptr : ctx.challengeStoryText,
ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f countdown ? 0.0f : (ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f)
); );
} }
} }