282 lines
7.8 KiB
C++
282 lines
7.8 KiB
C++
#include "game_render.h"
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <stdio.h>
|
|
|
|
#include "tilemap.h"
|
|
|
|
namespace {
|
|
|
|
void draw_snake_head(const GameState &state, const RenderAssets &assets,
|
|
uint16_t &oamId) {
|
|
if (oamId >= 128 || state.snake.empty()) {
|
|
return;
|
|
}
|
|
|
|
const std::array<int, 3> dot = state.snake.front();
|
|
u16 *headGfx = nullptr;
|
|
bool hflip = false;
|
|
bool vflip = false;
|
|
|
|
switch (state.snakeDirection) {
|
|
case 0: // Up
|
|
headGfx = assets.gfx_head_v;
|
|
break;
|
|
case 1: // Right
|
|
headGfx = assets.gfx_head_h;
|
|
break;
|
|
case 2: // Down
|
|
headGfx = assets.gfx_head_v;
|
|
vflip = true;
|
|
break;
|
|
case 3: // Left
|
|
headGfx = assets.gfx_head_h;
|
|
hflip = true;
|
|
break;
|
|
default:
|
|
headGfx = assets.gfx_head_h;
|
|
break;
|
|
}
|
|
|
|
oamSet(&oamSub, oamId, dot[0] * 8, dot[1] * 8, 0, 0, SpriteSize_8x8,
|
|
SpriteColorFormat_256Color, headGfx, -1, false, false, hflip, vflip,
|
|
false);
|
|
oamId++;
|
|
}
|
|
|
|
void draw_snake_tail(const GameState &state, const RenderAssets &assets,
|
|
uint16_t &oamId) {
|
|
if (oamId >= 128 || state.snake.size() < 2) {
|
|
return;
|
|
}
|
|
|
|
const std::array<int, 3> dot = state.snake.back();
|
|
const size_t var = (state.snake.size() - 1) % 2;
|
|
u16 *tailGfx = nullptr;
|
|
bool hflip = false;
|
|
bool vflip = false;
|
|
|
|
switch (state.snake.at(state.snake.size() - 2).at(2)) {
|
|
case 0: // Up
|
|
tailGfx = assets.gfx_tail_v[var];
|
|
vflip = true;
|
|
break;
|
|
case 1: // Right
|
|
tailGfx = assets.gfx_tail_h[var];
|
|
hflip = true;
|
|
break;
|
|
case 2: // Down
|
|
tailGfx = assets.gfx_tail_v[var];
|
|
break;
|
|
case 3: // Left
|
|
tailGfx = assets.gfx_tail_h[var];
|
|
break;
|
|
default:
|
|
tailGfx = assets.gfx_tail_h[var];
|
|
break;
|
|
}
|
|
|
|
oamSet(&oamSub, oamId, dot[0] * 8, dot[1] * 8, 0, 0, SpriteSize_8x8,
|
|
SpriteColorFormat_256Color, tailGfx, -1, false, false, hflip, vflip,
|
|
false);
|
|
oamId++;
|
|
}
|
|
|
|
void draw_snake(const GameState &state, const RenderAssets &assets,
|
|
uint16_t &oamId) {
|
|
if (state.snake.empty()) {
|
|
return;
|
|
}
|
|
|
|
draw_snake_head(state, assets, oamId);
|
|
|
|
for (size_t i = 1; i + 1 < state.snake.size(); i++) {
|
|
if (oamId >= 128) {
|
|
return;
|
|
}
|
|
oamSet(&oamSub, oamId, state.snake[i][0] * 8, state.snake[i][1] * 8, 0, 0,
|
|
SpriteSize_8x8, SpriteColorFormat_256Color,
|
|
(i % 2) ? assets.gfx_segment_small : assets.gfx_segment, -1, false,
|
|
false, false, false, false);
|
|
oamId++;
|
|
}
|
|
|
|
if (state.snake.size() > 1) {
|
|
draw_snake_tail(state, assets, oamId);
|
|
}
|
|
}
|
|
|
|
void draw_apples(const GameState &state, const RenderAssets &assets,
|
|
uint16_t &oamId) {
|
|
u16 *appleGfx =
|
|
(state.ticks % 4 == 0) ? assets.gfx_apple_blink : assets.gfx_apple;
|
|
for (const std::array<int, 2> &apple : state.apples) {
|
|
if (oamId >= 128) {
|
|
return;
|
|
}
|
|
oamSet(&oamSub, oamId, apple[0] * 8, apple[1] * 8, 0, 0, SpriteSize_8x8,
|
|
SpriteColorFormat_256Color, appleGfx, -1, false, false, false, false,
|
|
false);
|
|
oamId++;
|
|
}
|
|
}
|
|
|
|
void draw_apples_rotten(const GameState &state, const RenderAssets &assets,
|
|
uint16_t &oamId) {
|
|
for (const std::array<int, 2> &apple : state.bad_apples) {
|
|
if (oamId >= 128) {
|
|
return;
|
|
}
|
|
oamSet(&oamSub, oamId, apple[0] * 8, apple[1] * 8, 0, 0, SpriteSize_8x8,
|
|
SpriteColorFormat_256Color, assets.gfx_apple_rotten, -1, false,
|
|
false, false, false, false);
|
|
oamId++;
|
|
}
|
|
}
|
|
|
|
void draw_apples_cursed(const GameState &state, const RenderAssets &assets,
|
|
uint16_t &oamId) {
|
|
for (const std::array<int, 2> &apple : state.cursed_apples) {
|
|
if (oamId >= 128) {
|
|
return;
|
|
}
|
|
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 invalidate_hud(RenderAssets &assets) { assets.hudCache.dirty = true; }
|
|
|
|
void draw_game(const GameState &state, const RenderAssets &assets) {
|
|
uint16_t oamId = 0;
|
|
|
|
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);
|
|
|
|
// Hide all unused sprites up to hardware limit (128) to eliminate trailing
|
|
// ghost sprites
|
|
if (oamId < 128) {
|
|
oamClear(&oamSub, oamId, 128 - oamId);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// On Game Over, hide all sprites so none are left dangling
|
|
oamClear(&oamSub, 0, 128);
|
|
|
|
iprintf("\x1b[11;12HGAME OVER!");
|
|
consoleSelect(assets.topScreen);
|
|
iprintf("\x1b[10;10H Score: %i ", state.score);
|
|
iprintf("\x1b[11;12HGAME OVER!");
|
|
}
|
|
|
|
void draw_stats(const GameState &state, RenderAssets &assets, bool internals,
|
|
bool musicEnabled, uint8_t frame) {
|
|
if (internals) {
|
|
iprintf("\x1b[0;0HFrame = %i\nTick = %i\ndir = %i", frame, state.ticks,
|
|
state.snakeDirection);
|
|
iprintf("\x1b[3;0HLength: %i", state.snakelength);
|
|
if (!state.snake.empty()) {
|
|
iprintf("\x1b[4;0Hx=%i\ny=%i", state.snake.front()[0],
|
|
state.snake.front()[1]);
|
|
}
|
|
}
|
|
|
|
HudCache &cache = assets.hudCache;
|
|
const bool cursed = (state.curseTicksRemaining > 0);
|
|
|
|
const bool needs_score_update = cache.dirty || (cache.score != state.score);
|
|
const bool needs_curse_update = cache.dirty || (cache.cursed != cursed);
|
|
const bool needs_pause_update =
|
|
cache.dirty || (cache.paused != state.paused) ||
|
|
(cache.musicEnabled != musicEnabled) ||
|
|
(cache.hardMode != state.hardMode) || (cache.gameOver != state.gameOver);
|
|
|
|
if (needs_score_update) {
|
|
iprintf("\x1b[10;12HScore: %i", state.score);
|
|
cache.score = state.score;
|
|
}
|
|
|
|
if (needs_curse_update) {
|
|
if (cursed) {
|
|
iprintf("\x1b[1;7HCONTROLS INVERTED!");
|
|
} else {
|
|
iprintf("\x1b[1;7H ");
|
|
}
|
|
cache.cursed = cursed;
|
|
}
|
|
|
|
if (needs_pause_update) {
|
|
if (!state.paused) {
|
|
if (state.gameOver) {
|
|
iprintf("\x1b[23;0HB = Main Menu");
|
|
} else if (cache.paused) {
|
|
// Clear any leftover pause strings if previously paused
|
|
iprintf("\x1b[21;0H ");
|
|
iprintf("\x1b[22;0H ");
|
|
iprintf("\x1b[23;0H ");
|
|
}
|
|
} else {
|
|
iprintf("\x1b[21;0HB = Main Menu");
|
|
if (state.hardMode) {
|
|
iprintf("\x1b[22;0HY = Restart in normal mode");
|
|
} else {
|
|
iprintf("\x1b[22;0HY = Restart in Hard mode ");
|
|
}
|
|
if (musicEnabled) {
|
|
iprintf("\x1b[23;0HX = Disable Music");
|
|
} else {
|
|
iprintf("\x1b[23;0HX = Enable Music ");
|
|
}
|
|
}
|
|
cache.paused = state.paused;
|
|
cache.musicEnabled = musicEnabled;
|
|
cache.hardMode = state.hardMode;
|
|
cache.gameOver = state.gameOver;
|
|
}
|
|
|
|
cache.dirty = false;
|
|
}
|
|
|
|
void draw_menu(const GameState &state, RenderAssets &assets,
|
|
bool musicEnabled) {
|
|
invalidate_hud(assets);
|
|
|
|
oamClear(&oamSub, 0, 128);
|
|
oamUpdate(&oamSub);
|
|
|
|
consoleSelect(assets.bottomScreen);
|
|
consoleClear();
|
|
|
|
consoleSelect(assets.topScreen);
|
|
consoleClear();
|
|
|
|
iprintf("\x1b[3;10HSNAKE PLUS");
|
|
iprintf("\x1b[5;5H---------------------");
|
|
|
|
const char *hardModeLabel = state.hardMode ? "ON " : "OFF";
|
|
const char *musicLabel = musicEnabled ? "ON " : "OFF";
|
|
|
|
iprintf("\x1b[8;7H%c Start Game", state.menuSelection == 0 ? '>' : ' ');
|
|
iprintf("\x1b[10;7H%c Hard Mode: %s", state.menuSelection == 1 ? '>' : ' ',
|
|
hardModeLabel);
|
|
iprintf("\x1b[12;7H%c Music: %s", state.menuSelection == 2 ? '>' : ' ',
|
|
musicLabel);
|
|
|
|
iprintf("\x1b[19;3HUP/DOWN: Navigate");
|
|
iprintf("\x1b[20;3HA/START: Select");
|
|
}
|
|
|
|
void blink_apples(const GameState & /*state*/, u16 * /*gfx_apple*/) {
|
|
// Blinking is now handled directly by draw_apples() via sprite pointer
|
|
// selection without DMA.
|
|
}
|