#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); 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); // Recolor a copy of the rotten-apple tile (shadow/outline/fill/rot-spots) into unused // palette slots to make the "cursed" apple sprite, since all 12 tiles are already in use. const uint16_t CURSE_PAL_SHADOW = 14; const uint16_t CURSE_PAL_OUTLINE = 15; const uint16_t CURSE_PAL_FILL = 16; const uint16_t CURSE_PAL_GLOW = 17; SPRITE_PALETTE_SUB[CURSE_PAL_SHADOW] = RGB15(9, 5, 12); SPRITE_PALETTE_SUB[CURSE_PAL_OUTLINE] = RGB15(10, 0, 14); SPRITE_PALETTE_SUB[CURSE_PAL_FILL] = RGB15(18, 0, 22); SPRITE_PALETTE_SUB[CURSE_PAL_GLOW] = RGB15(28, 0, 24); const auto *rottenTilePixels = static_cast(get_sprite_pointer(11)); uint8_t cursedTilePixels[64]; for (int i = 0; i < 64; i++) { switch (rottenTilePixels[i]) { case 5: cursedTilePixels[i] = static_cast(CURSE_PAL_SHADOW); break; case 7: cursedTilePixels[i] = static_cast(CURSE_PAL_OUTLINE); break; case 10: cursedTilePixels[i] = static_cast(CURSE_PAL_FILL); break; case 12: cursedTilePixels[i] = static_cast(CURSE_PAL_GLOW); break; default: cursedTilePixels[i] = rottenTilePixels[i]; break; } } dmaCopy(cursedTilePixels, renderAssets.gfx_apple_cursed, 64); 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); } oamUpdate(&oamSub); //flashy_colors(); } void do_tick() { if (gameState.scene == Scene::Playing && !paused) { ticks++; if (!gameOver){ const bool wasGameOver = 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_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); tick_status_effects(gameState); if (!wasGameOver && gameOver) { audio_stop_music(); audio_play_fail(audioContext); } //consoleSelect(&bottomScreen); draw_game(gameState, renderAssets); } } } void init_game() { applesEaten = 0; 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; }