feat: Add rat AI, 4-channel tracker, interactive cursor, and test suite updates

This commit is contained in:
2026-06-17 21:39:20 +02:00
parent 08218f3cda
commit 4f9e237698
22 changed files with 1091 additions and 66 deletions
+57
View File
@@ -0,0 +1,57 @@
#include "cursor.h"
#include "maze.h"
#include <gb/gb.h>
const unsigned char CursorSpriteData[] = {
// Bordo quadrato 8x8 (Nero, colore 3 -> 11)
0xFF,0xFF, 0x81,0x81, 0x81,0x81, 0x81,0x81,
0x81,0x81, 0x81,0x81, 0x81,0x81, 0xFF,0xFF
};
static uint8_t cursor_x = 1;
static uint8_t cursor_y = 1;
static uint8_t previous_keys = 0;
static uint8_t blink = 0;
void init_cursor(void) {
cursor_x = 1;
cursor_y = 1;
previous_keys = 0;
blink = 0;
// Carica il tile del cursore nell'indice 4 della VRAM degli Sprite
// (Gli indici 0-3 sono usati dai tile del topo)
set_sprite_data(4, 1, CursorSpriteData);
// Usa lo sprite hardware numero 2 (0 e 1 sono il topo)
set_sprite_tile(2, 4);
}
void update_cursor(void) {
uint8_t keys = joypad();
// Movimento controllato dal D-PAD
if ((keys & J_UP) && !(previous_keys & J_UP)) {
if (cursor_y > 0) cursor_y--;
}
if ((keys & J_DOWN) && !(previous_keys & J_DOWN)) {
if (cursor_y < MAZE_HEIGHT - 1) cursor_y++;
}
if ((keys & J_LEFT) && !(previous_keys & J_LEFT)) {
if (cursor_x > 0) cursor_x--;
}
if ((keys & J_RIGHT) && !(previous_keys & J_RIGHT)) {
if (cursor_x < MAZE_WIDTH - 1) cursor_x++;
}
previous_keys = keys;
// Effetto lampeggiante: nascondi il cursore per metà del ciclo (ogni 16 frame)
blink++;
if (blink & 0x10) {
move_sprite(2, 0, 0); // Nascondi spostandolo fuori schermo
} else {
// Posiziona il cursore calcolando offset (12, 20) per centrarlo sulla griglia 8x8
move_sprite(2, cursor_x * 8 + 12, cursor_y * 8 + 20);
}
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef CURSOR_H
#define CURSOR_H
#include <stdint.h>
void init_cursor(void);
void update_cursor(void);
#endif
+84
View File
@@ -0,0 +1,84 @@
/**
* @file main.c
* @brief Entry point e inizializzazione hardware del Game Boy.
*
* Questo modulo funge da collante: inizializza le periferiche video,
* semina la randomicità per il generatore, invoca l'algoritmo logico e
* disegna i tile sullo schermo del Game Boy, manipolando i registri hardware.
*/
#include <gb/gb.h>
#include <stdint.h>
#include <rand.h>
#include "maze.h"
#include "tiles.h"
#include "rat.h"
#include "music.h"
#include "cursor.h"
void main(void) {
uint8_t y, x;
uint8_t black = 1;
// Attende il termine dell'aggiornamento verticale dello schermo (VBLANK).
// Caricare dati in VRAM al di fuori di questo periodo può causare tearing o artefatti.
wait_vbl_done();
// Invia i dati grafici (array TileData) dal banco ROM alla VRAM a partire dall'indice 0.
set_bkg_data(0, 2, TileData);
// La mappa background in memoria hardware è grande 32x32 tiles.
// Riempiamo preventivamente tutta quest'area nascosta con il tile Nero (indice 1),
// utile per mascherare i bordi quando lo schermo verrà "scrollato" successivamente.
for (y = 0; y < 32; y++) {
for (x = 0; x < 32; x++) {
set_bkg_tiles(x, y, 1, 1, &black);
}
}
// Inizializza il seme dei numeri casuali.
initrand(DIV_REG);
// Invoca la funzione logica per popolare l'array `maze` in memoria.
generate_maze();
// Riversa l'array generato `maze` nella memoria del Background (VRAM Mappa).
for (y = 0; y < MAZE_HEIGHT; y++) {
for (x = 0; x < MAZE_WIDTH; x++) {
set_bkg_tiles(x, y, 1, 1, &maze[y][x]);
}
}
// Trucco di centraggio (Hardware Scrolling):
move_bkg(252, 252);
// Inizializza il topo autonomo nel punto di partenza (1, 1)
init_rat(1, 1);
// Inizializza il cursore controllato dal giocatore
init_cursor();
// Inizializza il sequencer musicale
init_music();
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
// Game Loop primario
while (1) {
// Aggiorna la musica in background
update_music();
// Aggiorna l'intelligenza artificiale e l'animazione del topo
update_rat();
// Aggiorna l'input del cursore del giocatore
update_cursor();
// Attendi la sincronizzazione con il monitor
wait_vbl_done();
}
}
+95
View File
@@ -0,0 +1,95 @@
/**
* @file maze.c
* @brief Implementazione dell'algoritmo di generazione del labirinto.
*/
#include "maze.h"
#include <rand.h>
// Istanza globale in RAM (WRAM) della mappa del labirinto.
uint8_t maze[MAZE_HEIGHT][MAZE_WIDTH];
// Stack customizzato utilizzato per l'algoritmo Recursive Backtracker.
// Si evitano le chiamate di funzione ricorsive per non esaurire
// lo spazio dello stack hardware del Game Boy.
uint8_t stack_x[100];
uint8_t stack_y[100];
uint8_t stack_ptr = 0;
/**
* @brief Inserisce una nuova coordinata in cima allo stack.
* @param x Posizione X della stanza.
* @param y Posizione Y della stanza.
*/
static void push(uint8_t x, uint8_t y) {
stack_x[stack_ptr] = x;
stack_y[stack_ptr] = y;
stack_ptr++;
}
/**
* @brief Estrae e rimuove l'ultima coordinata dallo stack.
* @param x Puntatore su cui scrivere la X rimossa.
* @param y Puntatore su cui scrivere la Y rimossa.
*/
static void pop(uint8_t *x, uint8_t *y) {
stack_ptr--;
*x = stack_x[stack_ptr];
*y = stack_y[stack_ptr];
}
void generate_maze(void) {
uint8_t x, y;
uint8_t nx, ny;
uint8_t dirs[4];
uint8_t count, r;
// Fase 1: Inizializza l'intera griglia con blocchi di muro (1)
for (y = 0; y < MAZE_HEIGHT; y++) {
for (x = 0; x < MAZE_WIDTH; x++) {
maze[y][x] = 1;
}
}
// Fase 2: Imposta il punto iniziale di scavo.
// Il punto in (1, 1) è la prima "stanza" calpestabile.
stack_ptr = 0;
push(1, 1);
maze[1][1] = 0; // 0 = calpestabile
// Fase 3: Scavo iterativo (Recursive Backtracker)
while (stack_ptr > 0) {
// Leggi la cella attuale in cima allo stack
x = stack_x[stack_ptr - 1];
y = stack_y[stack_ptr - 1];
// Cerca direzioni inesplorate a distanza 2 (oltre il potenziale muro)
count = 0;
if (x >= 2 && maze[y][x - 2] == 1) dirs[count++] = 0; // Controllo Sinistra
if (x <= MAZE_WIDTH - 3 && maze[y][x + 2] == 1) dirs[count++] = 1; // Controllo Destra
if (y >= 2 && maze[y - 2][x] == 1) dirs[count++] = 2; // Controllo Su
if (y <= MAZE_HEIGHT - 3 && maze[y + 2][x] == 1) dirs[count++] = 3; // Controllo Giù
if (count > 0) {
// È stata trovata almeno una direzione valida:
// si sceglie casualmente una tra quelle esplorabili
r = rand() % count;
nx = x; ny = y;
// Calcola la cella adiacente finale e demolisci il muro intermedio
if (dirs[r] == 0) { nx -= 2; maze[y][x - 1] = 0; }
else if (dirs[r] == 1) { nx += 2; maze[y][x + 1] = 0; }
else if (dirs[r] == 2) { ny -= 2; maze[y - 1][x] = 0; }
else if (dirs[r] == 3) { ny += 2; maze[y + 1][x] = 0; }
// Imposta la nuova cella come esplorata (calpestabile)
maze[ny][nx] = 0;
// Pusha la nuova coordinata nello stack per espandere il ramo
push(nx, ny);
} else {
// Vicolo cieco: estrae la coordinata corrente per risalire
// al nodo precedente (backtracking)
pop(&x, &y);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
/**
* @file maze.h
* @brief Strutture e funzioni per la generazione procedurale del labirinto.
*/
#ifndef MAZE_H
#define MAZE_H
#include <stdint.h>
// Dimensioni logiche del labirinto in numero di tiles (1 tile = 8x8 px).
// Manteniamo dimensioni dispari (19x17) in modo da poter creare un
// labirinto simmetrico circondato da muri uniformi una volta centrato.
#define MAZE_WIDTH 19
#define MAZE_HEIGHT 17
/**
* @brief Matrice globale che rappresenta la mappa del labirinto in RAM.
*
* Il valore 1 rappresenta un Muro (Tile Nero).
* Il valore 0 rappresenta un Percorso (Tile Bianco).
*/
extern uint8_t maze[MAZE_HEIGHT][MAZE_WIDTH];
/**
* @brief Esegue l'algoritmo di generazione del labirinto.
*
* Utilizza la logica di tipo Recursive Backtracker (implementata iterativamente
* con array custom per non sforare lo stack di sistema limitato).
*/
void generate_maze(void);
#endif
+279
View File
@@ -0,0 +1,279 @@
#include "music.h"
#include <gb/gb.h>
#include <stdint.h>
#define N_C4 0x60B
#define N_CS4 0x624
#define N_D4 0x641
#define N_DS4 0x65A
#define N_E4 0x672
#define N_F4 0x689
#define N_FS4 0x69E
#define N_G4 0x6B2
#define N_GS4 0x6C6
#define N_A4 0x6D6
#define N_AS4 0x6E8
#define N_B4 0x6F7
#define N_C5 0x706
#define N_CS5 0x712
#define N_D5 0x721
#define N_DS5 0x72D
#define N_E5 0x739
#define N_F5 0x744
#define N_FS5 0x74F
#define N_G5 0x759
#define N_GS5 0x764
#define N_A5 0x76B
#define N_AS5 0x774
#define N_B5 0x77B
#define N_C6 0x783
#define N_CS6 0x789
#define N_D6 0x790
#define N_DS6 0x796
#define N_E6 0x79C
#define N_REST 0x000
// ==========================================
// TRACK 1: ARPEGGIO (CH1)
// ==========================================
#define A_C N_C4
#define A_E N_E4
#define A_F N_F4
#define A_G N_G4
#define A_GS N_GS4
#define A_A N_A4
#define A_B N_B4
#define A_D N_D4
#define A_C5 N_C5
#define A_E5 N_E5
const uint16_t trk_arp_0[64] = {
A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E,
A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A,
A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B,
A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_C5, A_E, A_G, A_C5, A_E
};
const uint16_t trk_arp_1[64] = {
A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E,
A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A,
A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B,
A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E
};
const uint16_t trk_arp_2[64] = {
A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A,
A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B,
A_E, A_GS, A_B, A_GS, A_E, A_GS, A_B, A_GS, A_E, A_GS, A_B, A_GS, A_E, A_GS, A_B, A_GS,
A_A, A_C5, A_E5, A_C5, A_A, A_C5, A_E5, A_C5, A_A, A_C5, A_E5, A_C5, A_A, A_C5, A_E5, A_C5
};
const uint16_t trk_arp_3[64] = {
A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A, A_F, A_A, A_C5, A_A,
A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B, A_G, A_B, A_D, A_B,
A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E,
A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_E, A_C, A_E, A_G, A_C5, A_E, A_G, A_C5, A_E
};
const uint16_t* const arp_parts[4] = { trk_arp_0, trk_arp_1, trk_arp_2, trk_arp_3 };
// ==========================================
// TRACK 2: MELODY (CH2)
// ==========================================
#define M_C N_C5
#define M_D N_D5
#define M_E N_E5
#define M_F N_F5
#define M_G N_G5
#define M_GS N_GS5
#define M_A N_A5
#define M_B N_B5
#define M_C6 N_C6
#define M_D6 N_D6
#define M_E6 N_E6
#define M__ N_REST
const uint16_t trk_mel_0[64] = {
M_E, M__, M_D, M_C, M__, M_G, M__, M__, M_E, M__, M_D, M_C, M__, M_G, M__, M__,
M_F, M__, M_E, M_D, M__, M_A, M__, M__, M_F, M__, M_E, M_D, M__, M_A, M__, M__,
M_G, M__, M_F, M_E, M__, M_B, M__, M__, M_G, M__, M_F, M_E, M__, M_B, M__, M__,
M_C6, M__, M_B, M_A, M_G, M_F, M_E, M_D, M_C, M__, M__, M__, M__, M__, M__, M__
};
const uint16_t trk_mel_1[64] = {
M_E, M__, M_E, M__, M_D, M_C, M_G, M__, M_E, M__, M_D, M_C, M__, M_G, M__, M__,
M_F, M__, M_F, M__, M_E, M_D, M_A, M__, M_F, M__, M_E, M_D, M__, M_A, M__, M__,
M_G, M__, M_G, M__, M_F, M_E, M_B, M__, M_G, M__, M_F, M_E, M__, M_B, M__, M__,
M_C6, M_G, M_A, M_B, M_C6, M__, M__, M__, M_C, M__, M__, M__, M__, M__, M__, M__
};
const uint16_t trk_mel_2[64] = {
M_A, M__, M_C6, M_A, M_F, M__, M__, M__, M_A, M__, M_C6, M_A, M_F, M__, M__, M__,
M_B, M__, M_D6, M_B, M_G, M__, M__, M__, M_B, M__, M_D6, M_B, M_G, M__, M__, M__,
M_GS, M__, M_B, M_GS, M_E, M__, M__, M__, M_GS, M__, M_B, M_GS, M_E, M__, M__, M__,
M_A, M__, M_C6, M_A, M_E, M__, M__, M__, M_A, M__, M_A, M__, M_A, M__, M_A, M__
};
const uint16_t trk_mel_3[64] = {
M_A, M__, M_G, M_F, M__, M_C6, M__, M__, M_A, M__, M_G, M_F, M__, M_C6, M__, M__,
M_B, M__, M_A, M_G, M__, M_D6, M__, M__, M_B, M__, M_A, M_G, M__, M_D6, M__, M__,
M_E6, M__, M_D6, M_C6, M_B, M_A, M_G, M_F, M_E, M_D, M_C, M__, M__, M__, M__, M__,
M_C, M__, M_D, M__, M_E, M__, M_G, M__, M_C6, M__, M__, M__, M__, M__, M__, M__
};
const uint16_t* const mel_parts[4] = { trk_mel_0, trk_mel_1, trk_mel_2, trk_mel_3 };
// ==========================================
// TRACK 3: BASS (CH3 - Wave)
// ==========================================
#define B_C N_C4
#define B_D N_D4
#define B_E N_E4
#define B_F N_F4
#define B_G N_G4
#define B_GS N_GS4
#define B_A N_A4
#define B_B N_B4
#define B_C5 N_C5
#define B_E5 N_E5
#define B__ N_REST
const uint16_t trk_bass_0[64] = {
B_C, B__, B_C, B__, B_E, B__, B_G, B__, B_C, B__, B_C, B__, B_G, B__, B_E, B__,
B_F, B__, B_F, B__, B_A, B__, B_C5, B__, B_F, B__, B_F, B__, B_C5, B__, B_A, B__,
B_G, B__, B_G, B__, B_B, B__, B_D, B__, B_G, B__, B_G, B__, B_D, B__, B_B, B__,
B_C, B__, B_C, B__, B_E, B__, B_G, B__, B_C, B__, B_E, B__, B_G, B__, B_C5, B__
};
const uint16_t trk_bass_1[64] = {
B_C, B__, B_C, B__, B_E, B__, B_G, B__, B_C, B__, B_C, B__, B_G, B__, B_E, B__,
B_F, B__, B_F, B__, B_A, B__, B_C5, B__, B_F, B__, B_F, B__, B_C5, B__, B_A, B__,
B_G, B__, B_G, B__, B_B, B__, B_D, B__, B_G, B__, B_G, B__, B_D, B__, B_B, B__,
B_C, B__, B_C, B__, B_E, B__, B_G, B__, B_C, B__, B__, B__, B__, B__, B__, B__
};
const uint16_t trk_bass_2[64] = {
B_F, B__, B_A, B__, B_C5, B__, B_A, B__, B_F, B__, B_A, B__, B_C5, B__, B_A, B__,
B_G, B__, B_B, B__, B_D, B__, B_B, B__, B_G, B__, B_B, B__, B_D, B__, B_B, B__,
B_E, B__, B_GS, B__, B_B, B__, B_GS, B__, B_E, B__, B_GS, B__, B_B, B__, B_GS, B__,
B_A, B__, B_C5, B__, B_E5, B__, B_C5, B__, B_A, B__, B__, B__, B_G, B__, B__, B__
};
const uint16_t trk_bass_3[64] = {
B_F, B__, B_A, B__, B_C5, B__, B_A, B__, B_F, B__, B_A, B__, B_C5, B__, B_A, B__,
B_G, B__, B_B, B__, B_D, B__, B_B, B__, B_G, B__, B_B, B__, B_D, B__, B_B, B__,
B_C, B__, B_E, B__, B_G, B__, B_E, B__, B_C, B__, B_E, B__, B_G, B__, B_E, B__,
B_C, B__, B_G, B__, B_C5, B__, B_G, B__, B_C, B__, B__, B__, B__, B__, B__, B__
};
const uint16_t* const bass_parts[4] = { trk_bass_0, trk_bass_1, trk_bass_2, trk_bass_3 };
const uint8_t wave_ram[16] = {
0x02, 0x46, 0x8A, 0xCE, 0xFF, 0xFE, 0xEC, 0xA8,
0x64, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// ==========================================
// TRACK 4: DRUMS (CH4 - Noise)
// ==========================================
const uint8_t trk_drum_0[64] = {
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 3, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 1, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 3, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 1, 3, 3, 2, 2, 1, 1
};
const uint8_t trk_drum_1[64] = {
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 3, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 1, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 3, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 2, 1, 2, 1, 2, 1, 2
};
const uint8_t trk_drum_2[64] = {
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 3, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 1, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 1, 3, 3, 3, 2, 3, 3, 3,
1, 3, 3, 3, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2
};
const uint8_t trk_drum_3[64] = {
1, 3, 1, 3, 2, 3, 1, 3, 1, 3, 1, 3, 2, 3, 1, 3,
1, 3, 1, 3, 2, 3, 1, 3, 1, 3, 1, 3, 2, 3, 1, 3,
1, 3, 1, 3, 2, 3, 1, 3, 1, 3, 1, 3, 2, 3, 1, 3,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0
};
const uint8_t* const drum_parts[4] = { trk_drum_0, trk_drum_1, trk_drum_2, trk_drum_3 };
static uint16_t tick = 0;
static uint8_t frame_counter = 0;
#define FRAMES_PER_TICK 8 // Velocità del tracker
void init_music(void) {
// Reset e attivazione master sound
NR52_REG = 0x00;
NR52_REG = 0x80;
NR50_REG = 0x77;
NR51_REG = 0xFF;
// Inizializza CH3 Wave RAM (0xFF30 - 0xFF3F)
NR30_REG = 0x00; // Disable DAC
volatile uint8_t *wave_ptr = (volatile uint8_t *)0xFF30;
for (uint8_t i = 0; i < 16; i++) {
wave_ptr[i] = wave_ram[i];
}
NR30_REG = 0x80; // Enable DAC
tick = 0;
frame_counter = 0;
}
void update_music(void) {
if (frame_counter == 0) {
uint8_t part = (tick >> 6) & 0x03; // tick / 64
uint8_t sub = tick & 0x3F; // tick % 64
// --- CH1: Arpeggio ---
uint16_t f1 = arp_parts[part][sub];
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = 0x51; // Volume basso, decadimento rapido
NR13_REG = (uint8_t)(f1 & 0xFF);
NR14_REG = 0x80 | ((f1 >> 8) & 0x07);
// --- CH2: Melody ---
uint16_t f2 = mel_parts[part][sub];
if (f2 == N_REST) {
NR21_REG = 0x00; NR22_REG = 0x00; NR23_REG = 0x00; NR24_REG = 0x80;
} else {
NR21_REG = 0x80;
NR22_REG = 0xF2; // Volume alto
NR23_REG = (uint8_t)(f2 & 0xFF);
NR24_REG = 0x80 | ((f2 >> 8) & 0x07);
}
// --- CH3: Bass ---
uint16_t f3 = bass_parts[part][sub];
if (f3 == N_REST) {
NR32_REG = 0x00; // Vol 0
NR34_REG = 0x80;
} else {
NR32_REG = 0x20; // Vol 100%
NR33_REG = (uint8_t)(f3 & 0xFF);
NR34_REG = 0x80 | ((f3 >> 8) & 0x07);
}
// --- CH4: Drums ---
uint8_t d4 = drum_parts[part][sub];
if (d4 == 1) { // Kick
NR41_REG = 0x00;
NR42_REG = 0xF1;
NR43_REG = 0x11;
NR44_REG = 0x80;
} else if (d4 == 2) { // Snare
NR41_REG = 0x00;
NR42_REG = 0xF2;
NR43_REG = 0x51;
NR44_REG = 0x80;
} else if (d4 == 3) { // Hi-Hat
NR41_REG = 0x00;
NR42_REG = 0x51;
NR43_REG = 0x21;
NR44_REG = 0x80;
}
// Avanza il tracker
tick++;
if (tick >= 256) tick = 0; // Loop completo
frame_counter = FRAMES_PER_TICK;
} else {
frame_counter--;
}
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef MUSIC_H
#define MUSIC_H
void init_music(void);
void update_music(void);
#endif
+140
View File
@@ -0,0 +1,140 @@
/**
* @file rat.c
* @brief Implementazione del topo autonomo usando Meta-Sprite (16x8 / 8x16).
*/
#include "rat.h"
#include "maze.h"
#include <gb/gb.h>
#include <rand.h>
// Dati dei 4 tile per creare i meta-sprite del topo. Usa solo la forma "Giù" ruotata per tutte le direzioni!
// Colori: 0=Trasparente (Bianco), 1=Grigio Chiaro (Corpo), 2=Grigio Scuro (Macchie/Naso), 3=Nero (Contorni/Pupille)
const unsigned char RatSpriteData[] = {
// 0: Destra - Metà Sinistra (Coda/Posteriore) ruotata a partire da Giù
0x00, 0x00, 0x07, 0x07, 0x3F, 0x38, 0x5E, 0x71, 0x5B, 0x74, 0x3F, 0x38, 0x07, 0x07, 0x00, 0x00,
// 1: Destra - Metà Destra (Testa/Occhi/Naso) ruotata a partire da Giù
0x00, 0x00, 0xF0, 0xF0, 0xC8, 0x08, 0xD4, 0x1C, 0xD4, 0x1C, 0xC8, 0x08, 0xF0, 0xF0, 0x00, 0x00,
// 2: Giù - Metà Superiore (Coda/Schiena)
0x00, 0x00, 0x18, 0x18, 0x24, 0x3C, 0x3C, 0x3C, 0x3C, 0x24, 0x6E, 0x52, 0x7E, 0x42, 0x76, 0x4A,
// 3: Giù - Metà Inferiore (Occhi/Naso)
0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00
};
static uint8_t rat_x = 1;
static uint8_t rat_y = 1;
static uint8_t target_x = 1;
static uint8_t target_y = 1;
static uint8_t pixel_x = 0;
static uint8_t pixel_y = 0;
static uint8_t current_dir = 255;
static uint8_t get_opposite(uint8_t dir) {
if (dir == 0) return 1;
if (dir == 1) return 0;
if (dir == 2) return 3;
if (dir == 3) return 2;
return 255;
}
void init_rat(uint8_t start_x, uint8_t start_y) {
rat_x = target_x = start_x;
rat_y = target_y = start_y;
pixel_x = rat_x * 8;
pixel_y = rat_y * 8;
current_dir = 255;
// Carica tutti i 4 tile per le combinazioni 16x8 e 8x16
set_sprite_data(0, 4, RatSpriteData);
OBP0_REG = 0xE4; // Palette standard
// Imposta tile di default (Giù)
set_sprite_tile(0, 2);
set_sprite_tile(1, 3);
}
void update_rat(void) {
if (rat_x == target_x && rat_y == target_y) {
uint8_t valid_dirs[4];
uint8_t num_valid = 0;
if (rat_y < MAZE_HEIGHT - 1 && maze[rat_y + 1][rat_x] == 0) valid_dirs[num_valid++] = 0;
if (rat_y > 0 && maze[rat_y - 1][rat_x] == 0) valid_dirs[num_valid++] = 1;
if (rat_x > 0 && maze[rat_y][rat_x - 1] == 0) valid_dirs[num_valid++] = 2;
if (rat_x < MAZE_WIDTH - 1 && maze[rat_y][rat_x + 1] == 0) valid_dirs[num_valid++] = 3;
if (num_valid > 0) {
if (num_valid > 1 && current_dir != 255) {
uint8_t opp = get_opposite(current_dir);
uint8_t filtered[4];
uint8_t num_filtered = 0;
for (uint8_t i = 0; i < num_valid; i++) {
if (valid_dirs[i] != opp) {
filtered[num_filtered++] = valid_dirs[i];
}
}
if (num_filtered > 0) current_dir = filtered[rand() % num_filtered];
else current_dir = valid_dirs[rand() % num_valid];
} else {
current_dir = valid_dirs[rand() % num_valid];
}
if (current_dir == 0) target_y++;
else if (current_dir == 1) target_y--;
else if (current_dir == 2) target_x--;
else if (current_dir == 3) target_x++;
}
}
uint8_t target_px = target_x * 8;
uint8_t target_py = target_y * 8;
static uint8_t frame_counter = 0;
frame_counter++;
if (frame_counter & 1) {
if (pixel_x < target_px) pixel_x++;
else if (pixel_x > target_px) pixel_x--;
if (pixel_y < target_py) pixel_y++;
else if (pixel_y > target_py) pixel_y--;
}
if (pixel_x == target_px && pixel_y == target_py) {
rat_x = target_x;
rat_y = target_y;
}
// Aggiorna gli hardware sprite (Meta-Sprite system)
uint8_t base_x = pixel_x + 12;
uint8_t base_y = pixel_y + 20;
if (current_dir == 0 || current_dir == 255) { // Giù (Verticale 8x16)
set_sprite_tile(0, 2);
set_sprite_tile(1, 3);
set_sprite_prop(0, 0);
set_sprite_prop(1, 0);
move_sprite(0, base_x, base_y - 4);
move_sprite(1, base_x, base_y + 4);
} else if (current_dir == 1) { // Su (Verticale 8x16, FLIPPATO IN Y)
set_sprite_tile(0, 3); // Naso diventa parte alta
set_sprite_tile(1, 2); // Coda diventa parte bassa
set_sprite_prop(0, S_FLIPY);
set_sprite_prop(1, S_FLIPY);
move_sprite(0, base_x, base_y - 4);
move_sprite(1, base_x, base_y + 4);
} else if (current_dir == 2) { // Sinistra (Flippato orizzontalmente per puntare a sinistra)
set_sprite_tile(0, 1); // Naso flippato (va a sinistra)
set_sprite_tile(1, 0); // Coda flippata (va a destra)
set_sprite_prop(0, S_FLIPX);
set_sprite_prop(1, S_FLIPX);
move_sprite(0, base_x - 4, base_y);
move_sprite(1, base_x + 4, base_y);
} else if (current_dir == 3) { // Destra (Punta a destra nativamente)
set_sprite_tile(0, 0); // Coda (metà sinistra)
set_sprite_tile(1, 1); // Naso (metà destra)
set_sprite_prop(0, 0);
set_sprite_prop(1, 0);
move_sprite(0, base_x - 4, base_y);
move_sprite(1, base_x + 4, base_y);
}
}
+25
View File
@@ -0,0 +1,25 @@
/**
* @file rat.h
* @brief Header per la gestione del personaggio "ratto" autonomo.
*/
#ifndef RAT_H
#define RAT_H
#include <stdint.h>
/**
* @brief Inizializza il ratto alle coordinate logiche specificate.
* @param start_x Coordinata X (in tiles) nel labirinto.
* @param start_y Coordinata Y (in tiles) nel labirinto.
*/
void init_rat(uint8_t start_x, uint8_t start_y);
/**
* @brief Aggiorna la posizione e la logica del ratto.
*
* Da chiamare ogni frame nel game loop principale.
*/
void update_rat(void);
#endif
+20
View File
@@ -0,0 +1,20 @@
/**
* @file tiles.c
* @brief Implementazione dei dati grafici dei tile.
*/
#include "tiles.h"
// Definizione dei dati dei tile. Ogni tile su Game Boy è 8x8 pixel.
// Ogni riga del tile è rappresentata da 2 byte (planar format).
const unsigned char TileData[] = {
// Tile 0: Completamente bianco (tutti i bit a 0)
// Indica la stanza scavata e i percorsi.
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
// Tile 1: Completamente nero (tutti i bit a 1)
// Usato per disegnare i muri del labirinto.
0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF
};
+21
View File
@@ -0,0 +1,21 @@
/**
* @file tiles.h
* @brief Definizioni per i dati grafici dei tile.
*
* Questo file dichiara le risorse grafiche utilizzate dal gioco,
* in particolare i tile per disegnare il labirinto e i muri.
*/
#ifndef TILES_H
#define TILES_H
/**
* @brief Array contenente i dati grezzi dei tile.
*
* Formato planare Game Boy (2 bit per pixel, 16 byte per tile).
* - Tile 0: Bianco (usato per i percorsi)
* - Tile 1: Nero (usato per i muri)
*/
extern const unsigned char TileData[];
#endif