diff --git a/include/consts.h b/include/consts.h index 2046238..52527dd 100644 --- a/include/consts.h +++ b/include/consts.h @@ -5,6 +5,7 @@ const int START_LENGTH = 4; const int START_LENGTH_HARD =2; const int APPLE_SCORE = 10; +const int CURSE_DURATION_TICKS = 40; //Screen size const int X_MAX = 31; diff --git a/include/game_logic.h b/include/game_logic.h index 6dfec77..2e8dace 100644 --- a/include/game_logic.h +++ b/include/game_logic.h @@ -7,7 +7,10 @@ void update_snake(GameState &state); bool check_collision_self(GameState &state); bool check_collision_apples(GameState &state); bool check_collision_apples_rotten(GameState &state); +bool check_collision_apples_cursed(GameState &state); void spawn_apples(GameState &state); void spawn_apples_rotten(GameState &state); +void spawn_apples_cursed(GameState &state); +void tick_status_effects(GameState &state); #endif diff --git a/include/game_render.h b/include/game_render.h index f3e69f9..72772ba 100644 --- a/include/game_render.h +++ b/include/game_render.h @@ -15,6 +15,7 @@ struct RenderAssets { u16 *gfx_apple = nullptr; u16 *gfx_tail = nullptr; u16 *gfx_apple_rotten = nullptr; + u16 *gfx_apple_cursed = nullptr; }; void draw_game(const GameState &state, const RenderAssets &assets); diff --git a/include/game_state.h b/include/game_state.h index 913ec65..7384161 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -16,9 +16,12 @@ struct GameState { bool paused = false; bool hardMode = false; + uint16_t curseTicksRemaining = 0; + std::deque> snake; std::vector> apples; std::vector> bad_apples; + std::vector> cursed_apples; }; #endif diff --git a/source/game_input.cpp b/source/game_input.cpp index 07dcfb8..e49a0b9 100644 --- a/source/game_input.cpp +++ b/source/game_input.cpp @@ -30,23 +30,25 @@ bool handle_input_frame(InputContext &ctx) { } if (!ctx.state->paused && !ctx.state->gameOver) { + const bool cursed = ctx.state->curseTicksRemaining > 0; + if ((keys != 0U) && ((keys & KEY_UP) != 0U)) { - ctx.state->snakeDirection = 0; + ctx.state->snakeDirection = cursed ? 2 : 0; draw_game(*ctx.state, *ctx.renderAssets); } if ((keys != 0U) && ((keys & KEY_RIGHT) != 0U)) { - ctx.state->snakeDirection = 1; + ctx.state->snakeDirection = cursed ? 3 : 1; draw_game(*ctx.state, *ctx.renderAssets); } if ((keys != 0U) && ((keys & KEY_DOWN) != 0U)) { - ctx.state->snakeDirection = 2; + ctx.state->snakeDirection = cursed ? 0 : 2; draw_game(*ctx.state, *ctx.renderAssets); } if ((keys != 0U) && ((keys & KEY_LEFT) != 0U)) { - ctx.state->snakeDirection = 3; + ctx.state->snakeDirection = cursed ? 1 : 3; draw_game(*ctx.state, *ctx.renderAssets); } } diff --git a/source/game_logic.cpp b/source/game_logic.cpp index d8832e0..47a02cb 100644 --- a/source/game_logic.cpp +++ b/source/game_logic.cpp @@ -89,6 +89,23 @@ bool check_collision_apples_rotten(GameState &state) { return true; } +bool check_collision_apples_cursed(GameState &state) { + const std::array headpos = state.snake.front(); + + auto it = std::find_if(state.cursed_apples.begin(), state.cursed_apples.end(), + [&](const std::array &apple) { + return apple[0] == headpos[0] && apple[1] == headpos[1]; + }); + + if (it == state.cursed_apples.end()) { + return false; + } + + state.cursed_apples.erase(it); + state.curseTicksRemaining = CURSE_DURATION_TICKS; + return true; +} + void spawn_apples(GameState &state) { if (state.apples.size() < 1) { std::array apple = { rand() % (X_MAX + 1), rand() % (Y_MAX + 1) }; @@ -102,3 +119,16 @@ void spawn_apples_rotten(GameState &state) { state.bad_apples.push_back(badApple); } } + +void spawn_apples_cursed(GameState &state) { + if (state.cursed_apples.empty() && (state.ticks % 20 == 0) && ((rand() % 5) == 0)) { + std::array cursedApple = { rand() % (X_MAX + 1), rand() % (Y_MAX + 1) }; + state.cursed_apples.push_back(cursedApple); + } +} + +void tick_status_effects(GameState &state) { + if (state.curseTicksRemaining > 0) { + state.curseTicksRemaining--; + } +} diff --git a/source/game_render.cpp b/source/game_render.cpp index 5e5f09c..470bca4 100644 --- a/source/game_render.cpp +++ b/source/game_render.cpp @@ -100,6 +100,15 @@ void draw_apples_rotten(const GameState &state, const RenderAssets &assets, uint } } +void draw_apples_cursed(const GameState &state, const RenderAssets &assets, uint16_t &oamId) { + for (const std::array &apple : state.cursed_apples) { + oamSet(&oamSub, oamId, apple[0] * 8, apple[1] * 8, 0, 0, SpriteSize_8x8, + SpriteColorFormat_256Color, assets.gfx_apple_cursed, -1, false, false, false, false, + false); + oamId++; + } +} + } // namespace void draw_game(const GameState &state, const RenderAssets &assets) { @@ -108,6 +117,7 @@ void draw_game(const GameState &state, const RenderAssets &assets) { if (!state.gameOver) { draw_apples(state, assets, oamId); draw_apples_rotten(state, assets, oamId); + draw_apples_cursed(state, assets, oamId); draw_snake(state, assets, oamId); return; } @@ -126,6 +136,12 @@ void draw_stats(const GameState &state, bool internals, bool musicEnabled, uint8 iprintf("\x1b[10;12HScore: %i", state.score); + if (state.curseTicksRemaining > 0) { + iprintf("\x1b[1;7HCONTROLS INVERTED!"); + } else { + iprintf("\x1b[1;7H "); + } + if (!state.paused) { return; } diff --git a/source/main.cpp b/source/main.cpp index 350428b..d3adc88 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -71,6 +71,7 @@ void setup() { renderAssets.gfx_head = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color); renderAssets.gfx_apple = 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_tail = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color); dmaCopy(get_sprite_pointer(1), renderAssets.gfx_segment, 64); @@ -80,10 +81,35 @@ void setup() { 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); } @@ -115,8 +141,13 @@ void 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); @@ -141,6 +172,8 @@ void init_game() { snakeDirection = 1; ticks = 0; gameOver=false; + gameState.curseTicksRemaining = 0; + gameState.cursed_apples.clear(); consoleSelect(&topScreen); consoleClear(); consoleSelect(&bottomScreen); @@ -158,6 +191,8 @@ void init_game() { snakeDirection = 1; ticks = 0; gameOver=false; + gameState.curseTicksRemaining = 0; + gameState.cursed_apples.clear(); consoleSelect(&topScreen); consoleClear(); consoleSelect(&bottomScreen);