updated gameplay
This commit is contained in:
Binary file not shown.
Binary file not shown.
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user