#include #include #include #include #include #include #include #include #include #include #include #include "tilemap.h" using namespace std; volatile uint8_t frame = 0; volatile bool internals = false; GameState gameState; volatile uint16_t &ticks = gameState.ticks; volatile uint16_t &snakelength = gameState.snakelength; /*** * 0 = up * 1 = right * 2 = down * 3 = left ***/ volatile uint16_t &snakeDirection = gameState.snakeDirection; volatile uint16_t &score = gameState.score; volatile uint16_t &applesEaten = gameState.applesEaten; bool &gameOver = gameState.gameOver; bool &paused = gameState.paused; bool &hardMode = gameState.hardMode; std::deque> &snake = gameState.snake; std::vector> &apples = gameState.apples; std::vector> &bad_apples = gameState.bad_apples; // 31x23 PrintConsole topScreen; PrintConsole bottomScreen; int sound; AudioContext audioContext; RenderAssets renderAssets; BackgroundContext bgContext; const void * get_sprite_pointer(uint16_t spriteIndex) { return &tilemapTiles[spriteIndex*16]; } 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); bg_setup(bgContext, topScreen.bgId, bottomScreen.bgId); renderAssets.topScreen = &topScreen; renderAssets.bottomScreen = &bottomScreen; renderAssets.bg_context = &bgContext; 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(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); consoleSelect(&topScreen); dmaCopy(tilemapPal, SPRITE_PALETTE_SUB, tilemapPalLen); // 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 // 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) static const uint32_t CURSED_APPLE_TILE[16] = { 0x10000000, 0x00001414, 0x10101000, 0x00101014, 0x12111110, 0x10111112, 0x13121110, 0x10111112, 0x13131110, 0x10111112, 0x13121110, 0x10111211, 0x11111000, 0x00101111, 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) auto *dest_apple_cursed = static_cast(renderAssets.gfx_apple_cursed); const auto *src_apple_cursed = reinterpret_cast(CURSED_APPLE_TILE); for (size_t i = 0; i < 32; i++) { dest_apple_cursed[i] = src_apple_cursed[i]; } auto *dest_barrier = static_cast(renderAssets.gfx_barrier); const auto *src_barrier = reinterpret_cast(BARRIER_SOLID_TILE); for (size_t i = 0; i < 32; i++) { dest_barrier[i] = src_barrier[i]; } auto *dest_barrier_warn = static_cast(renderAssets.gfx_barrier_warn); const auto *src_barrier_warn = reinterpret_cast(BARRIER_WARN_TILE); for (size_t i = 0; i < 32; i++) { dest_barrier_warn[i] = src_barrier_warn[i]; } 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); } // 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(7U - pulse); // 0, 1, 2, 3, 3, 2, 1, 0 SPRITE_PALETTE_SUB[CURSE_PAL_FILL] = static_cast(RGB15(20 + (wave * 3), 2 + wave, 26 + wave)); SPRITE_PALETTE_SUB[CURSE_PAL_GLOW] = static_cast(RGB15(26 + wave, 6 + (wave * 4), 31)); SPRITE_PALETTE_SUB[CURSE_PAL_CORE] = (wave >= 2U) ? static_cast(RGB15(31, 31, 31)) : static_cast(RGB15(31, 22 + (wave * 3), 31)); SPRITE_PALETTE_SUB[CURSE_PAL_LEAF] = static_cast(RGB15(2 + (wave * 2), 31, 10 + (wave * 3))); 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); } 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() { applesEaten = 0; reset_barriers(gameState); if (hardMode) { timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick); setBackdropColorSub(RGB15(8, 3, 3)); setBackdropColor(RGB15(6, 2, 2)); score = 0; snakelength = START_LENGTH_HARD; snake = { {2,1,1}, {1,1,1} }; apples.clear(); bad_apples.clear(); snakeDirection = 1; ticks = 0; gameOver=false; gameState.curseTicksRemaining = 0; gameState.cursed_apples.clear(); consoleSelect(&topScreen); consoleClear(); invalidate_hud(renderAssets); consoleSelect(&bottomScreen); consoleClear(); oamClear(&oamSub,0,128); audio_start_music(audioContext); } else { timerStart(0, ClockDivider_1024, calculate_timer_reload(gameState), do_tick); setBackdropColorSub(RGB15(3, 4, 7)); setBackdropColor(RGB15(2, 3, 6)); score = 0; snakelength = START_LENGTH; snake = { {4,1,1}, {3,1,1}, {2,1,1}, {1,1,1} }; snakeDirection = 1; ticks = 0; gameOver=false; gameState.curseTicksRemaining = 0; gameState.cursed_apples.clear(); consoleSelect(&topScreen); consoleClear(); invalidate_hud(renderAssets); consoleSelect(&bottomScreen); consoleClear(); oamClear(&oamSub,0,128); audio_start_music(audioContext); } } int main(void) { // 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)); InputContext inputContext; 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; } } return 0; }