used tileset sprite sheet for asteroids
This commit is contained in:
BIN
assets/images/asteroids_001.png
Normal file
BIN
assets/images/asteroids_001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 183 KiB |
@ -137,6 +137,7 @@ struct TetrisApp::Impl {
|
|||||||
int mainScreenH = 0;
|
int mainScreenH = 0;
|
||||||
|
|
||||||
SDL_Texture* blocksTex = nullptr;
|
SDL_Texture* blocksTex = nullptr;
|
||||||
|
SDL_Texture* asteroidsTex = nullptr;
|
||||||
SDL_Texture* scorePanelTex = nullptr;
|
SDL_Texture* scorePanelTex = nullptr;
|
||||||
SDL_Texture* statisticsPanelTex = nullptr;
|
SDL_Texture* statisticsPanelTex = nullptr;
|
||||||
SDL_Texture* nextPanelTex = nullptr;
|
SDL_Texture* nextPanelTex = nullptr;
|
||||||
@ -163,6 +164,7 @@ struct TetrisApp::Impl {
|
|||||||
std::vector<std::string> tripleSounds;
|
std::vector<std::string> tripleSounds;
|
||||||
std::vector<std::string> tetrisSounds;
|
std::vector<std::string> tetrisSounds;
|
||||||
bool suppressLineVoiceForLevelUp = false;
|
bool suppressLineVoiceForLevelUp = false;
|
||||||
|
bool skipNextLevelUpJingle = false;
|
||||||
|
|
||||||
AppState state = AppState::Loading;
|
AppState state = AppState::Loading;
|
||||||
double loadingProgress = 0.0;
|
double loadingProgress = 0.0;
|
||||||
@ -184,12 +186,18 @@ struct TetrisApp::Impl {
|
|||||||
float menuFadeAlpha = 0.0f;
|
float menuFadeAlpha = 0.0f;
|
||||||
double MENU_PLAY_FADE_DURATION_MS = 450.0;
|
double MENU_PLAY_FADE_DURATION_MS = 450.0;
|
||||||
AppState menuFadeTarget = AppState::Menu;
|
AppState menuFadeTarget = AppState::Menu;
|
||||||
|
|
||||||
|
enum class CountdownSource { MenuStart, ChallengeLevel };
|
||||||
bool menuPlayCountdownArmed = false;
|
bool menuPlayCountdownArmed = false;
|
||||||
bool gameplayCountdownActive = false;
|
bool gameplayCountdownActive = false;
|
||||||
double gameplayCountdownElapsed = 0.0;
|
double gameplayCountdownElapsed = 0.0;
|
||||||
int gameplayCountdownIndex = 0;
|
int gameplayCountdownIndex = 0;
|
||||||
double GAMEPLAY_COUNTDOWN_STEP_MS = 400.0;
|
double GAMEPLAY_COUNTDOWN_STEP_MS = 400.0;
|
||||||
std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
|
std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
|
||||||
|
CountdownSource gameplayCountdownSource = CountdownSource::MenuStart;
|
||||||
|
int countdownLevel = 0;
|
||||||
|
int countdownGoalAsteroids = 0;
|
||||||
|
bool countdownAdvancesChallenge = false;
|
||||||
double gameplayBackgroundClockMs = 0.0;
|
double gameplayBackgroundClockMs = 0.0;
|
||||||
|
|
||||||
std::unique_ptr<StateManager> stateMgr;
|
std::unique_ptr<StateManager> stateMgr;
|
||||||
@ -369,8 +377,12 @@ int TetrisApp::Impl::init()
|
|||||||
});
|
});
|
||||||
|
|
||||||
game->setLevelUpCallback([this](int /*newLevel*/) {
|
game->setLevelUpCallback([this](int /*newLevel*/) {
|
||||||
SoundEffectManager::instance().playSound("new_level", 1.0f);
|
if (skipNextLevelUpJingle) {
|
||||||
SoundEffectManager::instance().playSound("lets_go", 1.0f);
|
skipNextLevelUpJingle = false;
|
||||||
|
} else {
|
||||||
|
SoundEffectManager::instance().playSound("new_level", 1.0f);
|
||||||
|
SoundEffectManager::instance().playSound("lets_go", 1.0f);
|
||||||
|
}
|
||||||
suppressLineVoiceForLevelUp = true;
|
suppressLineVoiceForLevelUp = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -419,6 +431,7 @@ int TetrisApp::Impl::init()
|
|||||||
ctx.logoSmallW = logoSmallW;
|
ctx.logoSmallW = logoSmallW;
|
||||||
ctx.logoSmallH = logoSmallH;
|
ctx.logoSmallH = logoSmallH;
|
||||||
ctx.backgroundTex = nullptr;
|
ctx.backgroundTex = nullptr;
|
||||||
|
ctx.asteroidsTex = asteroidsTex;
|
||||||
ctx.blocksTex = blocksTex;
|
ctx.blocksTex = blocksTex;
|
||||||
ctx.scorePanelTex = scorePanelTex;
|
ctx.scorePanelTex = scorePanelTex;
|
||||||
ctx.statisticsPanelTex = statisticsPanelTex;
|
ctx.statisticsPanelTex = statisticsPanelTex;
|
||||||
@ -989,7 +1002,8 @@ void TetrisApp::Impl::runLoop()
|
|||||||
Assets::PANEL_SCORE,
|
Assets::PANEL_SCORE,
|
||||||
Assets::PANEL_STATS,
|
Assets::PANEL_STATS,
|
||||||
Assets::NEXT_PANEL,
|
Assets::NEXT_PANEL,
|
||||||
Assets::HOLD_PANEL
|
Assets::HOLD_PANEL,
|
||||||
|
Assets::ASTEROID_SPRITE
|
||||||
};
|
};
|
||||||
for (auto &p : queuedPaths) {
|
for (auto &p : queuedPaths) {
|
||||||
loadingManager->queueTexture(p);
|
loadingManager->queueTexture(p);
|
||||||
@ -1024,6 +1038,7 @@ void TetrisApp::Impl::runLoop()
|
|||||||
logoSmallTex = assetLoader.getTexture(Assets::LOGO);
|
logoSmallTex = assetLoader.getTexture(Assets::LOGO);
|
||||||
mainScreenTex = assetLoader.getTexture(Assets::MAIN_SCREEN);
|
mainScreenTex = assetLoader.getTexture(Assets::MAIN_SCREEN);
|
||||||
blocksTex = assetLoader.getTexture(Assets::BLOCKS_SPRITE);
|
blocksTex = assetLoader.getTexture(Assets::BLOCKS_SPRITE);
|
||||||
|
asteroidsTex = assetLoader.getTexture(Assets::ASTEROID_SPRITE);
|
||||||
scorePanelTex = assetLoader.getTexture(Assets::PANEL_SCORE);
|
scorePanelTex = assetLoader.getTexture(Assets::PANEL_SCORE);
|
||||||
statisticsPanelTex = assetLoader.getTexture(Assets::PANEL_STATS);
|
statisticsPanelTex = assetLoader.getTexture(Assets::PANEL_STATS);
|
||||||
nextPanelTex = assetLoader.getTexture(Assets::NEXT_PANEL);
|
nextPanelTex = assetLoader.getTexture(Assets::NEXT_PANEL);
|
||||||
@ -1056,6 +1071,7 @@ void TetrisApp::Impl::runLoop()
|
|||||||
legacyLoad(Assets::LOGO, logoSmallTex, &logoSmallW, &logoSmallH);
|
legacyLoad(Assets::LOGO, logoSmallTex, &logoSmallW, &logoSmallH);
|
||||||
legacyLoad(Assets::MAIN_SCREEN, mainScreenTex, &mainScreenW, &mainScreenH);
|
legacyLoad(Assets::MAIN_SCREEN, mainScreenTex, &mainScreenW, &mainScreenH);
|
||||||
legacyLoad(Assets::BLOCKS_SPRITE, blocksTex);
|
legacyLoad(Assets::BLOCKS_SPRITE, blocksTex);
|
||||||
|
legacyLoad(Assets::ASTEROID_SPRITE, asteroidsTex);
|
||||||
legacyLoad(Assets::PANEL_SCORE, scorePanelTex);
|
legacyLoad(Assets::PANEL_SCORE, scorePanelTex);
|
||||||
legacyLoad(Assets::PANEL_STATS, statisticsPanelTex);
|
legacyLoad(Assets::PANEL_STATS, statisticsPanelTex);
|
||||||
legacyLoad(Assets::NEXT_PANEL, nextPanelTex);
|
legacyLoad(Assets::NEXT_PANEL, nextPanelTex);
|
||||||
@ -1208,12 +1224,30 @@ void TetrisApp::Impl::runLoop()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state == AppState::Playing && game && game->getMode() == GameMode::Challenge && !gameplayCountdownActive) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.logoTex = logoTex;
|
ctx.logoTex = logoTex;
|
||||||
ctx.logoSmallTex = logoSmallTex;
|
ctx.logoSmallTex = logoSmallTex;
|
||||||
ctx.logoSmallW = logoSmallW;
|
ctx.logoSmallW = logoSmallW;
|
||||||
ctx.logoSmallH = logoSmallH;
|
ctx.logoSmallH = logoSmallH;
|
||||||
ctx.backgroundTex = backgroundTex;
|
ctx.backgroundTex = backgroundTex;
|
||||||
ctx.blocksTex = blocksTex;
|
ctx.blocksTex = blocksTex;
|
||||||
|
ctx.asteroidsTex = asteroidsTex;
|
||||||
ctx.scorePanelTex = scorePanelTex;
|
ctx.scorePanelTex = scorePanelTex;
|
||||||
ctx.statisticsPanelTex = statisticsPanelTex;
|
ctx.statisticsPanelTex = statisticsPanelTex;
|
||||||
ctx.nextPanelTex = nextPanelTex;
|
ctx.nextPanelTex = nextPanelTex;
|
||||||
@ -1232,6 +1266,12 @@ void TetrisApp::Impl::runLoop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (menuFadeTarget == AppState::Playing) {
|
if (menuFadeTarget == AppState::Playing) {
|
||||||
|
gameplayCountdownSource = (game && game->getMode() == GameMode::Challenge)
|
||||||
|
? CountdownSource::ChallengeLevel
|
||||||
|
: CountdownSource::MenuStart;
|
||||||
|
countdownLevel = game ? game->challengeLevel() : 1;
|
||||||
|
countdownGoalAsteroids = countdownLevel;
|
||||||
|
countdownAdvancesChallenge = false;
|
||||||
menuPlayCountdownArmed = true;
|
menuPlayCountdownArmed = true;
|
||||||
gameplayCountdownActive = false;
|
gameplayCountdownActive = false;
|
||||||
gameplayCountdownIndex = 0;
|
gameplayCountdownIndex = 0;
|
||||||
@ -1259,6 +1299,12 @@ void TetrisApp::Impl::runLoop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (menuFadePhase == MenuFadePhase::None && menuPlayCountdownArmed && !gameplayCountdownActive && state == AppState::Playing) {
|
if (menuFadePhase == MenuFadePhase::None && menuPlayCountdownArmed && !gameplayCountdownActive && state == AppState::Playing) {
|
||||||
|
gameplayCountdownSource = (game && game->getMode() == GameMode::Challenge)
|
||||||
|
? CountdownSource::ChallengeLevel
|
||||||
|
: CountdownSource::MenuStart;
|
||||||
|
countdownLevel = game ? game->challengeLevel() : 1;
|
||||||
|
countdownGoalAsteroids = countdownLevel;
|
||||||
|
countdownAdvancesChallenge = false;
|
||||||
gameplayCountdownActive = true;
|
gameplayCountdownActive = true;
|
||||||
menuPlayCountdownArmed = false;
|
menuPlayCountdownArmed = false;
|
||||||
gameplayCountdownElapsed = 0.0;
|
gameplayCountdownElapsed = 0.0;
|
||||||
@ -1275,6 +1321,10 @@ void TetrisApp::Impl::runLoop()
|
|||||||
gameplayCountdownActive = false;
|
gameplayCountdownActive = false;
|
||||||
gameplayCountdownElapsed = 0.0;
|
gameplayCountdownElapsed = 0.0;
|
||||||
gameplayCountdownIndex = 0;
|
gameplayCountdownIndex = 0;
|
||||||
|
if (gameplayCountdownSource == CountdownSource::ChallengeLevel && countdownAdvancesChallenge && game) {
|
||||||
|
game->beginNextChallengeLevel();
|
||||||
|
}
|
||||||
|
countdownAdvancesChallenge = false;
|
||||||
game->setPaused(false);
|
game->setPaused(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1285,6 +1335,7 @@ void TetrisApp::Impl::runLoop()
|
|||||||
menuPlayCountdownArmed = false;
|
menuPlayCountdownArmed = false;
|
||||||
gameplayCountdownElapsed = 0.0;
|
gameplayCountdownElapsed = 0.0;
|
||||||
gameplayCountdownIndex = 0;
|
gameplayCountdownIndex = 0;
|
||||||
|
countdownAdvancesChallenge = false;
|
||||||
game->setPaused(false);
|
game->setPaused(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1520,6 +1571,7 @@ void TetrisApp::Impl::runLoop()
|
|||||||
&pixelFont,
|
&pixelFont,
|
||||||
&lineEffect,
|
&lineEffect,
|
||||||
blocksTex,
|
blocksTex,
|
||||||
|
asteroidsTex,
|
||||||
ctx.statisticsPanelTex,
|
ctx.statisticsPanelTex,
|
||||||
scorePanelTex,
|
scorePanelTex,
|
||||||
nextPanelTex,
|
nextPanelTex,
|
||||||
@ -1674,6 +1726,27 @@ void TetrisApp::Impl::runLoop()
|
|||||||
|
|
||||||
float textX = (winW - static_cast<float>(textW)) * 0.5f;
|
float textX = (winW - static_cast<float>(textW)) * 0.5f;
|
||||||
float textY = (winH - static_cast<float>(textH)) * 0.5f;
|
float textY = (winH - static_cast<float>(textH)) * 0.5f;
|
||||||
|
if (gameplayCountdownSource == CountdownSource::ChallengeLevel) {
|
||||||
|
char levelBuf[32];
|
||||||
|
std::snprintf(levelBuf, sizeof(levelBuf), "LEVEL %d", countdownLevel);
|
||||||
|
int lvlW = 0, lvlH = 0;
|
||||||
|
float lvlScale = 2.5f;
|
||||||
|
pixelFont.measure(levelBuf, lvlScale, lvlW, lvlH);
|
||||||
|
float levelX = (winW - static_cast<float>(lvlW)) * 0.5f;
|
||||||
|
float levelY = winH * 0.32f;
|
||||||
|
pixelFont.draw(renderer, levelX, levelY, levelBuf, lvlScale, SDL_Color{140, 210, 255, 255});
|
||||||
|
|
||||||
|
char goalBuf[64];
|
||||||
|
std::snprintf(goalBuf, sizeof(goalBuf), "ASTEROIDS: %d", countdownGoalAsteroids);
|
||||||
|
int goalW = 0, goalH = 0;
|
||||||
|
float goalScale = 1.7f;
|
||||||
|
pixelFont.measure(goalBuf, goalScale, goalW, goalH);
|
||||||
|
float goalX = (winW - static_cast<float>(goalW)) * 0.5f;
|
||||||
|
float goalY = levelY + static_cast<float>(lvlH) + 14.0f;
|
||||||
|
pixelFont.draw(renderer, goalX, goalY, goalBuf, goalScale, SDL_Color{220, 245, 255, 255});
|
||||||
|
|
||||||
|
textY = goalY + static_cast<float>(goalH) + 38.0f;
|
||||||
|
}
|
||||||
SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255};
|
SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255};
|
||||||
pixelFont.draw(renderer, textX, textY, label, textScale, textColor);
|
pixelFont.draw(renderer, textX, textY, label, textScale, textColor);
|
||||||
|
|
||||||
|
|||||||
@ -643,6 +643,7 @@ bool ApplicationManager::initializeGame() {
|
|||||||
} else { m_stateContext.logoSmallW = 0; m_stateContext.logoSmallH = 0; }
|
} else { m_stateContext.logoSmallW = 0; m_stateContext.logoSmallH = 0; }
|
||||||
m_stateContext.backgroundTex = m_assetManager->getTexture("background");
|
m_stateContext.backgroundTex = m_assetManager->getTexture("background");
|
||||||
m_stateContext.blocksTex = m_assetManager->getTexture("blocks");
|
m_stateContext.blocksTex = m_assetManager->getTexture("blocks");
|
||||||
|
m_stateContext.asteroidsTex = m_assetManager->getTexture("asteroids");
|
||||||
m_stateContext.musicEnabled = &m_musicEnabled;
|
m_stateContext.musicEnabled = &m_musicEnabled;
|
||||||
m_stateContext.musicStarted = &m_musicStarted;
|
m_stateContext.musicStarted = &m_musicStarted;
|
||||||
m_stateContext.musicLoaded = &m_musicLoaded;
|
m_stateContext.musicLoaded = &m_musicLoaded;
|
||||||
@ -1162,6 +1163,7 @@ void ApplicationManager::setupStateHandlers() {
|
|||||||
m_stateContext.pixelFont,
|
m_stateContext.pixelFont,
|
||||||
m_stateContext.lineEffect,
|
m_stateContext.lineEffect,
|
||||||
m_stateContext.blocksTex,
|
m_stateContext.blocksTex,
|
||||||
|
m_stateContext.asteroidsTex,
|
||||||
m_stateContext.statisticsPanelTex,
|
m_stateContext.statisticsPanelTex,
|
||||||
m_stateContext.scorePanelTex,
|
m_stateContext.scorePanelTex,
|
||||||
m_stateContext.nextPanelTex,
|
m_stateContext.nextPanelTex,
|
||||||
|
|||||||
@ -64,6 +64,8 @@ void Game::reset(int startLevel_) {
|
|||||||
_comboCount = 0;
|
_comboCount = 0;
|
||||||
challengeComplete = false;
|
challengeComplete = false;
|
||||||
challengeLevelActive = false;
|
challengeLevelActive = false;
|
||||||
|
challengeAdvanceQueued = false;
|
||||||
|
challengeQueuedLevel = 0;
|
||||||
// Initialize gravity using NES timing table (ms per cell by level)
|
// Initialize gravity using NES timing table (ms per cell by level)
|
||||||
gravityMs = gravityMsForLevel(_level, gravityGlobalMultiplier);
|
gravityMs = gravityMsForLevel(_level, gravityGlobalMultiplier);
|
||||||
fallAcc = 0; gameOver=false; paused=false;
|
fallAcc = 0; gameOver=false; paused=false;
|
||||||
@ -109,6 +111,8 @@ void Game::startChallengeRun(int startingLevel) {
|
|||||||
challengeSeedBase = static_cast<uint32_t>(SDL_GetTicks());
|
challengeSeedBase = static_cast<uint32_t>(SDL_GetTicks());
|
||||||
}
|
}
|
||||||
challengeRng.seed(challengeSeedBase + static_cast<uint32_t>(lvl));
|
challengeRng.seed(challengeSeedBase + static_cast<uint32_t>(lvl));
|
||||||
|
challengeAdvanceQueued = false;
|
||||||
|
challengeQueuedLevel = 0;
|
||||||
setupChallengeLevel(lvl, false);
|
setupChallengeLevel(lvl, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +120,8 @@ void Game::beginNextChallengeLevel() {
|
|||||||
if (mode != GameMode::Challenge || challengeComplete) {
|
if (mode != GameMode::Challenge || challengeComplete) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
challengeAdvanceQueued = false;
|
||||||
|
challengeQueuedLevel = 0;
|
||||||
int next = challengeLevelIndex + 1;
|
int next = challengeLevelIndex + 1;
|
||||||
if (next > ASTEROID_MAX_LEVEL) {
|
if (next > ASTEROID_MAX_LEVEL) {
|
||||||
challengeComplete = true;
|
challengeComplete = true;
|
||||||
@ -131,6 +137,8 @@ void Game::setupChallengeLevel(int level, bool preserveStats) {
|
|||||||
startLevel = challengeLevelIndex;
|
startLevel = challengeLevelIndex;
|
||||||
challengeComplete = false;
|
challengeComplete = false;
|
||||||
challengeLevelActive = true;
|
challengeLevelActive = true;
|
||||||
|
challengeAdvanceQueued = false;
|
||||||
|
challengeQueuedLevel = 0;
|
||||||
// Refresh deterministic RNG for this level
|
// Refresh deterministic RNG for this level
|
||||||
challengeRng.seed(challengeSeedBase + static_cast<uint32_t>(challengeLevelIndex));
|
challengeRng.seed(challengeSeedBase + static_cast<uint32_t>(challengeLevelIndex));
|
||||||
|
|
||||||
@ -318,6 +326,16 @@ void Game::setPaused(bool p) {
|
|||||||
paused = p;
|
paused = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Game::consumeQueuedChallengeLevel() {
|
||||||
|
if (!challengeAdvanceQueued) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int next = challengeQueuedLevel;
|
||||||
|
challengeAdvanceQueued = false;
|
||||||
|
challengeQueuedLevel = 0;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
void Game::setSoftDropping(bool on) {
|
void Game::setSoftDropping(bool on) {
|
||||||
if (softDropping == on) {
|
if (softDropping == on) {
|
||||||
return;
|
return;
|
||||||
@ -521,7 +539,18 @@ void Game::actualClearLines() {
|
|||||||
|
|
||||||
if (mode == GameMode::Challenge) {
|
if (mode == GameMode::Challenge) {
|
||||||
if (asteroidsRemainingCount <= 0) {
|
if (asteroidsRemainingCount <= 0) {
|
||||||
beginNextChallengeLevel();
|
int nextLevel = challengeLevelIndex + 1;
|
||||||
|
if (nextLevel > ASTEROID_MAX_LEVEL) {
|
||||||
|
challengeComplete = true;
|
||||||
|
challengeLevelActive = false;
|
||||||
|
challengeAdvanceQueued = false;
|
||||||
|
challengeQueuedLevel = 0;
|
||||||
|
} else {
|
||||||
|
challengeAdvanceQueued = true;
|
||||||
|
challengeQueuedLevel = nextLevel;
|
||||||
|
challengeLevelActive = false;
|
||||||
|
setPaused(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,10 @@ public:
|
|||||||
int asteroidsRemaining() const { return asteroidsRemainingCount; }
|
int asteroidsRemaining() const { return asteroidsRemainingCount; }
|
||||||
int asteroidsTotal() const { return asteroidsTotalThisLevel; }
|
int asteroidsTotal() const { return asteroidsTotalThisLevel; }
|
||||||
bool isChallengeComplete() const { return challengeComplete; }
|
bool isChallengeComplete() const { return challengeComplete; }
|
||||||
|
bool isChallengeLevelActive() const { return challengeLevelActive; }
|
||||||
|
bool isChallengeAdvanceQueued() const { return challengeAdvanceQueued; }
|
||||||
|
int queuedChallengeLevel() const { return challengeQueuedLevel; }
|
||||||
|
int consumeQueuedChallengeLevel(); // returns next level if queued, else 0
|
||||||
int startLevelBase() const { return startLevel; }
|
int startLevelBase() const { return startLevel; }
|
||||||
double elapsed() const; // Now calculated from start time
|
double elapsed() const; // Now calculated from start time
|
||||||
void updateElapsedTime(); // Update elapsed time from system clock
|
void updateElapsedTime(); // Update elapsed time from system clock
|
||||||
@ -168,6 +172,8 @@ private:
|
|||||||
uint32_t challengeSeedBase{0};
|
uint32_t challengeSeedBase{0};
|
||||||
std::mt19937 challengeRng{ std::random_device{}() };
|
std::mt19937 challengeRng{ std::random_device{}() };
|
||||||
bool challengeLevelActive{false};
|
bool challengeLevelActive{false};
|
||||||
|
bool challengeAdvanceQueued{false};
|
||||||
|
int challengeQueuedLevel{0};
|
||||||
|
|
||||||
// Internal helpers ----------------------------------------------------
|
// Internal helpers ----------------------------------------------------
|
||||||
void refillBag();
|
void refillBag();
|
||||||
|
|||||||
@ -282,6 +282,62 @@ void GameRenderer::drawRect(SDL_Renderer* renderer, float x, float y, float w, f
|
|||||||
SDL_RenderFillRect(renderer, &fr);
|
SDL_RenderFillRect(renderer, &fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void drawAsteroid(SDL_Renderer* renderer, SDL_Texture* asteroidTex, float x, float y, float size, const AsteroidCell& cell) {
|
||||||
|
auto outlineGravity = [&](float inset, SDL_Color color) {
|
||||||
|
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||||
|
SDL_FRect glow{ x + inset, y + inset, size - inset * 2.0f, size - inset * 2.0f };
|
||||||
|
SDL_RenderRect(renderer, &glow);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (asteroidTex) {
|
||||||
|
const float SPRITE_SIZE = 90.0f;
|
||||||
|
int col = 0;
|
||||||
|
switch (cell.type) {
|
||||||
|
case AsteroidType::Normal: col = 0; break;
|
||||||
|
case AsteroidType::Armored: col = 1; break;
|
||||||
|
case AsteroidType::Falling: col = 2; break;
|
||||||
|
case AsteroidType::Core: col = 3; break;
|
||||||
|
}
|
||||||
|
int row = std::clamp<int>(cell.visualState, 0, 2);
|
||||||
|
SDL_FRect src{ col * SPRITE_SIZE, row * SPRITE_SIZE, SPRITE_SIZE, SPRITE_SIZE };
|
||||||
|
SDL_FRect dst{ x, y, size, size };
|
||||||
|
SDL_RenderTexture(renderer, asteroidTex, &src, &dst);
|
||||||
|
|
||||||
|
if (cell.gravityEnabled) {
|
||||||
|
outlineGravity(2.0f, SDL_Color{255, 230, 120, 180});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: draw a colored quad (previous implementation)
|
||||||
|
SDL_Color base{};
|
||||||
|
switch (cell.type) {
|
||||||
|
case AsteroidType::Normal: base = SDL_Color{172, 138, 104, 255}; break;
|
||||||
|
case AsteroidType::Armored: base = SDL_Color{130, 150, 176, 255}; break;
|
||||||
|
case AsteroidType::Falling: base = SDL_Color{210, 120, 82, 255}; break;
|
||||||
|
case AsteroidType::Core: base = SDL_Color{198, 78, 200, 255}; break;
|
||||||
|
}
|
||||||
|
float hpScale = std::clamp(static_cast<float>(cell.hitsRemaining) / 3.0f, 0.25f, 1.0f);
|
||||||
|
SDL_Color fill{
|
||||||
|
static_cast<Uint8>(base.r * hpScale + 40 * (1.0f - hpScale)),
|
||||||
|
static_cast<Uint8>(base.g * hpScale + 40 * (1.0f - hpScale)),
|
||||||
|
static_cast<Uint8>(base.b * hpScale + 40 * (1.0f - hpScale)),
|
||||||
|
255
|
||||||
|
};
|
||||||
|
SDL_SetRenderDrawColor(renderer, fill.r, fill.g, fill.b, fill.a);
|
||||||
|
SDL_FRect body{x, y, size - 1.0f, size - 1.0f};
|
||||||
|
SDL_RenderFillRect(renderer, &body);
|
||||||
|
|
||||||
|
SDL_Color outline = base;
|
||||||
|
outline.a = 220;
|
||||||
|
SDL_FRect border{x + 1.0f, y + 1.0f, size - 2.0f, size - 2.0f};
|
||||||
|
SDL_SetRenderDrawColor(renderer, outline.r, outline.g, outline.b, outline.a);
|
||||||
|
SDL_RenderRect(renderer, &border);
|
||||||
|
if (cell.gravityEnabled) {
|
||||||
|
outlineGravity(2.0f, SDL_Color{255, 230, 120, 180});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameRenderer::drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType) {
|
void GameRenderer::drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType) {
|
||||||
if (!blocksTex || blockType < 0 || blockType >= PIECE_COUNT) {
|
if (!blocksTex || blockType < 0 || blockType >= PIECE_COUNT) {
|
||||||
// Fallback to colored rectangle if texture isn't available
|
// Fallback to colored rectangle if texture isn't available
|
||||||
@ -515,6 +571,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
FontAtlas* pixelFont,
|
FontAtlas* pixelFont,
|
||||||
LineEffect* lineEffect,
|
LineEffect* lineEffect,
|
||||||
SDL_Texture* blocksTex,
|
SDL_Texture* blocksTex,
|
||||||
|
SDL_Texture* asteroidsTex,
|
||||||
SDL_Texture* statisticsPanelTex,
|
SDL_Texture* statisticsPanelTex,
|
||||||
SDL_Texture* scorePanelTex,
|
SDL_Texture* scorePanelTex,
|
||||||
SDL_Texture* nextPanelTex,
|
SDL_Texture* nextPanelTex,
|
||||||
@ -960,32 +1017,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
bool isAsteroid = challengeMode && asteroidCells[cellIdx].has_value();
|
bool isAsteroid = challengeMode && asteroidCells[cellIdx].has_value();
|
||||||
if (isAsteroid) {
|
if (isAsteroid) {
|
||||||
const AsteroidCell& cell = *asteroidCells[cellIdx];
|
const AsteroidCell& cell = *asteroidCells[cellIdx];
|
||||||
SDL_Color base{};
|
drawAsteroid(renderer, asteroidsTex, bx, by, finalBlockSize, cell);
|
||||||
switch (cell.type) {
|
|
||||||
case AsteroidType::Normal: base = SDL_Color{172, 138, 104, 255}; break;
|
|
||||||
case AsteroidType::Armored: base = SDL_Color{130, 150, 176, 255}; break;
|
|
||||||
case AsteroidType::Falling: base = SDL_Color{210, 120, 82, 255}; break;
|
|
||||||
case AsteroidType::Core: base = SDL_Color{198, 78, 200, 255}; break;
|
|
||||||
}
|
|
||||||
float hpScale = std::clamp(static_cast<float>(cell.hitsRemaining) / 3.0f, 0.25f, 1.0f);
|
|
||||||
SDL_Color fill{
|
|
||||||
static_cast<Uint8>(base.r * hpScale + 40 * (1.0f - hpScale)),
|
|
||||||
static_cast<Uint8>(base.g * hpScale + 40 * (1.0f - hpScale)),
|
|
||||||
static_cast<Uint8>(base.b * hpScale + 40 * (1.0f - hpScale)),
|
|
||||||
255
|
|
||||||
};
|
|
||||||
drawRect(renderer, bx, by, finalBlockSize - 1, finalBlockSize - 1, fill);
|
|
||||||
// Subtle outline to differentiate types
|
|
||||||
SDL_Color outline = base;
|
|
||||||
outline.a = 220;
|
|
||||||
SDL_FRect border{bx + 1.0f, by + 1.0f, finalBlockSize - 2.0f, finalBlockSize - 2.0f};
|
|
||||||
SDL_SetRenderDrawColor(renderer, outline.r, outline.g, outline.b, outline.a);
|
|
||||||
SDL_RenderRect(renderer, &border);
|
|
||||||
if (cell.gravityEnabled) {
|
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 230, 120, 180);
|
|
||||||
SDL_FRect glow{bx + 2.0f, by + 2.0f, finalBlockSize - 4.0f, finalBlockSize - 4.0f};
|
|
||||||
SDL_RenderRect(renderer, &glow);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
drawBlockTexture(renderer, blocksTex, bx, by, finalBlockSize, v - 1);
|
drawBlockTexture(renderer, blocksTex, bx, by, finalBlockSize, v - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public:
|
|||||||
FontAtlas* pixelFont,
|
FontAtlas* pixelFont,
|
||||||
LineEffect* lineEffect,
|
LineEffect* lineEffect,
|
||||||
SDL_Texture* blocksTex,
|
SDL_Texture* blocksTex,
|
||||||
|
SDL_Texture* asteroidsTex,
|
||||||
SDL_Texture* statisticsPanelTex,
|
SDL_Texture* statisticsPanelTex,
|
||||||
SDL_Texture* scorePanelTex,
|
SDL_Texture* scorePanelTex,
|
||||||
SDL_Texture* nextPanelTex,
|
SDL_Texture* nextPanelTex,
|
||||||
|
|||||||
@ -7,7 +7,8 @@ namespace Assets {
|
|||||||
|
|
||||||
inline constexpr const char* LOGO = "assets/images/spacetris.png";
|
inline constexpr const char* LOGO = "assets/images/spacetris.png";
|
||||||
inline constexpr const char* MAIN_SCREEN = "assets/images/main_screen.png";
|
inline constexpr const char* MAIN_SCREEN = "assets/images/main_screen.png";
|
||||||
inline constexpr const char* BLOCKS_SPRITE = "assets/images/blocks90px_003.png";
|
inline constexpr const char* BLOCKS_SPRITE = "assets/images/blocks90px_003.png";
|
||||||
|
inline constexpr const char* ASTEROID_SPRITE = "assets/images/asteroids_001.png";
|
||||||
inline constexpr const char* PANEL_SCORE = "assets/images/panel_score.png";
|
inline constexpr const char* PANEL_SCORE = "assets/images/panel_score.png";
|
||||||
inline constexpr const char* PANEL_STATS = "assets/images/statistics_panel.png";
|
inline constexpr const char* PANEL_STATS = "assets/images/statistics_panel.png";
|
||||||
inline constexpr const char* NEXT_PANEL = "assets/images/next_panel.png";
|
inline constexpr const char* NEXT_PANEL = "assets/images/next_panel.png";
|
||||||
|
|||||||
@ -241,6 +241,7 @@ void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
|||||||
ctx.pixelFont,
|
ctx.pixelFont,
|
||||||
ctx.lineEffect,
|
ctx.lineEffect,
|
||||||
ctx.blocksTex,
|
ctx.blocksTex,
|
||||||
|
ctx.asteroidsTex,
|
||||||
ctx.statisticsPanelTex,
|
ctx.statisticsPanelTex,
|
||||||
ctx.scorePanelTex,
|
ctx.scorePanelTex,
|
||||||
ctx.nextPanelTex,
|
ctx.nextPanelTex,
|
||||||
@ -329,6 +330,7 @@ void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
|||||||
ctx.pixelFont,
|
ctx.pixelFont,
|
||||||
ctx.lineEffect,
|
ctx.lineEffect,
|
||||||
ctx.blocksTex,
|
ctx.blocksTex,
|
||||||
|
ctx.asteroidsTex,
|
||||||
ctx.statisticsPanelTex,
|
ctx.statisticsPanelTex,
|
||||||
ctx.scorePanelTex,
|
ctx.scorePanelTex,
|
||||||
ctx.nextPanelTex,
|
ctx.nextPanelTex,
|
||||||
|
|||||||
@ -40,6 +40,7 @@ struct StateContext {
|
|||||||
// backgroundTex is set once in `main.cpp` and passed to states via this context.
|
// backgroundTex is set once in `main.cpp` and passed to states via this context.
|
||||||
// Prefer reading this field instead of relying on any `extern SDL_Texture*` globals.
|
// Prefer reading this field instead of relying on any `extern SDL_Texture*` globals.
|
||||||
SDL_Texture* blocksTex = nullptr;
|
SDL_Texture* blocksTex = nullptr;
|
||||||
|
SDL_Texture* asteroidsTex = nullptr;
|
||||||
SDL_Texture* scorePanelTex = nullptr;
|
SDL_Texture* scorePanelTex = nullptr;
|
||||||
SDL_Texture* statisticsPanelTex = nullptr;
|
SDL_Texture* statisticsPanelTex = nullptr;
|
||||||
SDL_Texture* nextPanelTex = nullptr;
|
SDL_Texture* nextPanelTex = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user