Skip to main content

Project Structure

Una Aventura Inesperada is a Nintendo DS puzzle game written in C using devkitPro. The project follows a simple flat structure with all source files in a single directory.

File Organization

source/
├── main.c              # Main game logic and entry point
├── teselas.h           # Tile definitions and sprite data
├── menuPrincipal.h/.s  # Main menu graphics
├── menuCreditos.h/.s   # Credits screen graphics
├── HUD.h/.s            # Heads-up display graphics
├── CinematicaInicio*.h/.s  # Opening cinematic frames
├── CinematicaFinal*.h/.s   # Ending cinematic frames
└── Pregunta*.h/.s      # Question screen graphics
Each graphic asset has two files: a .h header file with metadata and a .s assembly file containing the actual bitmap data.

Code Organization

The game is structured around a single main.c file (~/workspace/source/source/main.c:1) containing all game logic.

Key Components

1

Hardware Initialization

The game initializes Nintendo DS hardware including:
  • VRAM configuration for dual screens
  • Background graphics layers
  • Tile and map memory allocation
  • Color palette setup (RGB15 format)
REG_POWERCNT = POWER_ALL_2D;
REG_DISPCNT_SUB = MODE_0_2D | DISPLAY_BG0_ACTIVE;
VRAM_C_CR = VRAM_ENABLE | VRAM_C_SUB_BG;
2

Game Loop

The main loop handles level resets and game state management:
while(1) {
    if(esActivoBotonReinicio == true) {
        scanKeys();
        keys = keysCurrent();
        if(keys & KEY_TOUCH && esActivoBotonesDialogos == true) {
            touchRead(&posicionXY);
            // Check if restart button is pressed
            if((posicionXY.px >= puntosReinicioNivelBoton[0].x && ...) {
                esJuegoReiniciado = true;
                ConsultarSistemaDialogo();
            }
        }
        swiWaitForVBlank();
    }
}
3

Interrupt Handlers

Three main interrupt handlers manage game events:
  • Keyboard/D-Pad input: TeclasJugador() - Processes player movement
  • Animation timer: ActualizarAnimacion() - Updates sprite frames
  • Dialog timer: HabilitarBotonesDialogo() - Prevents accidental input

Key Data Structures

PuntoPantalla

Defines touchscreen button boundaries (~/workspace/source/source/main.c:76):
struct PuntoPantalla {
    int x;
    int y;
};
Usage Example:
struct PuntoPantalla puntosBoton1[2] = {
    {50, 138},   // Top-left corner
    {213, 161}   // Bottom-right corner
};

PosicionEnemigo

Tracks enemy state and position (~/workspace/source/source/main.c:82):
struct PosicionEnemigo {
    int x;      // Column position
    int y;      // Row position  
    int vivo;   // Alive status (true/false)
};
The game maintains an array of up to 10 enemies:
struct PosicionEnemigo posicionesEnemigo[10];

Main Game Loop Architecture

State Management

The game uses boolean flags to manage state:
bool esPartidaAcabada = true;      // Game over state
bool jugadorVivo = false;           // Player alive
bool puedeJugadorMoverse = true;   // Movement allowed
bool esActivoBotonReinicio = false; // Restart button active
bool esJuegoComenzado = false;      // Game started
bool esFotograma1Activo = true;    // Animation frame toggle

Movement System

Player movement is constrained by:
  • Available moves (movimientosJugador)
  • Map boundaries (32x24 tile grid)
  • Collision detection with walls, enemies, and boxes
int movimientosJugador = 20;        // Current moves
int maximoMovimientosJugador = 20;  // Max moves for level
int posJugFila = 22, posJugColumna = 16;  // Player position

Memory Management

VRAM Organization

The game uses two VRAM banks:
  • VRAM_A: Main screen framebuffer (256x192x2 bytes)
  • VRAM_C: Sub screen background tiles and maps
// Tile memory for sprites (in VRAM_C)
u8* tileMemory = (u8*) BG_TILE_RAM_SUB(1);

// Map memory for level layout
u16* mapMemory = (u16*) BG_MAP_RAM_SUB(0);

Tile Memory Layout

Tiles are loaded sequentially into VRAM using DMA:
dmaCopy(t_jugadorF1Parte1, tileMemory, sizeof(t_jugadorF1Parte1));        // Index 0
dmaCopy(t_jugadorF1Parte2, tileMemory+(64*8), sizeof(t_jugadorF1Parte2)); // Index 8
dmaCopy(t_salida, tileMemory+(64*1), sizeof(t_salida));                   // Index 1
Each 8x8 tile occupies 64 bytes in memory. Tile indices are calculated as (offset / 64).

Map Memory

The game uses a 32x24 tile map (768 tiles total):
u16 nivel1[768] = {
    18,18, 18,18, 18,18, // Row 0 (empty tiles)
    // ... level layout data
};
Map positions are calculated as: position = row * 32 + column

Function Overview

Core Functions

FunctionPurposeLocation
main()Entry point, hardware init, main loopmain.c:157
ConfigurarInterrupciones()Sets up interrupt handlersmain.c:242
InicializarTeselas()Loads all tile graphicsmain.c:1065
GenerarNivel()Renders level from map datamain.c:792
CrearMenu()Displays main menumain.c:871
CrearDialogo()Shows question dialogsmain.c:939

Movement Functions

FunctionPurposeLocation
TeclasJugador()Handles player inputmain.c:271
MoverEnemigo()Moves or eliminates enemiesmain.c:518
MoverObstaculo()Pushes movable boxesmain.c:605
ComprobarSuelo()Returns original floor tilemain.c:446
ElegirFondoJugador()Selects player sprite with backgroundmain.c:464

Game Logic Functions

FunctionPurposeLocation
ConsultarSistemaDialogo()Manages level progressionmain.c:658
ActualizarBarraMovimientos()Updates stamina barmain.c:853
ActualizarAnimacion()Cycles sprite animationsmain.c:981
HabilitarBotonesDialogo()Re-enables dialog buttonsmain.c:780
The game uses a 2x2 tile grid for all sprites (player, enemies, NPCs, boxes), allowing for 16x16 pixel graphics.

Build docs developers (and LLMs) love