Skip to main content

Control Overview

Una Aventura Inesperada utilizes both Nintendo DS screens:
  • Top Screen: Game world (tile-based puzzle grid)
  • Bottom Screen: HUD, menus, dialogs, and touch controls
The game uses hardware interrupts for responsive input handling via ConfigurarInterrupciones() (source/main.c:242-260).

D-Pad Movement

The D-pad controls player movement on the grid. Movement is handled by the TeclasJugador() interrupt function (source/main.c:271-437).

Movement Keys

Up

D-Pad UpREG_KEYINPUT == 0x03BFMoves player 2 tiles north

Down

D-Pad DownREG_KEYINPUT == 0x037FMoves player 2 tiles south

Left

D-Pad LeftREG_KEYINPUT == 0x03DFMoves player 2 tiles west

Right

D-Pad RightREG_KEYINPUT == 0x03EFMoves player 2 tiles east

Input Values

The REG_KEYINPUT register contains button states. Each direction has a unique hexadecimal value:
// From source/main.c:274-436
REG_KEYINPUT == 0x03BF  // Up
REG_KEYINPUT == 0x037F  // Down
REG_KEYINPUT == 0x03DF  // Left
REG_KEYINPUT == 0x03EF  // Right
The NDS REG_KEYINPUT register uses inverted logic (0 = pressed, 1 = released):
  • Bit 0: A button
  • Bit 1: B button
  • Bit 2: Select
  • Bit 3: Start
  • Bit 4: Right
  • Bit 5: Left
  • Bit 6: Up
  • Bit 7: Down
  • Bits 8-9: R/L triggers
The values 0x03BF, 0x037F, etc. represent specific button combinations where only the directional bit is 0 (pressed).

Movement Execution

// From source/main.c:274-321
if (movimientosJugador > 0 &&           // Have stamina
    REG_KEYINPUT == 0x03BF &&           // Up pressed
    posJugFila > 0 &&                    // Not at top edge
    esPartidaAcabada == false &&         // Game active
    mapMemory[(posJugFila-1)*32+posJugColumna] != 18 &&  // Not void
    mapMemory[(posJugFila-1)*32+posJugColumna] != 28){   // Not wall
    
    // Check for obstacles
    if(mapMemory[(posJugFila-2)*32+posJugColumna] == 19){
        MoverObstaculo(0);  // Push box up
    }
    else if(mapMemory[(posJugFila-2)*32+posJugColumna] == 5 || 
            mapMemory[(posJugFila-2)*32+posJugColumna] == 42){
        MoverEnemigo(0, enemActual);  // Push enemy up
    }
    
    // Execute movement if path is clear
    if(mapMemory[(posJugFila-2)*32+posJugColumna] != 19 && 
       mapMemory[(posJugFila-2)*32+posJugColumna] != 5 && 
       puedeJugadorMoverse == true){
        
        // Restore old tiles
        mapMemory[(posJugFila)*32+posJugColumna] = ComprobarSuelo(...);
        // ... restore other 3 tiles
        
        posJugFila -= 2;  // Update position
        
        // Draw player at new position
        mapMemory[posJugFila*32+posJugColumna] = ElegirFondoJugador(0, ...);
        // ... draw other 3 tiles
    }
    
    movimientosJugador--;  // Consume stamina
    ActualizarBarraMovimientos();  // Update UI
}

Movement Constraints

Every movement input must satisfy these conditions:
1

Stamina Check

movimientosJugador > 0 - At least 1 movement remaining
2

Button Check

REG_KEYINPUT == [direction value] - Specific direction pressed
3

Boundary Check

Position not at map edge (0-31 columns, 0-23 rows)
4

Game State Check

esPartidaAcabada == false - Game is active (not in menu)
5

Obstacle Check

Target tile is not void (18) or wall (17/24/26/27/28)
6

Interaction Check

If box (19) or enemy (5/42) present, attempt to push
Important: Movement consumes stamina even if the action fails (e.g., pushing a box against a wall). Plan carefully!

Touch Screen Controls

The bottom screen provides touch-based navigation for menus and dialogs.

Touch Input Detection

Touch input is detected using the NDS touch API:
u32 keys;
scanKeys();  // Update key states
keys = keysCurrent();  // Get current frame's keys

if(keys & KEY_TOUCH){
    touchRead(&posicionXY);  // Read touch coordinates
    // Check if coordinates are within button bounds
}
The main menu has three touch buttons:

Start Game

Position: (66, 70) to (184, 93)Size: 118×23 pixelsStarts opening cinematic and Level 1

Credits

Position: (66, 134) to (184, 158)Size: 118×24 pixelsDisplays credits screen

Back

Position: (65, 162) to (184, 187)Size: 119×25 pixelsReturns from credits to main menu
Implementation:
// From source/main.c:131-142
struct PuntoPantalla puntosComenzaPartidaBoton [2]={
    {66,70},   // Top-left
    {184,93}   // Bottom-right
};
struct PuntoPantalla puntosCreditosBoton [2]={
    {66,134},
    {184,158}
};
struct PuntoPantalla puntosAtrasBoton [2]={
    {65,162},
    {184,187}
};

Dialog/Quiz Controls

Quiz questions display two answer buttons:
Top Answer Button
// From source/main.c:123-126
struct PuntoPantalla puntosBoton1 [2]={
    {50,138},   // Top-left
    {213,161}   // Bottom-right
};
Properties:
  • Position: (50, 138)
  • Size: 163×23 pixels
  • Returns: opcionElegida = 0
Touch Detection Logic:
// From source/main.c:953-961
if(keys & KEY_TOUCH && esActivoBotonesDialogos == true){
    touchRead(&posicionXY);
    
    // Check if touch is within button 1 bounds
    if((posicionXY.px >= puntosBoton1[0].x && posicionXY.px <= puntosBoton1[1].x) && 
       (posicionXY.py >= puntosBoton1[0].y && posicionXY.py <= puntosBoton1[1].y)){
        opcionElegida = 0;
    }
    // Check button 2
    else if((posicionXY.px >= puntosBoton2[0].x && posicionXY.px <= puntosBoton2[1].x) && 
            (posicionXY.py >= puntosBoton2[0].y && posicionXY.py <= puntosBoton2[1].y)){
        opcionElegida = 1;
    }
}

Restart Button

During gameplay, the HUD displays a restart button:
// From source/main.c:143-146
struct PuntoPantalla puntosReinicioNivelBoton [2]={
    {117,100},  // Top-left
    {236,125}   // Bottom-right
};
Properties:
  • Position: (117, 100)
  • Size: 119×25 pixels
  • Function: Restarts current level immediately
  • Availability: Only active when esActivoBotonReinicio == true
Detection:
// From source/main.c:220-233
if(esActivoBotonReinicio == true){
    scanKeys();
    keys = keysCurrent();
    if(keys & KEY_TOUCH && esActivoBotonesDialogos == true){
        touchRead(&posicionXY);
        if((posicionXY.px >= puntosReinicioNivelBoton[0].x && 
            posicionXY.px <= puntosReinicioNivelBoton[1].x) && 
           (posicionXY.py >= puntosReinicioNivelBoton[0].y && 
            posicionXY.py <= puntosReinicioNivelBoton[1].y)){
            esJuegoReiniciado = true;
            ConsultarSistemaDialogo();  // Reload level
        }
    }
}
The restart button is disabled during dialogs to prevent accidental level resets while answering quiz questions.

Button Mapping Reference

Hardware Buttons

ButtonREG_KEYINPUT ValueFunctionUsage
D-Pad Up0x03BFMove NorthPlayer movement
D-Pad Down0x037FMove SouthPlayer movement
D-Pad Left0x03DFMove WestPlayer movement
D-Pad Right0x03EFMove EastPlayer movement
Touch ScreenKEY_TOUCHSelectMenus, dialogs, restart
The game does not utilize:
  • A button
  • B button
  • X button
  • Y button
  • L trigger
  • R trigger
  • Start button
  • Select button
These could potentially be mapped to additional features in future versions.

Touch Screen Regions

RegionCoordinatesSizeContextAction
Start Game(66,70) → (184,93)118×23Main MenuBegin game
Credits(66,134) → (184,158)118×24Main MenuView credits
Back(65,162) → (184,187)119×25CreditsReturn to menu
Answer 1(50,138) → (213,161)163×23QuizSelect option 0
Answer 2(50,165) → (213,189)163×24QuizSelect option 1
Restart(117,100) → (236,125)119×25GameplayRestart level

Input Interrupts

The game uses hardware interrupts for responsive controls.

Interrupt Configuration

// From source/main.c:242-260
void ConfigurarInterrupciones(){
    // Enable keyboard interrupts
    irqSet(IRQ_KEYS, TeclasJugador);
    irqEnable(IRQ_KEYS);
    REG_KEYCNT = 0x7FFF;
    
    // Timer 0: Dialog button debounce
    irqEnable(IRQ_TIMER0);
    irqSet(IRQ_TIMER0, HabilitarBotonesDialogo);
    TIMER_DATA(0) = 32768; 
    TIMER_CR(0) = TIMER_DIV_1024 | TIMER_ENABLE | TIMER_IRQ_REQ;
    timerPause(0);
    
    // Timer 1: Animation updates
    irqEnable(IRQ_TIMER1);
    irqSet(IRQ_TIMER1, ActualizarAnimacion);
    TIMER_DATA(1) = 32768; 
    TIMER_CR(1) = TIMER_DIV_1024 | TIMER_ENABLE | TIMER_IRQ_REQ;
}
Keyboard Interrupt
  • Handler: TeclasJugador() (source/main.c:271)
  • Purpose: Detect D-pad presses
  • Trigger: Any button state change
  • Register: REG_KEYCNT = 0x7FFF (all keys enabled)

Control Flow Diagram

Best Practices

D-Pad Movement:
  • Press direction once per move (no holding)
  • Wait for stamina bar to update before next move
  • Plan route before executing to avoid wasted movements
Touch Controls:
  • Tap buttons precisely within defined regions
  • Wait for visual feedback before next tap
  • Be patient with dialog transitions (debounce timer active)
Common Mistakes:
  • Holding D-pad doesn’t repeat moves (single press per move)
  • Touching outside button bounds doesn’t register
  • Rapid tapping during dialogs may be ignored (timer cooldown)

Accessibility Notes

  • Grid Movement: 2-tile increments simplify navigation
  • Visual Feedback: Stamina bar provides clear movement tracking
  • Touch Regions: Large button areas (100+ pixels) reduce precision requirements
  • Input Debouncing: Prevents accidental double-inputs
The game’s interrupt-driven input system ensures responsive controls even during graphical updates, maintaining smooth gameplay on the NDS hardware.

Build docs developers (and LLMs) love