249 lines
7.5 KiB
C++
249 lines
7.5 KiB
C++
#include <nds.h>
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <array>
|
|
#include <consts.h>
|
|
#include <game_state.h>
|
|
#include <game_logic.h>
|
|
#include <game_render.h>
|
|
#include <game_audio.h>
|
|
#include <game_input.h>
|
|
|
|
#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;
|
|
|
|
bool &gameOver = gameState.gameOver;
|
|
bool &paused = gameState.paused;
|
|
bool &hardMode = gameState.hardMode;
|
|
|
|
std::deque<std::array<int, 3>> &snake = gameState.snake;
|
|
std::vector<std::array<int, 2>> &apples = gameState.apples;
|
|
std::vector<std::array<int, 2>> &bad_apples = gameState.bad_apples;
|
|
|
|
// 31x23
|
|
PrintConsole topScreen;
|
|
PrintConsole bottomScreen;
|
|
|
|
int sound;
|
|
|
|
AudioContext audioContext;
|
|
|
|
RenderAssets renderAssets;
|
|
|
|
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);
|
|
|
|
renderAssets.topScreen = &topScreen;
|
|
renderAssets.bottomScreen = &bottomScreen;
|
|
|
|
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<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);
|
|
|
|
}
|
|
void handle_vblank() {
|
|
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);
|
|
}
|
|
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() {
|
|
|
|
if (hardMode) {
|
|
timerStart(0, ClockDivider_1024, TIMER_FREQ_1024(9), do_tick);
|
|
setBackdropColorSub(RGB15(10,5,5));
|
|
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, TIMER_FREQ_1024(5), do_tick);
|
|
setBackdropColorSub(RGB15(5,5,5));
|
|
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();
|
|
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;
|
|
}
|