feat: add temporary hazardous barriers in hard mode

This commit is contained in:
phillip committed 2026-10-03 21:32:02 +02:00
1 parent 08a4b41280
commit 9a3a0bd9fd
8 files changed
+466 -17

No files matched your search

+85 -6
View File
@@ -155,7 +155,8 @@ void draw_apples_cursed(const GameState &state, const RenderAssets &assets,
void invalidate_hud(RenderAssets &assets) { assets.hudCache.dirty = true; }
void draw_game(const GameState &state, const RenderAssets &assets) {
uint16_t oamId = 0;
// OAM slots below BARRIER_OAM_SLOTS are owned by draw_barriers() (VBlank)
uint16_t oamId = BARRIER_OAM_SLOTS;
if (!state.gameOver) {
draw_apples(state, assets, oamId);
@@ -180,6 +181,79 @@ void draw_game(const GameState &state, const RenderAssets &assets) {
iprintf("\x1b[11;12HGAME OVER!");
}
void draw_barriers(const GameState &state, const RenderAssets &assets,
uint8_t frame) {
if (state.scene != Scene::Playing || state.gameOver || !state.hardMode) {
oamClear(&oamSub, 0, BARRIER_OAM_SLOTS);
return;
}
// Most imminent warning drives the shared warning colour intensity
uint16_t min_warn_frames = BARRIER_WARN_FRAMES;
for (size_t b = 0; b < state.barriers.size(); b++) {
const Barrier &barrier = state.barriers.at(b);
const int base_slot = static_cast<int>(b) * BARRIER_MAX_LENGTH;
if (barrier.phase == BarrierPhase::Inactive) {
oamClear(&oamSub, base_slot, BARRIER_MAX_LENGTH);
continue;
}
const uint16_t frames_left = barrier.framesLeft;
bool visible = true;
u16 *gfx = assets.gfx_barrier;
if (barrier.phase == BarrierPhase::Warning) {
min_warn_frames = std::min(min_warn_frames, frames_left);
if (frames_left < BARRIER_SOLIDIFY_FRAMES) {
// About to become solid: rapid flicker between outline and solid look
gfx = (((frames_left >> 1U) & 1U) != 0U) ? assets.gfx_barrier
: assets.gfx_barrier_warn;
} else {
// Blink period shrinks from 24 to 4 frames as activation approaches
const int elapsed = BARRIER_WARN_FRAMES - frames_left;
const int span = BARRIER_WARN_FRAMES - BARRIER_SOLIDIFY_FRAMES;
const int period = std::max(4, 24 - ((elapsed * 20) / span));
visible = (elapsed % period) < (period / 2);
gfx = assets.gfx_barrier_warn;
}
} else if (frames_left < BARRIER_FADE_FRAMES) {
// Still fatal, but slowly blinking to signal it's about to vanish
visible = ((frames_left >> 3U) & 1U) == 0U;
}
for (int i = 0; i < BARRIER_MAX_LENGTH; i++) {
const int slot = base_slot + i;
if (i >= barrier.length) {
oamClear(&oamSub, slot, 1);
continue;
}
const int cx = barrier.x + (barrier.vertical ? 0 : i);
const int cy = barrier.y + (barrier.vertical ? i : 0);
oamSet(&oamSub, slot, cx * 8, cy * 8, 0, 0, SpriteSize_8x8,
SpriteColorFormat_256Color, gfx, -1, false, !visible, false, false,
false);
}
}
// Palette animation (shared by all barriers)
const uint8_t pulse = (frame >> 1U) & 7U;
const int wave = (pulse < 4U) ? pulse : (7 - pulse); // 0..3..0
SPRITE_PALETTE_SUB[BARRIER_PAL_EDGE] = static_cast<uint16_t>(RGB15(12, 0, 3));
SPRITE_PALETTE_SUB[BARRIER_PAL_FILL] =
static_cast<uint16_t>(RGB15(22 + (wave * 3), 2 + wave, 4));
SPRITE_PALETTE_SUB[BARRIER_PAL_STRIPE] =
static_cast<uint16_t>(RGB15(31, 22 + (wave * 3), 6 + (wave * 4)));
// Warning colour ramps from dim orange to hot yellow-white
const int urgency =
31 - ((static_cast<int>(min_warn_frames) * 31) / BARRIER_WARN_FRAMES);
SPRITE_PALETTE_SUB[BARRIER_PAL_WARN] = static_cast<uint16_t>(
RGB15(18 + ((urgency * 13) / 31), 4 + ((urgency * 22) / 31),
2 + ((urgency * 10) / 31)));
}
void draw_stats(const GameState &state, RenderAssets &assets, bool internals,
bool musicEnabled, uint8_t frame) {
if (internals) {
@@ -196,7 +270,12 @@ void draw_stats(const GameState &state, RenderAssets &assets, bool internals,
iprintf("\x1b[7;0HSpeed: %u.%01u Hz (%u%%) ",
static_cast<unsigned int>(freq_mhz / 1000U),
static_cast<unsigned int>((freq_mhz % 1000U) / 100U),
static_cast<unsigned int>((static_cast<uint32_t>(intensity) * 100U) / 31U));
static_cast<unsigned int>(
(static_cast<uint32_t>(intensity) * 100U) / 31U));
if (state.hardMode) {
iprintf("\x1b[8;0HBarriers: max len %u ",
static_cast<unsigned int>(calculate_barrier_max_length(state)));
}
}
HudCache &cache = assets.hudCache;
@@ -274,14 +353,14 @@ void draw_menu(const GameState &state, RenderAssets &assets,
iprintf("\x1b[3;10HSNAKE PLUS");
iprintf("\x1b[5;5H---------------------");
const char *hardModeLabel = state.hardMode ? "ON " : "OFF";
const char *musicLabel = musicEnabled ? "ON " : "OFF";
const char *hard_mode_label = state.hardMode ? "ON " : "OFF";
const char *music_label = musicEnabled ? "ON " : "OFF";
iprintf("\x1b[8;7H%c Start Game", state.menuSelection == 0 ? '>' : ' ');
iprintf("\x1b[10;7H%c Hard Mode: %s", state.menuSelection == 1 ? '>' : ' ',
hardModeLabel);
hard_mode_label);
iprintf("\x1b[12;7H%c Music: %s", state.menuSelection == 2 ? '>' : ' ',
musicLabel);
music_label);
iprintf("\x1b[19;3HUP/DOWN: Navigate");
iprintf("\x1b[20;3HA/START: Select");