feat: add handcrafted 8x8 sprite art and VBlank bioluminescent animation for rotten apples

This commit is contained in:
phillip committed 2026-10-03 23:49:24 +02:00
1 parent 8559159fc0
commit 91908d1f15
6 files changed
+322 -260

No files matched your search

+28 -23
View File
@@ -425,26 +425,26 @@ void bg_update(BackgroundContext &bg_ctx, const GameState &state,
col_frame = make_rgb15(18, 2, 22);
col_frame_accent = make_rgb15(28, 6, 31);
} else if (hard) {
// Fiery amber / crimson cyber grid interpolating towards volcanic hyper-crimson
const uint16_t c_faint_0 = make_rgb15(5, 2, 1);
const uint16_t c_subtle_0 = make_rgb15(10, 4, 2);
const uint16_t c_primary_0 = make_rgb15(18, 8, 3);
const uint16_t c_star_dim_0 = make_rgb15(8, 3, 1);
const uint16_t c_star_med_0 = make_rgb15(16, 7, 2);
const uint16_t c_star_bright_0 = make_rgb15(26, 14, 4);
const uint16_t c_star_flare_0 = make_rgb15(31, 24, 10);
const uint16_t c_frame_0 = make_rgb15(14, 5, 2);
const uint16_t c_frame_accent_0 = make_rgb15(26, 10, 4);
// Fiery amber / crimson cyber grid interpolating towards volcanic hyper-crimson (toned darker for high item contrast)
const uint16_t c_faint_0 = make_rgb15(3, 1, 1);
const uint16_t c_subtle_0 = make_rgb15(6, 2, 1);
const uint16_t c_primary_0 = make_rgb15(12, 4, 2);
const uint16_t c_star_dim_0 = make_rgb15(6, 2, 1);
const uint16_t c_star_med_0 = make_rgb15(11, 4, 2);
const uint16_t c_star_bright_0 = make_rgb15(20, 9, 3);
const uint16_t c_star_flare_0 = make_rgb15(28, 18, 6);
const uint16_t c_frame_0 = make_rgb15(9, 3, 1);
const uint16_t c_frame_accent_0 = make_rgb15(18, 6, 2);
const uint16_t c_faint_max = make_rgb15(8, 3, 2);
const uint16_t c_subtle_max = make_rgb15(16, 7, 3);
const uint16_t c_primary_max = make_rgb15(28, 14, 5);
const uint16_t c_star_dim_max = make_rgb15(14, 5, 2);
const uint16_t c_star_med_max = make_rgb15(24, 11, 3);
const uint16_t c_star_bright_max = make_rgb15(31, 20, 6);
const uint16_t c_star_flare_max = make_rgb15(31, 31, 18);
const uint16_t c_frame_max = make_rgb15(22, 8, 3);
const uint16_t c_frame_accent_max = make_rgb15(31, 16, 5);
const uint16_t c_faint_max = make_rgb15(5, 2, 1);
const uint16_t c_subtle_max = make_rgb15(9, 3, 2);
const uint16_t c_primary_max = make_rgb15(18, 6, 2);
const uint16_t c_star_dim_max = make_rgb15(9, 3, 1);
const uint16_t c_star_med_max = make_rgb15(16, 6, 2);
const uint16_t c_star_bright_max = make_rgb15(26, 12, 4);
const uint16_t c_star_flare_max = make_rgb15(31, 26, 10);
const uint16_t c_frame_max = make_rgb15(15, 5, 2);
const uint16_t c_frame_accent_max = make_rgb15(25, 10, 3);
col_faint = lerp_color(c_faint_0, c_faint_max, intensity_31);
col_subtle = lerp_color(c_subtle_0, c_subtle_max, intensity_31);
@@ -491,8 +491,8 @@ void bg_update(BackgroundContext &bg_ctx, const GameState &state,
// Deep space backdrop color dynamically illuminates with intensity
if (in_game && !over) {
if (hard) {
BG_PALETTE_SUB[0] = lerp_color(make_rgb15(8, 3, 3), make_rgb15(12, 4, 4), intensity_31);
BG_PALETTE[0] = lerp_color(make_rgb15(6, 2, 2), make_rgb15(9, 3, 3), intensity_31);
BG_PALETTE_SUB[0] = lerp_color(make_rgb15(3, 1, 1), make_rgb15(6, 2, 2), intensity_31);
BG_PALETTE[0] = lerp_color(make_rgb15(2, 1, 1), make_rgb15(5, 1, 2), intensity_31);
} else {
BG_PALETTE_SUB[0] = lerp_color(make_rgb15(3, 4, 7), make_rgb15(5, 7, 12), intensity_31);
BG_PALETTE[0] = lerp_color(make_rgb15(2, 3, 6), make_rgb15(3, 5, 10), intensity_31);
@@ -501,8 +501,13 @@ void bg_update(BackgroundContext &bg_ctx, const GameState &state,
BG_PALETTE_SUB[0] = make_rgb15(3, 2, 2);
BG_PALETTE[0] = make_rgb15(2, 1, 1);
} else {
BG_PALETTE_SUB[0] = make_rgb15(2, 2, 6);
BG_PALETTE[0] = make_rgb15(2, 2, 6);
if (hard) {
BG_PALETTE_SUB[0] = make_rgb15(3, 1, 1);
BG_PALETTE[0] = make_rgb15(2, 1, 1);
} else {
BG_PALETTE_SUB[0] = make_rgb15(2, 2, 6);
BG_PALETTE[0] = make_rgb15(2, 2, 6);
}
}
// Animate twinkling stars using sine oscillations
+2 -1
View File
@@ -129,9 +129,10 @@ void draw_apples_rotten(const GameState &state, const RenderAssets &assets,
if (oamId >= 128) {
return;
}
const bool hflip = (((apple.x + apple.y) & 1) != 0);
oamSet(&oamSub, oamId, apple.x * 8, apple.y * 8, 0, 0, SpriteSize_8x8,
SpriteColorFormat_256Color, assets.gfx_apple_rotten, -1, false,
false, false, false, false);
false, hflip, false, false);
oamId++;
}
}
+281 -233
View File
@@ -1,14 +1,14 @@
#include <nds.h>
#include <vector>
#include <deque>
#include <array>
#include <consts.h>
#include <game_state.h>
#include <deque>
#include <game_audio.h>
#include <game_bg.h>
#include <game_input.h>
#include <game_logic.h>
#include <game_render.h>
#include <game_audio.h>
#include <game_input.h>
#include <game_bg.h>
#include <game_state.h>
#include <nds.h>
#include <vector>
#include "tilemap.h"
@@ -51,266 +51,314 @@ RenderAssets renderAssets;
BackgroundContext bgContext;
InputContext inputContext;
const void * get_sprite_pointer(uint16_t spriteIndex) {
return &tilemapTiles[spriteIndex*16];
const void *get_sprite_pointer(uint16_t spriteIndex) {
return &tilemapTiles[spriteIndex * 16];
}
namespace {
// 8x8 256-color tile data for Cursed Apple (vibrant electric purple apple with
// radioactive green horn & glowing core)
alignas(4) constexpr std::array<uint32_t, 16> cursed_apple_tile = {
0x10000000, 0x00001414, 0x10101000, 0x00101014, 0x12111110, 0x10111112,
0x13121110, 0x10111112, 0x13131110, 0x10111112, 0x13121110, 0x10111211,
0x11111000, 0x00101111, 0x10100000, 0x00101000,
};
// 8x8 256-color tile data for Rotten Apple (bruised amber decaying apple with
// dark sunken rot & toxic green mold)
alignas(4) constexpr std::array<uint32_t, 16> rotten_apple_tile = {
0x1E000000, 0x0000001E, 0x19191900, 0x0019191E, 0x1B1A1A19, 0x191A1A1B,
0x1D1B1A19, 0x191A1A1C, 0x1D1C1A19, 0x191A1C1D, 0x1C1A1A19, 0x191A1B1C,
0x1A1A1900, 0x00191A1A, 0x19190000, 0x00001919,
};
// 8x8 256-color tile data for solid fatal barrier (bordered hazard stripe
// block)
alignas(4) constexpr std::array<uint32_t, 16> barrier_solid_tile = {
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)
alignas(4) constexpr std::array<uint32_t, 16> barrier_warn_tile = {
0x00001818, 0x18180000, 0x18000018, 0x18000018, 0x18180000, 0x00001818,
0x00181800, 0x00181800, 0x00181800, 0x00181800, 0x18180000, 0x00001818,
0x18000018, 0x18000018, 0x00001818, 0x18180000,
};
} // namespace
void setup() {
// Set up screens
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_BG);
vramSetBankC(VRAM_C_SUB_BG);
vramSetBankD(VRAM_D_SUB_SPRITE);
consoleInit(&topScreen, 3,BgType_Text4bpp, BgSize_T_256x256, X_MAX, 0, true, true);
consoleInit(&bottomScreen, 3,BgType_Text4bpp, BgSize_T_256x256, X_MAX, 0, false, true);
oamInit(&oamSub, SpriteMapping_1D_32, false);
// Set up screens
bg_setup(bgContext, topScreen.bgId, bottomScreen.bgId);
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
renderAssets.topScreen = &topScreen;
renderAssets.bottomScreen = &bottomScreen;
renderAssets.bg_context = &bgContext;
vramSetBankA(VRAM_A_MAIN_BG);
vramSetBankC(VRAM_C_SUB_BG);
vramSetBankD(VRAM_D_SUB_SPRITE);
consoleInit(&topScreen, 3, BgType_Text4bpp, BgSize_T_256x256, X_MAX, 0, true,
true);
consoleInit(&bottomScreen, 3, BgType_Text4bpp, BgSize_T_256x256, X_MAX, 0,
false, true);
oamInit(&oamSub, SpriteMapping_1D_32, false);
renderAssets.gfx_segment = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_segment_small = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_head_h = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_head_v = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_apple = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_apple_blink = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_tail_h[0] = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_tail_h[1] = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_tail_v[0] = 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_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);
bg_setup(bgContext, topScreen.bgId, bottomScreen.bgId);
dmaCopy(get_sprite_pointer(1), renderAssets.gfx_segment, 64);
dmaCopy(get_sprite_pointer(2), renderAssets.gfx_segment_small, 64);
dmaCopy(get_sprite_pointer(3), renderAssets.gfx_head_h, 64);
dmaCopy(get_sprite_pointer(4), renderAssets.gfx_head_v, 64);
dmaCopy(get_sprite_pointer(5), renderAssets.gfx_apple, 64);
dmaCopy(get_sprite_pointer(6), renderAssets.gfx_apple_blink, 64);
dmaCopy(get_sprite_pointer(7), renderAssets.gfx_tail_h[0], 64);
dmaCopy(get_sprite_pointer(8), renderAssets.gfx_tail_h[1], 64);
dmaCopy(get_sprite_pointer(9), renderAssets.gfx_tail_v[0], 64);
dmaCopy(get_sprite_pointer(10), renderAssets.gfx_tail_v[1], 64);
dmaCopy(get_sprite_pointer(11), renderAssets.gfx_apple_rotten, 64);
renderAssets.topScreen = &topScreen;
renderAssets.bottomScreen = &bottomScreen;
renderAssets.bg_context = &bgContext;
consoleSelect(&topScreen);
renderAssets.gfx_segment =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_segment_small =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_head_h =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_head_v =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_apple =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_apple_blink =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_tail_h[0] =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_tail_h[1] =
oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
renderAssets.gfx_tail_v[0] =
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_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(tilemapPal, SPRITE_PALETTE_SUB, tilemapPalLen);
dmaCopy(get_sprite_pointer(1), renderAssets.gfx_segment, 64);
dmaCopy(get_sprite_pointer(2), renderAssets.gfx_segment_small, 64);
dmaCopy(get_sprite_pointer(3), renderAssets.gfx_head_h, 64);
dmaCopy(get_sprite_pointer(4), renderAssets.gfx_head_v, 64);
dmaCopy(get_sprite_pointer(5), renderAssets.gfx_apple, 64);
dmaCopy(get_sprite_pointer(6), renderAssets.gfx_apple_blink, 64);
dmaCopy(get_sprite_pointer(7), renderAssets.gfx_tail_h[0], 64);
dmaCopy(get_sprite_pointer(8), renderAssets.gfx_tail_h[1], 64);
dmaCopy(get_sprite_pointer(9), renderAssets.gfx_tail_v[0], 64);
dmaCopy(get_sprite_pointer(10), renderAssets.gfx_tail_v[1], 64);
// Setup vibrant base colors for the Cursed Apple sprite (neon electric purple with toxic green horn)
SPRITE_PALETTE_SUB[CURSE_PAL_OUTLINE] = RGB15(5, 0, 12); // Deep mystical dark purple outline
SPRITE_PALETTE_SUB[CURSE_PAL_FILL] = RGB15(22, 2, 28); // Vibrant Electric Purple
SPRITE_PALETTE_SUB[CURSE_PAL_GLOW] = RGB15(29, 6, 31); // Radiant Neon Magenta Glow
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
consoleSelect(&topScreen);
// 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);
dmaCopy(tilemapPal, SPRITE_PALETTE_SUB, tilemapPalLen);
// 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] = {
0x10000000, 0x00001414,
0x10101000, 0x00101014,
0x12111110, 0x10111112,
0x13121110, 0x10111112,
0x13131110, 0x10111112,
0x13121110, 0x10111211,
0x11111000, 0x00101111,
0x10100000, 0x00101000,
};
// Setup vibrant base colors for the Cursed Apple sprite (neon electric purple
// with toxic green horn)
SPRITE_PALETTE_SUB[CURSE_PAL_OUTLINE] =
RGB15(5, 0, 12); // Deep mystical dark purple outline
SPRITE_PALETTE_SUB[CURSE_PAL_FILL] =
RGB15(22, 2, 28); // Vibrant Electric Purple
SPRITE_PALETTE_SUB[CURSE_PAL_GLOW] =
RGB15(29, 6, 31); // Radiant Neon Magenta Glow
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
// 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,
};
// 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 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,
};
// Setup initial colors for the Rotten Apple sprite (bruised amber decaying
// skin with dark rot & glowing green mold)
SPRITE_PALETTE_SUB[ROTTEN_PAL_OUTLINE] = RGB15(2, 2, 1); // Crisp dark outline
SPRITE_PALETTE_SUB[ROTTEN_PAL_SKIN] = RGB15(
25, 17,
4); // Bruised golden-amber decaying skin (high contrast against crimson)
SPRITE_PALETTE_SUB[ROTTEN_PAL_DECAY] =
RGB15(5, 3, 1); // Sunken dark necrotic rot cavity
SPRITE_PALETTE_SUB[ROTTEN_PAL_MOLD] =
RGB15(12, 24, 4); // Vivid toxic fungal green mold
SPRITE_PALETTE_SUB[ROTTEN_PAL_SPORE] =
RGB15(22, 31, 10); // Radiant glowing spore pustule
SPRITE_PALETTE_SUB[ROTTEN_PAL_STEM] =
RGB15(13, 8, 3); // Withered woody twig stem
// Copy cursed apple tile directly into allocated VRAM (32 halfwords)
auto *dest_apple_cursed = static_cast<uint16_t *>(renderAssets.gfx_apple_cursed);
const auto *src_apple_cursed = reinterpret_cast<const uint16_t *>(CURSED_APPLE_TILE);
for (size_t i = 0; i < 32; i++) {
dest_apple_cursed[i] = src_apple_cursed[i];
}
dmaCopy(cursed_apple_tile.data(), renderAssets.gfx_apple_cursed, 64);
dmaCopy(rotten_apple_tile.data(), renderAssets.gfx_apple_rotten, 64);
dmaCopy(barrier_solid_tile.data(), renderAssets.gfx_barrier, 64);
dmaCopy(barrier_warn_tile.data(), renderAssets.gfx_barrier_warn, 64);
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];
}
// Pre-reserve capacities to eliminate dynamic heap allocations inside Timer 0 ISR (do_tick)
gameState.bad_apples.reserve(32);
gameState.cursed_apples.reserve(4);
gameState.apples.reserve(4);
audio_setup(audioContext);
// Pre-reserve capacities to eliminate dynamic heap allocations inside Timer 0
// ISR (do_tick)
gameState.bad_apples.reserve(32);
gameState.cursed_apples.reserve(4);
gameState.apples.reserve(4);
audio_setup(audioContext);
}
void handle_vblank() {
frame++;
bg_update(bgContext, gameState, frame);
if (gameState.scene == Scene::Playing) {
consoleSelect(&topScreen);
draw_stats(gameState, renderAssets, internals, audio_is_music_enabled(audioContext), frame);
if (!gameState.paused) {
tick_barriers_frame(gameState);
}
draw_barriers(gameState, renderAssets, frame);
}
frame++;
bg_update(bgContext, gameState, frame);
if (gameState.scene == Scene::Playing) {
consoleSelect(&topScreen);
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)
const uint8_t pulse = (frame >> 2U) & 7U; // 8-step cycle
const uint8_t wave = (pulse < 4U) ? pulse : static_cast<uint8_t>(7U - pulse); // 0, 1, 2, 3, 3, 2, 1, 0
SPRITE_PALETTE_SUB[CURSE_PAL_FILL] = static_cast<uint16_t>(RGB15(20 + (wave * 3), 2 + wave, 26 + wave));
SPRITE_PALETTE_SUB[CURSE_PAL_GLOW] = static_cast<uint16_t>(RGB15(26 + wave, 6 + (wave * 4), 31));
SPRITE_PALETTE_SUB[CURSE_PAL_CORE] = (wave >= 2U)
? static_cast<uint16_t>(RGB15(31, 31, 31))
: static_cast<uint16_t>(RGB15(31, 22 + (wave * 3), 31));
SPRITE_PALETTE_SUB[CURSE_PAL_LEAF] = static_cast<uint16_t>(RGB15(2 + (wave * 2), 31, 10 + (wave * 3)));
// Dynamic pulsing radioactive glow for Cursed Apples (smooth 60fps breathing)
const uint8_t pulse = (frame >> 2U) & 7U; // 8-step cycle
const uint8_t wave =
(pulse < 4U) ? pulse
: static_cast<uint8_t>(7U - pulse); // 0, 1, 2, 3, 3, 2, 1, 0
SPRITE_PALETTE_SUB[CURSE_PAL_FILL] =
static_cast<uint16_t>(RGB15(20 + (wave * 3), 2 + wave, 26 + wave));
SPRITE_PALETTE_SUB[CURSE_PAL_GLOW] =
static_cast<uint16_t>(RGB15(26 + wave, 6 + (wave * 4), 31));
SPRITE_PALETTE_SUB[CURSE_PAL_CORE] =
(wave >= 2U) ? static_cast<uint16_t>(RGB15(31, 31, 31))
: static_cast<uint16_t>(RGB15(31, 22 + (wave * 3), 31));
SPRITE_PALETTE_SUB[CURSE_PAL_LEAF] =
static_cast<uint16_t>(RGB15(2 + (wave * 2), 31, 10 + (wave * 3)));
oamUpdate(&oamSub);
//flashy_colors();
// Dynamic festering mold & spore respiration for Rotten Apples (smooth 60fps
// organic pulse)
const uint8_t rot_pulse = ((frame + 3U) >> 2U) &
7U; // 8-step cycle, phase-shifted from cursed apple
const uint8_t rot_wave =
(rot_pulse < 4U)
? rot_pulse
: static_cast<uint8_t>(7U - rot_pulse); // 0, 1, 2, 3, 3, 2, 1, 0
SPRITE_PALETTE_SUB[ROTTEN_PAL_DECAY] =
static_cast<uint16_t>(RGB15(5 + rot_wave, 3 + rot_wave, 1));
SPRITE_PALETTE_SUB[ROTTEN_PAL_MOLD] = static_cast<uint16_t>(
RGB15(10 + (rot_wave * 2), 22 + (rot_wave * 2), 3 + rot_wave));
SPRITE_PALETTE_SUB[ROTTEN_PAL_SPORE] =
(rot_wave >= 2U)
? static_cast<uint16_t>(RGB15(26, 31, 14))
: static_cast<uint16_t>(
RGB15(20 + (rot_wave * 2), 28 + rot_wave, 8 + (rot_wave * 2)));
oamUpdate(&oamSub);
// flashy_colors();
}
void do_tick() {
if (gameState.scene == Scene::Playing && !paused) {
ticks++;
if (!gameOver){
const bool was_game_over = gameOver;
/*if (hardMode) {
timerStop(0);
timerStart(0, ClockDivider_1024, TIMER_FREQ_1024(ticks+5), do_tick);
}*/
update_snake(gameState);
audio_play_plop(audioContext);
check_collision_self(gameState);
if (check_collision_barriers(gameState)) {
audio_play_boom(audioContext);
}
if (check_collision_apples(gameState)) {
audio_play_pickup(audioContext);
bg_trigger_pulse(bgContext);
timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick);
}
if (check_collision_apples_rotten(gameState)) {
audio_play_boom(audioContext);
}
if (check_collision_apples_cursed(gameState)) {
audio_play_boom(audioContext);
}
tick_evil_items(gameState);
spawn_apples(gameState);
spawn_apples_rotten(gameState);
spawn_apples_cursed(gameState);
spawn_barriers(gameState);
tick_status_effects(gameState);
if (!was_game_over && gameOver) {
audio_stop_music();
audio_play_fail(audioContext);
}
//consoleSelect(&bottomScreen);
draw_game(gameState, renderAssets);
}
}
if (gameState.scene == Scene::Playing && !paused) {
ticks++;
if (!gameOver) {
const bool was_game_over = gameOver;
/*if (hardMode) {
timerStop(0);
timerStart(0, ClockDivider_1024, TIMER_FREQ_1024(ticks+5),
do_tick);
}*/
update_snake(gameState);
audio_play_plop(audioContext);
check_collision_self(gameState);
if (check_collision_barriers(gameState)) {
audio_play_boom(audioContext);
}
if (check_collision_apples(gameState)) {
audio_play_pickup(audioContext);
bg_trigger_pulse(bgContext);
timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState),
do_tick);
}
if (check_collision_apples_rotten(gameState)) {
audio_play_boom(audioContext);
}
if (check_collision_apples_cursed(gameState)) {
audio_play_boom(audioContext);
}
tick_evil_items(gameState);
spawn_apples(gameState);
spawn_apples_rotten(gameState);
spawn_apples_cursed(gameState);
spawn_barriers(gameState);
tick_status_effects(gameState);
if (!was_game_over && gameOver) {
audio_stop_music();
audio_play_fail(audioContext);
}
// consoleSelect(&bottomScreen);
draw_game(gameState, renderAssets);
}
}
}
void init_game() {
timerStop(0);
applesEaten = 0;
reset_barriers(gameState);
gameState.bad_apples.reserve(32);
gameState.cursed_apples.reserve(4);
gameState.apples.reserve(4);
score = 0;
ticks = 0;
gameOver = false;
snakeDirection = 1;
gameState.curseTicksRemaining = 0;
apples.clear();
bad_apples.clear();
gameState.cursed_apples.clear();
timerStop(0);
applesEaten = 0;
reset_barriers(gameState);
gameState.bad_apples.reserve(32);
gameState.cursed_apples.reserve(4);
gameState.apples.reserve(4);
score = 0;
ticks = 0;
gameOver = false;
snakeDirection = 1;
gameState.curseTicksRemaining = 0;
apples.clear();
bad_apples.clear();
gameState.cursed_apples.clear();
if (hardMode) {
setBackdropColorSub(RGB15(8, 3, 3));
setBackdropColor(RGB15(6, 2, 2));
snakelength = START_LENGTH_HARD;
snake = { {2,1,1}, {1,1,1} };
} else {
setBackdropColorSub(RGB15(3, 4, 7));
setBackdropColor(RGB15(2, 3, 6));
snakelength = START_LENGTH;
snake = { {4,1,1}, {3,1,1}, {2,1,1}, {1,1,1} };
}
if (hardMode) {
setBackdropColorSub(RGB15(3, 1, 1));
setBackdropColor(RGB15(2, 1, 1));
snakelength = START_LENGTH_HARD;
snake = {{2, 1, 1}, {1, 1, 1}};
} else {
setBackdropColorSub(RGB15(3, 4, 7));
setBackdropColor(RGB15(2, 3, 6));
snakelength = START_LENGTH;
snake = {{4, 1, 1}, {3, 1, 1}, {2, 1, 1}, {1, 1, 1}};
}
consoleSelect(&topScreen);
consoleClear();
invalidate_hud(renderAssets);
consoleSelect(&bottomScreen);
consoleClear();
oamClear(&oamSub, 0, 128);
audio_start_music(audioContext);
timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick);
consoleSelect(&topScreen);
consoleClear();
invalidate_hud(renderAssets);
consoleSelect(&bottomScreen);
consoleClear();
oamClear(&oamSub, 0, 128);
audio_start_music(audioContext);
timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick);
}
int main(void) {
// Set up VBlank handler
irqSet(IRQ_VBLANK, handle_vblank);
// Set up VBlank handler
irqSet(IRQ_VBLANK, handle_vblank);
setup();
setBackdropColor(RGB15(2, 2, 6));
setBackdropColorSub(RGB15(2, 2, 6));
draw_menu(gameState, renderAssets, audio_is_music_enabled(audioContext));
setup();
setBackdropColor(RGB15(2, 2, 6));
setBackdropColorSub(RGB15(2, 2, 6));
draw_menu(gameState, renderAssets, audio_is_music_enabled(audioContext));
inputContext.state = &gameState;
inputContext.renderAssets = &renderAssets;
inputContext.audio = &audioContext;
inputContext.internals = &internals;
inputContext.topScreen = &topScreen;
inputContext.init_game = init_game;
inputContext.state = &gameState;
inputContext.renderAssets = &renderAssets;
inputContext.audio = &audioContext;
inputContext.internals = &internals;
inputContext.topScreen = &topScreen;
inputContext.init_game = init_game;
while(1) {
if (!handle_input_frame(inputContext)) {
break;
}
}
while (1) {
if (!handle_input_frame(inputContext)) {
break;
}
}
return 0;
return 0;
}