Add cursed apple: a status-effect apple that inverts controls
Eating a cursed apple inverts the snake's steering for CURSE_DURATION_TICKS ticks, shown on the top screen while active. The sprite is a recolored copy of the rotten-apple tile since all existing sprite tiles are already in use.
This commit is contained in:
1 parent
df9b859601
commit
0e7477031f
8 files changed
+96
-5
No files matched your search
@@ -5,6 +5,7 @@
|
|||||||
const int START_LENGTH = 4;
|
const int START_LENGTH = 4;
|
||||||
const int START_LENGTH_HARD =2;
|
const int START_LENGTH_HARD =2;
|
||||||
const int APPLE_SCORE = 10;
|
const int APPLE_SCORE = 10;
|
||||||
|
const int CURSE_DURATION_TICKS = 40;
|
||||||
|
|
||||||
//Screen size
|
//Screen size
|
||||||
const int X_MAX = 31;
|
const int X_MAX = 31;
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ void update_snake(GameState &state);
|
|||||||
bool check_collision_self(GameState &state);
|
bool check_collision_self(GameState &state);
|
||||||
bool check_collision_apples(GameState &state);
|
bool check_collision_apples(GameState &state);
|
||||||
bool check_collision_apples_rotten(GameState &state);
|
bool check_collision_apples_rotten(GameState &state);
|
||||||
|
bool check_collision_apples_cursed(GameState &state);
|
||||||
void spawn_apples(GameState &state);
|
void spawn_apples(GameState &state);
|
||||||
void spawn_apples_rotten(GameState &state);
|
void spawn_apples_rotten(GameState &state);
|
||||||
|
void spawn_apples_cursed(GameState &state);
|
||||||
|
void tick_status_effects(GameState &state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -15,6 +15,7 @@ struct RenderAssets {
|
|||||||
u16 *gfx_apple = nullptr;
|
u16 *gfx_apple = nullptr;
|
||||||
u16 *gfx_tail = nullptr;
|
u16 *gfx_tail = nullptr;
|
||||||
u16 *gfx_apple_rotten = nullptr;
|
u16 *gfx_apple_rotten = nullptr;
|
||||||
|
u16 *gfx_apple_cursed = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
void draw_game(const GameState &state, const RenderAssets &assets);
|
void draw_game(const GameState &state, const RenderAssets &assets);
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ struct GameState {
|
|||||||
bool paused = false;
|
bool paused = false;
|
||||||
bool hardMode = false;
|
bool hardMode = false;
|
||||||
|
|
||||||
|
uint16_t curseTicksRemaining = 0;
|
||||||
|
|
||||||
std::deque<std::array<int, 3>> snake;
|
std::deque<std::array<int, 3>> snake;
|
||||||
std::vector<std::array<int, 2>> apples;
|
std::vector<std::array<int, 2>> apples;
|
||||||
std::vector<std::array<int, 2>> bad_apples;
|
std::vector<std::array<int, 2>> bad_apples;
|
||||||
|
std::vector<std::array<int, 2>> cursed_apples;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -30,23 +30,25 @@ bool handle_input_frame(InputContext &ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.state->paused && !ctx.state->gameOver) {
|
if (!ctx.state->paused && !ctx.state->gameOver) {
|
||||||
|
const bool cursed = ctx.state->curseTicksRemaining > 0;
|
||||||
|
|
||||||
if ((keys != 0U) && ((keys & KEY_UP) != 0U)) {
|
if ((keys != 0U) && ((keys & KEY_UP) != 0U)) {
|
||||||
ctx.state->snakeDirection = 0;
|
ctx.state->snakeDirection = cursed ? 2 : 0;
|
||||||
draw_game(*ctx.state, *ctx.renderAssets);
|
draw_game(*ctx.state, *ctx.renderAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((keys != 0U) && ((keys & KEY_RIGHT) != 0U)) {
|
if ((keys != 0U) && ((keys & KEY_RIGHT) != 0U)) {
|
||||||
ctx.state->snakeDirection = 1;
|
ctx.state->snakeDirection = cursed ? 3 : 1;
|
||||||
draw_game(*ctx.state, *ctx.renderAssets);
|
draw_game(*ctx.state, *ctx.renderAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((keys != 0U) && ((keys & KEY_DOWN) != 0U)) {
|
if ((keys != 0U) && ((keys & KEY_DOWN) != 0U)) {
|
||||||
ctx.state->snakeDirection = 2;
|
ctx.state->snakeDirection = cursed ? 0 : 2;
|
||||||
draw_game(*ctx.state, *ctx.renderAssets);
|
draw_game(*ctx.state, *ctx.renderAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((keys != 0U) && ((keys & KEY_LEFT) != 0U)) {
|
if ((keys != 0U) && ((keys & KEY_LEFT) != 0U)) {
|
||||||
ctx.state->snakeDirection = 3;
|
ctx.state->snakeDirection = cursed ? 1 : 3;
|
||||||
draw_game(*ctx.state, *ctx.renderAssets);
|
draw_game(*ctx.state, *ctx.renderAssets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,23 @@ bool check_collision_apples_rotten(GameState &state) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_collision_apples_cursed(GameState &state) {
|
||||||
|
const std::array<int, 3> headpos = state.snake.front();
|
||||||
|
|
||||||
|
auto it = std::find_if(state.cursed_apples.begin(), state.cursed_apples.end(),
|
||||||
|
[&](const std::array<int, 2> &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) {
|
void spawn_apples(GameState &state) {
|
||||||
if (state.apples.size() < 1) {
|
if (state.apples.size() < 1) {
|
||||||
std::array<int, 2> apple = { rand() % (X_MAX + 1), rand() % (Y_MAX + 1) };
|
std::array<int, 2> 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);
|
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<int, 2> 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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int, 2> &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
|
} // namespace
|
||||||
|
|
||||||
void draw_game(const GameState &state, const RenderAssets &assets) {
|
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) {
|
if (!state.gameOver) {
|
||||||
draw_apples(state, assets, oamId);
|
draw_apples(state, assets, oamId);
|
||||||
draw_apples_rotten(state, assets, oamId);
|
draw_apples_rotten(state, assets, oamId);
|
||||||
|
draw_apples_cursed(state, assets, oamId);
|
||||||
draw_snake(state, assets, oamId);
|
draw_snake(state, assets, oamId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -126,6 +136,12 @@ void draw_stats(const GameState &state, bool internals, bool musicEnabled, uint8
|
|||||||
|
|
||||||
iprintf("\x1b[10;12HScore: %i", state.score);
|
iprintf("\x1b[10;12HScore: %i", state.score);
|
||||||
|
|
||||||
|
if (state.curseTicksRemaining > 0) {
|
||||||
|
iprintf("\x1b[1;7HCONTROLS INVERTED!");
|
||||||
|
} else {
|
||||||
|
iprintf("\x1b[1;7H ");
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.paused) {
|
if (!state.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-1
@@ -71,6 +71,7 @@ void setup() {
|
|||||||
renderAssets.gfx_head = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
renderAssets.gfx_head = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
renderAssets.gfx_apple = 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_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);
|
renderAssets.gfx_tail = oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color);
|
||||||
|
|
||||||
dmaCopy(get_sprite_pointer(1), renderAssets.gfx_segment, 64);
|
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);
|
dmaCopy(get_sprite_pointer(11), renderAssets.gfx_apple_rotten, 64);
|
||||||
|
|
||||||
consoleSelect(&topScreen);
|
consoleSelect(&topScreen);
|
||||||
|
|
||||||
|
|
||||||
dmaCopy(tilemapPal,SPRITE_PALETTE_SUB,tilemapPalLen);
|
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<const uint8_t *>(get_sprite_pointer(11));
|
||||||
|
uint8_t cursedTilePixels[64];
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
switch (rottenTilePixels[i]) {
|
||||||
|
case 5: cursedTilePixels[i] = static_cast<uint8_t>(CURSE_PAL_SHADOW); break;
|
||||||
|
case 7: cursedTilePixels[i] = static_cast<uint8_t>(CURSE_PAL_OUTLINE); break;
|
||||||
|
case 10: cursedTilePixels[i] = static_cast<uint8_t>(CURSE_PAL_FILL); break;
|
||||||
|
case 12: cursedTilePixels[i] = static_cast<uint8_t>(CURSE_PAL_GLOW); break;
|
||||||
|
default: cursedTilePixels[i] = rottenTilePixels[i]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dmaCopy(cursedTilePixels, renderAssets.gfx_apple_cursed, 64);
|
||||||
|
|
||||||
audio_setup(audioContext);
|
audio_setup(audioContext);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -115,8 +141,13 @@ void do_tick() {
|
|||||||
if (check_collision_apples_rotten(gameState)) {
|
if (check_collision_apples_rotten(gameState)) {
|
||||||
audio_play_boom(audioContext);
|
audio_play_boom(audioContext);
|
||||||
}
|
}
|
||||||
|
if (check_collision_apples_cursed(gameState)) {
|
||||||
|
audio_play_boom(audioContext);
|
||||||
|
}
|
||||||
spawn_apples(gameState);
|
spawn_apples(gameState);
|
||||||
spawn_apples_rotten(gameState);
|
spawn_apples_rotten(gameState);
|
||||||
|
spawn_apples_cursed(gameState);
|
||||||
|
tick_status_effects(gameState);
|
||||||
if (!wasGameOver && gameOver) {
|
if (!wasGameOver && gameOver) {
|
||||||
audio_stop_music();
|
audio_stop_music();
|
||||||
audio_play_fail(audioContext);
|
audio_play_fail(audioContext);
|
||||||
@@ -141,6 +172,8 @@ void init_game() {
|
|||||||
snakeDirection = 1;
|
snakeDirection = 1;
|
||||||
ticks = 0;
|
ticks = 0;
|
||||||
gameOver=false;
|
gameOver=false;
|
||||||
|
gameState.curseTicksRemaining = 0;
|
||||||
|
gameState.cursed_apples.clear();
|
||||||
consoleSelect(&topScreen);
|
consoleSelect(&topScreen);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
consoleSelect(&bottomScreen);
|
consoleSelect(&bottomScreen);
|
||||||
@@ -158,6 +191,8 @@ void init_game() {
|
|||||||
snakeDirection = 1;
|
snakeDirection = 1;
|
||||||
ticks = 0;
|
ticks = 0;
|
||||||
gameOver=false;
|
gameOver=false;
|
||||||
|
gameState.curseTicksRemaining = 0;
|
||||||
|
gameState.cursed_apples.clear();
|
||||||
consoleSelect(&topScreen);
|
consoleSelect(&topScreen);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
consoleSelect(&bottomScreen);
|
consoleSelect(&bottomScreen);
|
||||||
|
|||||||
Reference in new issue
Block a user