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
+33
-3
@@ -1,6 +1,7 @@
|
||||
#ifndef CONSTS_H
|
||||
#define CONSTS_H
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
const int START_LENGTH = 4;
|
||||
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_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)
|
||||
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_HARD_MHZ = 17000; // 17.0 Hz (~59 ms per step)
|
||||
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,
|
||||
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_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
|
||||
@@ -32,6 +32,8 @@ struct RenderAssets {
|
||||
u16 *gfx_tail_v[2] = {nullptr, nullptr};
|
||||
u16 *gfx_apple_rotten = 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;
|
||||
};
|
||||
@@ -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_menu(const GameState &state, RenderAssets &assets, bool musicEnabled);
|
||||
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
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <vector>
|
||||
#include <nds/ndstypes.h>
|
||||
|
||||
#include "consts.h"
|
||||
|
||||
enum class Scene {
|
||||
Menu,
|
||||
Playing,
|
||||
@@ -13,6 +15,23 @@ enum class Scene {
|
||||
|
||||
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 {
|
||||
volatile uint16_t ticks = 0;
|
||||
volatile uint16_t snakelength = 0;
|
||||
@@ -29,6 +48,11 @@ struct GameState {
|
||||
|
||||
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::vector<std::array<int, 2>> apples;
|
||||
std::vector<std::array<int, 2>> bad_apples;
|
||||
|
||||
Reference in new issue
Block a user