feat: add temporary hazardous barriers in hard mode
This commit is contained in:
1 parent
08a4b41280
commit
9a3a0bd9fd
8 files changed
+466
-17
No files matched your search
@@ -102,8 +102,9 @@ The game operates in one of two scenes ([`Scene`](include/game_state.h#L9-L12)):
|
|||||||
|
|
||||||
### 3.4 Game Modes & Progressive Speed Mechanics
|
### 3.4 Game Modes & Progressive Speed Mechanics
|
||||||
- **Normal Mode:** Starts with length 4, base timer frequency 5.0 Hz (`SPEED_BASE_NORMAL_MHZ = 5000`), scaling up to 13.0 Hz (`SPEED_MAX_NORMAL_MHZ = 13000`).
|
- **Normal Mode:** Starts with length 4, base timer frequency 5.0 Hz (`SPEED_BASE_NORMAL_MHZ = 5000`), scaling up to 13.0 Hz (`SPEED_MAX_NORMAL_MHZ = 13000`).
|
||||||
- **Hard Mode:** Starts with length 2, base timer frequency 9.0 Hz (`SPEED_BASE_HARD_MHZ = 9000`), scaling up to 17.0 Hz (`SPEED_MAX_HARD_MHZ = 17000`), and spawns rotten apples.
|
- **Hard Mode:** Starts with length 2, base timer frequency 5.0 Hz (`SPEED_BASE_HARD_MHZ = 5000`, matching Normal Mode's starting speed), scaling up to 17.0 Hz (`SPEED_MAX_HARD_MHZ = 17000`), spawns rotten apples, and spawns temporary fatal barriers.
|
||||||
- **Progressive Speed Scaling:** Each consumed regular apple increases `state.applesEaten`. Game speed scales linearly with consumed apples up to `SPEED_APPLES_CAP = 40`, granting smooth +200 mHz acceleration per apple. Timer 0 reload registers are recomputed on the fly (`calculate_timer_reload()`).
|
- **Temporary Fatal Barriers (Hard Mode):** Straight lines of blocks appear dynamically across the playfield without overlapping the snake or apples. Barriers only begin spawning after a few apples have been eaten (`BARRIER_START_APPLES = 3`). Their maximum possible length begins at 4 blocks (`BARRIER_BASE_MAX_LENGTH`) and gradually scales up to 10 blocks (`BARRIER_MAX_LENGTH`) later on in the game. At later stages of the game, multiple barriers can spawn simultaneously in a single spawn event (up to 2 at once starting at 16 apples, and up to 3 at once starting at 30 apples), synchronizing their warning and activation phases. Before becoming lethal, each barrier announces itself with a flashing warning reticle frame (`BARRIER_WARN_FRAMES = 120` frames / 2.0 s) whose pulse frequency accelerates and warning color intensifies from dim orange to incandescent yellow-white as solidification approaches. Once active, barriers turn into solid hazard-striped energy walls (`BARRIER_ACTIVE_FRAMES = 240` frames / 4.0 s) fatal to the snake's head on contact. As apples are eaten, spawn intervals accelerate from 9.0 s down to 2.0 s (`BARRIER_INTERVAL_MAX_FRAMES` to `BARRIER_INTERVAL_MIN_FRAMES`) and max concurrent barriers scale up from 1 to 4 (`BARRIER_MAX_COUNT`).
|
||||||
|
- **Progressive Speed Scaling:** Each consumed regular apple increases `state.applesEaten`. Game speed scales linearly with consumed apples up to `SPEED_APPLES_CAP = 40`, granting smooth +200 mHz acceleration per apple (or +300 mHz in Hard Mode). Timer 0 reload registers are recomputed on the fly (`calculate_timer_reload()`).
|
||||||
- **Progressive Background Intensity Scaling:** Visual effects intensify at the exact same rate (`calculate_intensity_31()`):
|
- **Progressive Background Intensity Scaling:** Visual effects intensify at the exact same rate (`calculate_intensity_31()`):
|
||||||
- Hardware drift scroll speeds accelerate on both top and bottom screens.
|
- Hardware drift scroll speeds accelerate on both top and bottom screens.
|
||||||
- Celestial node oscillation & twinkling breathing frequencies increase via fixed-point phase accumulation (`anim_phase_fp`).
|
- Celestial node oscillation & twinkling breathing frequencies increase via fixed-point phase accumulation (`anim_phase_fp`).
|
||||||
|
|||||||
+33
-3
@@ -1,6 +1,7 @@
|
|||||||
#ifndef CONSTS_H
|
#ifndef CONSTS_H
|
||||||
#define CONSTS_H
|
#define CONSTS_H
|
||||||
#endif
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
const int START_LENGTH = 4;
|
const int START_LENGTH = 4;
|
||||||
const int START_LENGTH_HARD = 2;
|
const int START_LENGTH_HARD = 2;
|
||||||
@@ -14,9 +15,36 @@ const uint16_t CURSE_PAL_GLOW = 18;
|
|||||||
const uint16_t CURSE_PAL_CORE = 19;
|
const uint16_t CURSE_PAL_CORE = 19;
|
||||||
const uint16_t CURSE_PAL_LEAF = 20;
|
const uint16_t CURSE_PAL_LEAF = 20;
|
||||||
|
|
||||||
|
// Barrier palette indices in SPRITE_PALETTE_SUB (animated every VBlank)
|
||||||
|
const uint16_t BARRIER_PAL_EDGE = 21;
|
||||||
|
const uint16_t BARRIER_PAL_FILL = 22;
|
||||||
|
const uint16_t BARRIER_PAL_STRIPE = 23;
|
||||||
|
const uint16_t BARRIER_PAL_WARN = 24;
|
||||||
|
|
||||||
|
// Hard-mode temporary fatal barriers (all timings in 60 Hz VBlank frames,
|
||||||
|
// so they stay constant in wall-clock time while the tick rate accelerates)
|
||||||
|
const int BARRIER_START_APPLES = 3; // Barriers only start appearing after eating a few apples
|
||||||
|
const int BARRIER_MULTI_SPAWN_APPLES_2 = 16; // Apples required to spawn up to 2 barriers at once
|
||||||
|
const int BARRIER_MULTI_SPAWN_APPLES_3 = 30; // Apples required to spawn up to 3 barriers at once
|
||||||
|
const int BARRIER_MAX_COUNT = 4; // Max simultaneous barriers (reached at SPEED_APPLES_CAP)
|
||||||
|
const int BARRIER_MIN_LENGTH = 2;
|
||||||
|
const int BARRIER_BASE_MAX_LENGTH = 4; // Max length when barriers first appear
|
||||||
|
const int BARRIER_MAX_LENGTH = 10; // Max length later in the game (reached at SPEED_APPLES_CAP)
|
||||||
|
const int BARRIER_OAM_SLOTS = BARRIER_MAX_COUNT * BARRIER_MAX_LENGTH; // Reserved OAM ids 0..39
|
||||||
|
const int BARRIER_WARN_FRAMES = 120; // 2.0 s flashing announcement (harmless)
|
||||||
|
const int BARRIER_SOLIDIFY_FRAMES = 20; // Last part of warning: flickers to solid look
|
||||||
|
const int BARRIER_ACTIVE_FRAMES = 240; // 4.0 s fatal phase
|
||||||
|
const int BARRIER_FADE_FRAMES = 45; // Last part of active phase: slow blink (still fatal)
|
||||||
|
const int BARRIER_FIRST_SPAWN_FRAMES = 180; // 3.0 s grace period after reaching BARRIER_START_APPLES
|
||||||
|
const int BARRIER_INTERVAL_MAX_FRAMES = 540; // 9.0 s between spawns at BARRIER_START_APPLES
|
||||||
|
const int BARRIER_INTERVAL_MIN_FRAMES = 120; // 2.0 s between spawns at SPEED_APPLES_CAP
|
||||||
|
const int BARRIER_INTERVAL_JITTER = 60; // Random 0..1 s extra delay
|
||||||
|
const int BARRIER_RETRY_FRAMES = 30; // Retry delay if no valid spot was found
|
||||||
|
const int BARRIER_HEAD_SAFE_DISTANCE = 5; // Min wrapped Manhattan distance from the snake head
|
||||||
|
|
||||||
// Speed scaling configuration (gradual acceleration based on consumed apples)
|
// Speed scaling configuration (gradual acceleration based on consumed apples)
|
||||||
const int SPEED_BASE_NORMAL_MHZ = 5000; // 5.0 Hz (~200 ms per step)
|
const int SPEED_BASE_NORMAL_MHZ = 5000; // 5.0 Hz (~200 ms per step)
|
||||||
const int SPEED_BASE_HARD_MHZ = 9000; // 9.0 Hz (~111 ms per step)
|
const int SPEED_BASE_HARD_MHZ = 5000; // 5.0 Hz: hard mode starts as slow as normal mode
|
||||||
const int SPEED_MAX_NORMAL_MHZ = 13000; // 13.0 Hz (~77 ms per step)
|
const int SPEED_MAX_NORMAL_MHZ = 13000; // 13.0 Hz (~77 ms per step)
|
||||||
const int SPEED_MAX_HARD_MHZ = 17000; // 17.0 Hz (~59 ms per step)
|
const int SPEED_MAX_HARD_MHZ = 17000; // 17.0 Hz (~59 ms per step)
|
||||||
const int SPEED_APPLES_CAP = 40; // Apples consumed to reach max intensity & speed
|
const int SPEED_APPLES_CAP = 40; // Apples consumed to reach max intensity & speed
|
||||||
@@ -69,4 +97,6 @@ const char BITMAP_SEGMENT[] = {
|
|||||||
5,6,6,6,6,6,6,5,
|
5,6,6,6,6,6,6,5,
|
||||||
5,6,6,6,6,6,6,5,
|
5,6,6,6,6,6,6,5,
|
||||||
0,5,5,5,5,5,5,0
|
0,5,5,5,5,5,5,0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // CONSTS_H
|
||||||
@@ -17,4 +17,14 @@ uint8_t calculate_intensity_31(const GameState &state);
|
|||||||
uint16_t calculate_timer_freq_mhz(const GameState &state);
|
uint16_t calculate_timer_freq_mhz(const GameState &state);
|
||||||
uint16_t calculate_timer_reload(const GameState &state);
|
uint16_t calculate_timer_reload(const GameState &state);
|
||||||
|
|
||||||
|
// Hard-mode temporary barriers
|
||||||
|
void reset_barriers(GameState &state); // Main thread (init_game)
|
||||||
|
void tick_barriers_frame(GameState &state); // VBlank IRQ: advances lifetimes
|
||||||
|
void spawn_barriers(GameState &state); // Timer IRQ: spawns when cooldown expired
|
||||||
|
bool check_collision_barriers(GameState &state); // Timer IRQ: head vs. active barrier
|
||||||
|
bool is_barrier_cell(const GameState &state, int x, int y, bool include_warning);
|
||||||
|
uint16_t calculate_barrier_interval_frames(const GameState &state);
|
||||||
|
uint8_t calculate_barrier_max_length(const GameState &state);
|
||||||
|
uint8_t calculate_barrier_max_batch(const GameState &state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -32,6 +32,8 @@ struct RenderAssets {
|
|||||||
u16 *gfx_tail_v[2] = {nullptr, nullptr};
|
u16 *gfx_tail_v[2] = {nullptr, nullptr};
|
||||||
u16 *gfx_apple_rotten = nullptr;
|
u16 *gfx_apple_rotten = nullptr;
|
||||||
u16 *gfx_apple_cursed = nullptr;
|
u16 *gfx_apple_cursed = nullptr;
|
||||||
|
u16 *gfx_barrier = nullptr; // Solid, fatal barrier segment
|
||||||
|
u16 *gfx_barrier_warn = nullptr; // Dashed outline shown while announcing
|
||||||
|
|
||||||
HudCache hudCache;
|
HudCache hudCache;
|
||||||
};
|
};
|
||||||
@@ -41,5 +43,7 @@ void draw_game(const GameState &state, const RenderAssets &assets);
|
|||||||
void draw_stats(const GameState &state, RenderAssets &assets, bool internals, bool musicEnabled, uint8_t frame);
|
void draw_stats(const GameState &state, RenderAssets &assets, bool internals, bool musicEnabled, uint8_t frame);
|
||||||
void draw_menu(const GameState &state, RenderAssets &assets, bool musicEnabled);
|
void draw_menu(const GameState &state, RenderAssets &assets, bool musicEnabled);
|
||||||
void blink_apples(const GameState &state, u16 *gfx_apple);
|
void blink_apples(const GameState &state, u16 *gfx_apple);
|
||||||
|
// Called every VBlank: owns OAM slots [0, BARRIER_OAM_SLOTS) and animates barrier palette entries
|
||||||
|
void draw_barriers(const GameState &state, const RenderAssets &assets, uint8_t frame);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <nds/ndstypes.h>
|
#include <nds/ndstypes.h>
|
||||||
|
|
||||||
|
#include "consts.h"
|
||||||
|
|
||||||
enum class Scene {
|
enum class Scene {
|
||||||
Menu,
|
Menu,
|
||||||
Playing,
|
Playing,
|
||||||
@@ -13,6 +15,23 @@ enum class Scene {
|
|||||||
|
|
||||||
constexpr int MENU_ITEM_COUNT = 3;
|
constexpr int MENU_ITEM_COUNT = 3;
|
||||||
|
|
||||||
|
enum class BarrierPhase : uint8_t {
|
||||||
|
Inactive, // Slot free
|
||||||
|
Warning, // Flashing announcement, harmless
|
||||||
|
Active, // Solid and fatal to the snake head
|
||||||
|
};
|
||||||
|
|
||||||
|
// Temporary hard-mode wall: a straight line of `length` cells starting at (x, y).
|
||||||
|
// Lifetime is tracked in VBlank frames (see tick_barriers_frame()).
|
||||||
|
struct Barrier {
|
||||||
|
BarrierPhase phase = BarrierPhase::Inactive;
|
||||||
|
int8_t x = 0;
|
||||||
|
int8_t y = 0;
|
||||||
|
bool vertical = false;
|
||||||
|
uint8_t length = 0;
|
||||||
|
uint16_t framesLeft = 0; // Frames remaining in the current phase
|
||||||
|
};
|
||||||
|
|
||||||
struct GameState {
|
struct GameState {
|
||||||
volatile uint16_t ticks = 0;
|
volatile uint16_t ticks = 0;
|
||||||
volatile uint16_t snakelength = 0;
|
volatile uint16_t snakelength = 0;
|
||||||
@@ -29,6 +48,11 @@ struct GameState {
|
|||||||
|
|
||||||
uint16_t curseTicksRemaining = 0;
|
uint16_t curseTicksRemaining = 0;
|
||||||
|
|
||||||
|
// Fixed-size (no heap) because barriers are touched from both the VBlank
|
||||||
|
// and Timer 0 IRQs. Those IRQs don't nest, so no further locking is needed.
|
||||||
|
std::array<Barrier, BARRIER_MAX_COUNT> barriers{};
|
||||||
|
volatile uint16_t barrierSpawnCooldown = 0; // Frames until the next spawn attempt
|
||||||
|
|
||||||
std::deque<std::array<int, 3>> snake;
|
std::deque<std::array<int, 3>> snake;
|
||||||
std::vector<std::array<int, 2>> apples;
|
std::vector<std::array<int, 2>> apples;
|
||||||
std::vector<std::array<int, 2>> bad_apples;
|
std::vector<std::array<int, 2>> bad_apples;
|
||||||
|
|||||||
+249
-1
@@ -114,8 +114,14 @@ bool check_collision_apples_cursed(GameState &state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void spawn_apples(GameState &state) {
|
void spawn_apples(GameState &state) {
|
||||||
if (state.apples.size() < 1) {
|
if (state.apples.empty()) {
|
||||||
std::array<int, 2> apple = {rand() % (X_MAX + 1), rand() % (Y_MAX + 1)};
|
std::array<int, 2> apple = {rand() % (X_MAX + 1), rand() % (Y_MAX + 1)};
|
||||||
|
// Don't place the apple inside a (pending) barrier, it would be unreachable
|
||||||
|
for (int attempt = 0;
|
||||||
|
attempt < 16 && is_barrier_cell(state, apple[0], apple[1], true);
|
||||||
|
attempt++) {
|
||||||
|
apple = {rand() % (X_MAX + 1), rand() % (Y_MAX + 1)};
|
||||||
|
}
|
||||||
state.apples.push_back(apple);
|
state.apples.push_back(apple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,3 +178,245 @@ uint16_t calculate_timer_reload(const GameState &state) {
|
|||||||
const uint32_t period_ticks = timer_clock_mhz / freq_mhz;
|
const uint32_t period_ticks = timer_clock_mhz / freq_mhz;
|
||||||
return static_cast<uint16_t>(-static_cast<int32_t>(period_ticks));
|
return static_cast<uint16_t>(-static_cast<int32_t>(period_ticks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Hard-mode temporary barriers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool barrier_contains(const Barrier &barrier, int x, int y) {
|
||||||
|
if (barrier.vertical) {
|
||||||
|
return x == barrier.x && y >= barrier.y && y < barrier.y + barrier.length;
|
||||||
|
}
|
||||||
|
return y == barrier.y && x >= barrier.x && x < barrier.x + barrier.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manhattan distance on the wrapping playfield
|
||||||
|
int wrapped_distance(int x1, int y1, int x2, int y2) {
|
||||||
|
int dx = std::abs(x1 - x2);
|
||||||
|
int dy = std::abs(y1 - y2);
|
||||||
|
dx = std::min(dx, (X_MAX + 1) - dx);
|
||||||
|
dy = std::min(dy, (Y_MAX + 1) - dy);
|
||||||
|
return dx + dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cell_in_list(const std::vector<std::array<int, 2>> &list, int x, int y) {
|
||||||
|
return std::any_of(list.begin(), list.end(),
|
||||||
|
[&](const std::array<int, 2> &item) {
|
||||||
|
return item[0] == x && item[1] == y;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cell_blocked_for_barrier(const GameState &state, int x, int y) {
|
||||||
|
if (is_barrier_cell(state, x, y, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cell_in_list(state.apples, x, y) || cell_in_list(state.bad_apples, x, y) ||
|
||||||
|
cell_in_list(state.cursed_apples, x, y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!state.snake.empty()) {
|
||||||
|
const auto &head = state.snake.front();
|
||||||
|
if (wrapped_distance(head[0], head[1], x, y) < BARRIER_HEAD_SAFE_DISTANCE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::any_of(state.snake.begin(), state.snake.end(),
|
||||||
|
[&](const std::array<int, 3> &seg) {
|
||||||
|
return seg[0] == x && seg[1] == y;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int max_concurrent_barriers(const GameState &state) {
|
||||||
|
if (state.applesEaten < BARRIER_START_APPLES) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const int apples = std::min<int>(state.applesEaten, SPEED_APPLES_CAP);
|
||||||
|
const int apple_span = SPEED_APPLES_CAP - BARRIER_START_APPLES;
|
||||||
|
if (apple_span <= 0) {
|
||||||
|
return BARRIER_MAX_COUNT;
|
||||||
|
}
|
||||||
|
const int progress = apples - BARRIER_START_APPLES;
|
||||||
|
return 1 + ((progress * (BARRIER_MAX_COUNT - 1)) / apple_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void reset_barriers(GameState &state) {
|
||||||
|
// Called from the main thread; keep the VBlank/Timer IRQs out while resetting
|
||||||
|
const int old_ime = enterCriticalSection();
|
||||||
|
for (Barrier &barrier : state.barriers) {
|
||||||
|
barrier = Barrier{};
|
||||||
|
}
|
||||||
|
state.barrierSpawnCooldown = BARRIER_FIRST_SPAWN_FRAMES;
|
||||||
|
leaveCriticalSection(old_ime);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_barrier_cell(const GameState &state, int x, int y,
|
||||||
|
bool include_warning) {
|
||||||
|
return std::any_of(
|
||||||
|
state.barriers.begin(), state.barriers.end(), [&](const Barrier &b) {
|
||||||
|
const bool relevant =
|
||||||
|
(b.phase == BarrierPhase::Active) ||
|
||||||
|
(include_warning && b.phase == BarrierPhase::Warning);
|
||||||
|
return relevant && barrier_contains(b, x, y);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t calculate_barrier_max_length(const GameState &state) {
|
||||||
|
if (state.applesEaten <= BARRIER_START_APPLES) {
|
||||||
|
return static_cast<uint8_t>(BARRIER_BASE_MAX_LENGTH);
|
||||||
|
}
|
||||||
|
const int apples = std::min<int>(state.applesEaten, SPEED_APPLES_CAP);
|
||||||
|
const int apple_span = SPEED_APPLES_CAP - BARRIER_START_APPLES;
|
||||||
|
if (apple_span <= 0) {
|
||||||
|
return static_cast<uint8_t>(BARRIER_MAX_LENGTH);
|
||||||
|
}
|
||||||
|
const int length_span = BARRIER_MAX_LENGTH - BARRIER_BASE_MAX_LENGTH;
|
||||||
|
const int progress = apples - BARRIER_START_APPLES;
|
||||||
|
return static_cast<uint8_t>(BARRIER_BASE_MAX_LENGTH +
|
||||||
|
((progress * length_span) / apple_span));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t calculate_barrier_max_batch(const GameState &state) {
|
||||||
|
if (state.applesEaten >= BARRIER_MULTI_SPAWN_APPLES_3) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (state.applesEaten >= BARRIER_MULTI_SPAWN_APPLES_2) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t calculate_barrier_interval_frames(const GameState &state) {
|
||||||
|
if (state.applesEaten <= BARRIER_START_APPLES) {
|
||||||
|
return BARRIER_INTERVAL_MAX_FRAMES;
|
||||||
|
}
|
||||||
|
const int apples = std::min<int>(state.applesEaten, SPEED_APPLES_CAP);
|
||||||
|
const int apple_span = SPEED_APPLES_CAP - BARRIER_START_APPLES;
|
||||||
|
if (apple_span <= 0) {
|
||||||
|
return BARRIER_INTERVAL_MIN_FRAMES;
|
||||||
|
}
|
||||||
|
const int span = BARRIER_INTERVAL_MAX_FRAMES - BARRIER_INTERVAL_MIN_FRAMES;
|
||||||
|
const int progress = apples - BARRIER_START_APPLES;
|
||||||
|
return static_cast<uint16_t>(BARRIER_INTERVAL_MAX_FRAMES -
|
||||||
|
((progress * span) / apple_span));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tick_barriers_frame(GameState &state) {
|
||||||
|
if (state.applesEaten < BARRIER_START_APPLES) {
|
||||||
|
state.barrierSpawnCooldown = BARRIER_FIRST_SPAWN_FRAMES;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.barrierSpawnCooldown > 0) {
|
||||||
|
state.barrierSpawnCooldown = static_cast<uint16_t>(state.barrierSpawnCooldown - 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Barrier &barrier : state.barriers) {
|
||||||
|
if (barrier.phase == BarrierPhase::Inactive) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (barrier.framesLeft > 0) {
|
||||||
|
barrier.framesLeft--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (barrier.phase == BarrierPhase::Warning) {
|
||||||
|
barrier.phase = BarrierPhase::Active;
|
||||||
|
barrier.framesLeft = BARRIER_ACTIVE_FRAMES;
|
||||||
|
} else {
|
||||||
|
barrier.phase = BarrierPhase::Inactive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_barriers(GameState &state) {
|
||||||
|
if (!state.hardMode || state.applesEaten < BARRIER_START_APPLES ||
|
||||||
|
state.barrierSpawnCooldown > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int in_use = static_cast<int>(
|
||||||
|
std::count_if(state.barriers.begin(), state.barriers.end(),
|
||||||
|
[](const Barrier &b) { return b.phase != BarrierPhase::Inactive; }));
|
||||||
|
const int max_allowed = max_concurrent_barriers(state);
|
||||||
|
const int available = max_allowed - in_use;
|
||||||
|
if (available <= 0) {
|
||||||
|
state.barrierSpawnCooldown = BARRIER_RETRY_FRAMES;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int max_batch = calculate_barrier_max_batch(state);
|
||||||
|
int to_spawn = 1;
|
||||||
|
if (max_batch > 1 && available > 1) {
|
||||||
|
const int max_possible = std::min(max_batch, available);
|
||||||
|
to_spawn = 1 + (rand() % max_possible);
|
||||||
|
}
|
||||||
|
|
||||||
|
int spawned_count = 0;
|
||||||
|
const int current_max = calculate_barrier_max_length(state);
|
||||||
|
const int length_range = current_max - BARRIER_MIN_LENGTH + 1;
|
||||||
|
|
||||||
|
for (int spawn_idx = 0; spawn_idx < to_spawn; spawn_idx++) {
|
||||||
|
auto free_slot =
|
||||||
|
std::find_if(state.barriers.begin(), state.barriers.end(),
|
||||||
|
[](const Barrier &b) { return b.phase == BarrierPhase::Inactive; });
|
||||||
|
if (free_slot == state.barriers.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool spawned = false;
|
||||||
|
for (int attempt = 0; attempt < 48; attempt++) {
|
||||||
|
Barrier candidate;
|
||||||
|
candidate.vertical = (rand() % 2) == 0;
|
||||||
|
candidate.length = static_cast<uint8_t>(
|
||||||
|
BARRIER_MIN_LENGTH + (rand() % length_range));
|
||||||
|
// Keep the whole line on screen (barriers never wrap around edges)
|
||||||
|
const int max_x = candidate.vertical ? X_MAX : (X_MAX + 1 - candidate.length);
|
||||||
|
const int max_y = candidate.vertical ? (Y_MAX + 1 - candidate.length) : Y_MAX;
|
||||||
|
candidate.x = static_cast<int8_t>(rand() % (max_x + 1));
|
||||||
|
candidate.y = static_cast<int8_t>(rand() % (max_y + 1));
|
||||||
|
|
||||||
|
bool valid = true;
|
||||||
|
for (int i = 0; i < candidate.length && valid; i++) {
|
||||||
|
const int cx = candidate.x + (candidate.vertical ? 0 : i);
|
||||||
|
const int cy = candidate.y + (candidate.vertical ? i : 0);
|
||||||
|
valid = !cell_blocked_for_barrier(state, cx, cy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
candidate.phase = BarrierPhase::Warning;
|
||||||
|
candidate.framesLeft = BARRIER_WARN_FRAMES;
|
||||||
|
*free_slot = candidate;
|
||||||
|
spawned = true;
|
||||||
|
spawned_count++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spawned) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spawned_count > 0) {
|
||||||
|
state.barrierSpawnCooldown = static_cast<uint16_t>(
|
||||||
|
calculate_barrier_interval_frames(state) +
|
||||||
|
(rand() % (BARRIER_INTERVAL_JITTER + 1)));
|
||||||
|
} else {
|
||||||
|
state.barrierSpawnCooldown = BARRIER_RETRY_FRAMES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_collision_barriers(GameState &state) {
|
||||||
|
if (state.gameOver || state.snake.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto &head = state.snake.front();
|
||||||
|
if (is_barrier_cell(state, head[0], head[1], false)) {
|
||||||
|
state.gameOver = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
+85
-6
@@ -155,7 +155,8 @@ void draw_apples_cursed(const GameState &state, const RenderAssets &assets,
|
|||||||
void invalidate_hud(RenderAssets &assets) { assets.hudCache.dirty = true; }
|
void invalidate_hud(RenderAssets &assets) { assets.hudCache.dirty = true; }
|
||||||
|
|
||||||
void draw_game(const GameState &state, const RenderAssets &assets) {
|
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) {
|
if (!state.gameOver) {
|
||||||
draw_apples(state, assets, oamId);
|
draw_apples(state, assets, oamId);
|
||||||
@@ -180,6 +181,79 @@ void draw_game(const GameState &state, const RenderAssets &assets) {
|
|||||||
iprintf("\x1b[11;12HGAME OVER!");
|
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,
|
void draw_stats(const GameState &state, RenderAssets &assets, bool internals,
|
||||||
bool musicEnabled, uint8_t frame) {
|
bool musicEnabled, uint8_t frame) {
|
||||||
if (internals) {
|
if (internals) {
|
||||||
@@ -196,7 +270,12 @@ void draw_stats(const GameState &state, RenderAssets &assets, bool internals,
|
|||||||
iprintf("\x1b[7;0HSpeed: %u.%01u Hz (%u%%) ",
|
iprintf("\x1b[7;0HSpeed: %u.%01u Hz (%u%%) ",
|
||||||
static_cast<unsigned int>(freq_mhz / 1000U),
|
static_cast<unsigned int>(freq_mhz / 1000U),
|
||||||
static_cast<unsigned int>((freq_mhz % 1000U) / 100U),
|
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;
|
HudCache &cache = assets.hudCache;
|
||||||
@@ -274,14 +353,14 @@ void draw_menu(const GameState &state, RenderAssets &assets,
|
|||||||
iprintf("\x1b[3;10HSNAKE PLUS");
|
iprintf("\x1b[3;10HSNAKE PLUS");
|
||||||
iprintf("\x1b[5;5H---------------------");
|
iprintf("\x1b[5;5H---------------------");
|
||||||
|
|
||||||
const char *hardModeLabel = state.hardMode ? "ON " : "OFF";
|
const char *hard_mode_label = state.hardMode ? "ON " : "OFF";
|
||||||
const char *musicLabel = musicEnabled ? "ON " : "OFF";
|
const char *music_label = musicEnabled ? "ON " : "OFF";
|
||||||
|
|
||||||
iprintf("\x1b[8;7H%c Start Game", state.menuSelection == 0 ? '>' : ' ');
|
iprintf("\x1b[8;7H%c Start Game", state.menuSelection == 0 ? '>' : ' ');
|
||||||
iprintf("\x1b[10;7H%c Hard Mode: %s", state.menuSelection == 1 ? '>' : ' ',
|
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 ? '>' : ' ',
|
iprintf("\x1b[12;7H%c Music: %s", state.menuSelection == 2 ? '>' : ' ',
|
||||||
musicLabel);
|
music_label);
|
||||||
|
|
||||||
iprintf("\x1b[19;3HUP/DOWN: Navigate");
|
iprintf("\x1b[19;3HUP/DOWN: Navigate");
|
||||||
iprintf("\x1b[20;3HA/START: Select");
|
iprintf("\x1b[20;3HA/START: Select");
|
||||||
|
|||||||
+58
-5
@@ -85,6 +85,8 @@ void setup() {
|
|||||||
renderAssets.gfx_tail_v[1] = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
renderAssets.gfx_tail_v[1] = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
renderAssets.gfx_apple_rotten = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
renderAssets.gfx_apple_rotten = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
renderAssets.gfx_apple_cursed = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
renderAssets.gfx_apple_cursed = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
|
renderAssets.gfx_barrier = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
|
renderAssets.gfx_barrier_warn = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
|
|
||||||
dmaCopy(get_sprite_pointer(1), renderAssets.gfx_segment, 64);
|
dmaCopy(get_sprite_pointer(1), renderAssets.gfx_segment, 64);
|
||||||
dmaCopy(get_sprite_pointer(2), renderAssets.gfx_segment_small, 64);
|
dmaCopy(get_sprite_pointer(2), renderAssets.gfx_segment_small, 64);
|
||||||
@@ -109,6 +111,12 @@ void setup() {
|
|||||||
SPRITE_PALETTE_SUB[CURSE_PAL_CORE] = RGB15(31, 26, 31); // Glowing White-Pink Core
|
SPRITE_PALETTE_SUB[CURSE_PAL_CORE] = RGB15(31, 26, 31); // Glowing White-Pink Core
|
||||||
SPRITE_PALETTE_SUB[CURSE_PAL_LEAF] = RGB15(2, 31, 10); // Toxic Radioactive Neon Green
|
SPRITE_PALETTE_SUB[CURSE_PAL_LEAF] = RGB15(2, 31, 10); // Toxic Radioactive Neon Green
|
||||||
|
|
||||||
|
// Setup initial colors for the hard-mode temporary fatal barriers
|
||||||
|
SPRITE_PALETTE_SUB[BARRIER_PAL_EDGE] = RGB15(12, 0, 3);
|
||||||
|
SPRITE_PALETTE_SUB[BARRIER_PAL_FILL] = RGB15(22, 2, 4);
|
||||||
|
SPRITE_PALETTE_SUB[BARRIER_PAL_STRIPE] = RGB15(31, 22, 6);
|
||||||
|
SPRITE_PALETTE_SUB[BARRIER_PAL_WARN] = RGB15(18, 4, 2);
|
||||||
|
|
||||||
// 8x8 256-color tile data for Cursed Apple (vibrant electric purple apple with radioactive green horn & glowing core)
|
// 8x8 256-color tile data for Cursed Apple (vibrant electric purple apple with radioactive green horn & glowing core)
|
||||||
static const uint32_t CURSED_APPLE_TILE[16] = {
|
static const uint32_t CURSED_APPLE_TILE[16] = {
|
||||||
0x10000000, 0x00001414,
|
0x10000000, 0x00001414,
|
||||||
@@ -121,11 +129,47 @@ void setup() {
|
|||||||
0x10100000, 0x00101000,
|
0x10100000, 0x00101000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 8x8 256-color tile data for solid fatal barrier (bordered hazard stripe block)
|
||||||
|
static const uint32_t BARRIER_SOLID_TILE[16] = {
|
||||||
|
0x15151515, 0x15151515,
|
||||||
|
0x16171715, 0x15171716,
|
||||||
|
0x16161715, 0x15161717,
|
||||||
|
0x17161615, 0x15161617,
|
||||||
|
0x17171615, 0x15171616,
|
||||||
|
0x16171715, 0x15171716,
|
||||||
|
0x16161715, 0x15161717,
|
||||||
|
0x15151515, 0x15151515,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 8x8 256-color tile data for barrier warning announcement (pulsing reticle frame)
|
||||||
|
static const uint32_t BARRIER_WARN_TILE[16] = {
|
||||||
|
0x00001818, 0x18180000,
|
||||||
|
0x18000018, 0x18000018,
|
||||||
|
0x18180000, 0x00001818,
|
||||||
|
0x00181800, 0x00181800,
|
||||||
|
0x00181800, 0x00181800,
|
||||||
|
0x18180000, 0x00001818,
|
||||||
|
0x18000018, 0x18000018,
|
||||||
|
0x00001818, 0x18180000,
|
||||||
|
};
|
||||||
|
|
||||||
// Copy cursed apple tile directly into allocated VRAM (32 halfwords)
|
// Copy cursed apple tile directly into allocated VRAM (32 halfwords)
|
||||||
auto *destAppleCursed = static_cast<uint16_t *>(renderAssets.gfx_apple_cursed);
|
auto *dest_apple_cursed = static_cast<uint16_t *>(renderAssets.gfx_apple_cursed);
|
||||||
const auto *srcAppleCursed = reinterpret_cast<const uint16_t *>(CURSED_APPLE_TILE);
|
const auto *src_apple_cursed = reinterpret_cast<const uint16_t *>(CURSED_APPLE_TILE);
|
||||||
for (size_t i = 0; i < 32; i++) {
|
for (size_t i = 0; i < 32; i++) {
|
||||||
destAppleCursed[i] = srcAppleCursed[i];
|
dest_apple_cursed[i] = src_apple_cursed[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *dest_barrier = static_cast<uint16_t *>(renderAssets.gfx_barrier);
|
||||||
|
const auto *src_barrier = reinterpret_cast<const uint16_t *>(BARRIER_SOLID_TILE);
|
||||||
|
for (size_t i = 0; i < 32; i++) {
|
||||||
|
dest_barrier[i] = src_barrier[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *dest_barrier_warn = static_cast<uint16_t *>(renderAssets.gfx_barrier_warn);
|
||||||
|
const auto *src_barrier_warn = reinterpret_cast<const uint16_t *>(BARRIER_WARN_TILE);
|
||||||
|
for (size_t i = 0; i < 32; i++) {
|
||||||
|
dest_barrier_warn[i] = src_barrier_warn[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_setup(audioContext);
|
audio_setup(audioContext);
|
||||||
@@ -137,6 +181,10 @@ void handle_vblank() {
|
|||||||
if (gameState.scene == Scene::Playing) {
|
if (gameState.scene == Scene::Playing) {
|
||||||
consoleSelect(&topScreen);
|
consoleSelect(&topScreen);
|
||||||
draw_stats(gameState, renderAssets, internals, audio_is_music_enabled(audioContext), frame);
|
draw_stats(gameState, renderAssets, internals, audio_is_music_enabled(audioContext), frame);
|
||||||
|
if (!gameState.paused) {
|
||||||
|
tick_barriers_frame(gameState);
|
||||||
|
}
|
||||||
|
draw_barriers(gameState, renderAssets, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic pulsing radioactive glow for Cursed Apples (smooth 60fps breathing)
|
// Dynamic pulsing radioactive glow for Cursed Apples (smooth 60fps breathing)
|
||||||
@@ -157,7 +205,7 @@ void do_tick() {
|
|||||||
if (gameState.scene == Scene::Playing && !paused) {
|
if (gameState.scene == Scene::Playing && !paused) {
|
||||||
ticks++;
|
ticks++;
|
||||||
if (!gameOver){
|
if (!gameOver){
|
||||||
const bool wasGameOver = gameOver;
|
const bool was_game_over = gameOver;
|
||||||
|
|
||||||
/*if (hardMode) {
|
/*if (hardMode) {
|
||||||
timerStop(0);
|
timerStop(0);
|
||||||
@@ -167,6 +215,9 @@ void do_tick() {
|
|||||||
update_snake(gameState);
|
update_snake(gameState);
|
||||||
audio_play_plop(audioContext);
|
audio_play_plop(audioContext);
|
||||||
check_collision_self(gameState);
|
check_collision_self(gameState);
|
||||||
|
if (check_collision_barriers(gameState)) {
|
||||||
|
audio_play_boom(audioContext);
|
||||||
|
}
|
||||||
if (check_collision_apples(gameState)) {
|
if (check_collision_apples(gameState)) {
|
||||||
audio_play_pickup(audioContext);
|
audio_play_pickup(audioContext);
|
||||||
bg_trigger_pulse(bgContext);
|
bg_trigger_pulse(bgContext);
|
||||||
@@ -181,8 +232,9 @@ void do_tick() {
|
|||||||
spawn_apples(gameState);
|
spawn_apples(gameState);
|
||||||
spawn_apples_rotten(gameState);
|
spawn_apples_rotten(gameState);
|
||||||
spawn_apples_cursed(gameState);
|
spawn_apples_cursed(gameState);
|
||||||
|
spawn_barriers(gameState);
|
||||||
tick_status_effects(gameState);
|
tick_status_effects(gameState);
|
||||||
if (!wasGameOver && gameOver) {
|
if (!was_game_over && gameOver) {
|
||||||
audio_stop_music();
|
audio_stop_music();
|
||||||
audio_play_fail(audioContext);
|
audio_play_fail(audioContext);
|
||||||
}
|
}
|
||||||
@@ -194,6 +246,7 @@ void do_tick() {
|
|||||||
|
|
||||||
void init_game() {
|
void init_game() {
|
||||||
applesEaten = 0;
|
applesEaten = 0;
|
||||||
|
reset_barriers(gameState);
|
||||||
if (hardMode) {
|
if (hardMode) {
|
||||||
timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick);
|
timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick);
|
||||||
setBackdropColorSub(RGB15(8, 3, 3));
|
setBackdropColorSub(RGB15(8, 3, 3));
|
||||||
|
|||||||
Reference in new issue
Block a user