Browse Source

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

master
Matteo Benedetto 3 days ago
parent
commit
4f9e237698
  1. 25
      .gitignore
  2. 14
      AGENTS.md
  3. 26
      Makefile
  4. 92
      README.md
  5. BIN
      hello.gb
  6. 9
      main.c
  7. BIN
      screenshot.png
  8. 57
      src/cursor.c
  9. 9
      src/cursor.h
  10. 84
      src/main.c
  11. 95
      src/maze.c
  12. 33
      src/maze.h
  13. 279
      src/music.c
  14. 7
      src/music.h
  15. 140
      src/rat.c
  16. 25
      src/rat.h
  17. 20
      src/tiles.c
  18. 21
      src/tiles.h
  19. 59
      test_audio.c
  20. 72
      tests/render_sprites.py
  21. 74
      tests/scratch_rotate.py
  22. 28
      tests/test_pyboy.py

25
.gitignore vendored

@ -0,0 +1,25 @@
# GBDK build artifacts
*.o
*.lst
*.map
*.sym
*.cdb
*.ihx
*.noi
*.asm
# Compiled ROM (usually ignored in version control)
*.gb
# Emulator save states and battery RAM
*.sav
*.sn1
*.rtc
# Python cache (if using PyBoy scripts)
__pycache__/
*.pyc
# OS generated files
.DS_Store
Thumbs.db

14
AGENTS.md

@ -123,3 +123,17 @@ Learned from `kaspermeerts/tetris` disassembly and other sources:
- Implement the original orientation-based rotation and collision cancel logic.
- Replicate the original `rDIV`-based randomizer with 1-history reroll.
- Add DAS, BCD scoring, and proper line-clear behavior.
## Voice notification rule
When the agent reaches an important deduction, conclusion, decision, milestone, or completes a significant action, it should not only write the result on screen.
Also make it known to the user when you do a modification requested by the user and when the practical result of its modification is evident.
It should also play a short spoken notification aloud on the local machine.
Guidelines:
- Use spoken audio for important conclusions, key findings, successful completions, warnings, and major state changes.
- Keep spoken notifications short and clear.
- Prefer the local speech tools available in the environment.
- If the message is in Italian or the user is speaking Italian, prefer speaking in Italian.
- **After Build and Test Analysis**: Every time you attempt a build and test run, upon analyzing the log, you MUST speak aloud to the user summarizing what is happening. Tell them if it worked or not, what your doubts are, and what the suspected cause might be.
- **Implementation mechanism**: Use the `test_tts.py` script located in the project root (`/home/enne2/dev/aiDecompiling/relola/test_tts.py`) via direct execution (`python3 test_tts.py "Your message here"`). Do not use this as an MCP server; execute it directly as a background shell command.

26
Makefile

@ -1,10 +1,24 @@
CC = /home/enne2/.local/gbdk/bin/lcc
CFLAGS = -Wl-yt1 -Wl-ya4
CFLAGS = -Wl-yt1 -Wl-ya4 -Isrc
hello.gb: main.c
$(CC) $(CFLAGS) -o $@ $^
SRC_DIR = src
OBJ_DIR = obj
clean:
rm -f *.gb *.ihx *.cdb *.adb *.noi *.map *.lst *.sym *.rel
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))
TARGET = maze.gb
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
.PHONY: clean
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
clean:
rm -rf $(OBJ_DIR) *.gb

92
README.md

@ -1,63 +1,41 @@
# Game Boy Hello World
Questa sottocartella contiene un minimo esempio di "Hello World" per Game Boy originale, compilato con **GBDK-2020** in C.
## Struttura
- `main.c` — sorgente C.
- `Makefile` — regola per compilare con `lcc` (GBDK-2020).
- `hello.gb` — ROM Game Boy risultante (32 KB).
## Requisiti
GBDK-2020 e installato in `/home/enne2/.local/gbdk`. Se vuoi spostarlo altrove, aggiorna il `CC` nel `Makefile`.
## Build
# Game Boy Maze Generator & Rat Hunt
Un progetto per Game Boy, scritto in linguaggio C usando **GBDK-2020**. Nata come semplice generazione procedurale di un labirinto perfetto (Recursive Backtracker), l'applicazione si è evoluta integrando intelligenza artificiale, interattività e un motore audio custom.
![Screenshot del Gioco](screenshot.png)
## Funzionalità Aggiunte
- **Intelligenza Artificiale (Rat)**: Un topolino animato (costruito tramite *Meta-Sprite* 16x8 per ottimizzare la memoria) esplora autonomamente e fluidamente i percorsi del labirinto.
- **Cursore Interattivo (Giocatore)**: Un cursore lampeggiante controllabile dal giocatore tramite il D-Pad. Pone le basi per le meccaniche future di caccia al topo (piazzamento bombe).
- **Tracker Musicale a 4 Canali**: Una sontuosa colonna sonora chiptune generata da un sequencer nativo scritto in C, senza l'uso di engine esterni. Sfrutta l'onda quadra, la Wave RAM customizzata per il basso, e il Noise per le percussioni, ruotando su una traccia in quattro parti da ~35 secondi.
## Struttura del Progetto
Al fine di mantenere un codice ordinato, pulito ed espandibile, i sorgenti sono stati suddivisi:
- `src/` : Contiene tutto il codice sorgente del gioco.
- `main.c`: Entry point, loop principale, inizializzazione hardware e rendering (scrolling).
- `maze.c` / `maze.h`: Core algoritmico per la generazione del labirinto.
- `rat.c` / `rat.h`: Logica dell'automa e gestione degli sprite hardware per l'animazione del topo.
- `cursor.c` / `cursor.h`: Logica interattiva del selettore del giocatore.
- `music.c` / `music.h`: Mini-tracker musicale, sequencer e definizione dei registri audio.
- `tiles.c` / `tiles.h`: Dichiarazione e definizione della grafica dei tile utilizzati.
- `obj/` : Cartella autogenerata durante il processo di compilazione per immagazzinare i file intermedi (`.o` e metadati).
- `tests/` : Script e utilities di testing.
- `test_pyboy.py`: Esegue l'emulatore PyBoy in modalità headless, salvando la prova del funzionamento in un'immagine temporanea.
- `Makefile` : Sistema di build preconfigurato per GBDK-2020.
- `maze.gb` : La ROM finale giocabile (generata dopo il build).
- `test_audio.gb` / `test_audio.c`: Micro-rom dedicata alla diagnostica dell'hardware sonoro.
## Come Compilare
Assicurati di aver installato la toolchain **GBDK-2020** nel path corretto (solitamente configurato su `~/.local/gbdk` in base a questo repository).
Per compilare la ROM, basterà lanciare:
```bash
cd gameboy-hello
make
```
## Test con emulatore
### PyBoy (senza finestra, screenshot)
## Come Testare
Se vuoi validare la compilazione senza aprire GUI o se sei su un server remoto, puoi lanciare lo script headless:
```bash
python3 - <<'PY'
from pyboy import PyBoy
rom = 'hello.gb'
pyboy = PyBoy(rom, window='null')
for _ in range(60*5):
pyboy.tick()
pyboy.screen.image.save('/tmp/hello_gb.png')
pyboy.stop()
print('Screenshot salvato in /tmp/hello_gb.png')
PY
python3 tests/test_pyboy.py
```
### PyBoy (con finestra SDL2)
```bash
python3 - <<'PY'
from pyboy import PyBoy
pyboy = PyBoy('hello.gb', window='SDL2')
for _ in range(60*30):
pyboy.tick()
pyboy.stop()
PY
```
## Note
- La ROM usa il logo Nintendo corretto e il checksum di header e valido.
- Il testo viene visualizzato con `printf` sulla console testuale del Game Boy.
- Il loop principale chiama `wait_vbl_done()` per sincronizzarsi con il VBlank.
## Prossimi passi
Da qui si puo partire per un vero gioco Game Boy (ad esempio Tetris), aggiungendo:
- gestione di input tramite `joypad()`;
- sprite OAM per i pezzi;
- tilemap per il campo di gioco;
- musica/effetti sonori con i 4 canali audio del Game Boy.
Questo genererà un file PNG in locale (`/tmp/maze_gb.png`) per farti visualizzare l'output atteso della ROM. Puoi anche testare le ROM su emulatori diretti da terminale come `pyboy maze.gb`.

BIN
hello.gb

Binary file not shown.

9
main.c

@ -1,9 +0,0 @@
#include <gb/gb.h>
#include <stdio.h>
void main(void) {
printf("Hello Game Boy!");
while (1) {
wait_vbl_done();
}
}

BIN
screenshot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

57
src/cursor.c

@ -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
src/cursor.h

@ -0,0 +1,9 @@
#ifndef CURSOR_H
#define CURSOR_H
#include <stdint.h>
void init_cursor(void);
void update_cursor(void);
#endif

84
src/main.c

@ -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
src/maze.c

@ -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
src/maze.h

@ -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
src/music.c

@ -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
src/music.h

@ -0,0 +1,7 @@
#ifndef MUSIC_H
#define MUSIC_H
void init_music(void);
void update_music(void);
#endif

140
src/rat.c

@ -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
src/rat.h

@ -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
src/tiles.c

@ -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
src/tiles.h

@ -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

59
test_audio.c

@ -0,0 +1,59 @@
#include <gb/gb.h>
#include <stdio.h>
#define N_C5 0x706
#define N_E5 0x739
#define N_G5 0x759
#define N_REST 0x000
const uint16_t arp[16] = {
N_C5, N_REST, N_E5, N_REST, N_G5, N_REST, N_E5, N_REST,
N_C5, N_REST, N_E5, N_REST, N_G5, N_REST, N_E5, N_REST
};
static uint8_t tick = 0;
static uint8_t frame_counter = 0;
void init_music() {
NR52_REG = 0x00;
NR52_REG = 0x80;
NR50_REG = 0x77;
NR51_REG = 0xFF;
}
void update_music() {
if (frame_counter == 0) {
uint16_t f1 = arp[tick];
if (f1 == N_REST) {
NR11_REG = 0x00;
NR12_REG = 0x00;
NR13_REG = 0x00;
NR14_REG = 0x80;
} else {
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = 0xF2;
NR13_REG = (uint8_t)(f1 & 0xFF);
NR14_REG = 0x80 | ((f1 >> 8) & 0x07);
}
tick++;
if (tick >= 16) tick = 0;
frame_counter = 15; // Lento
} else {
frame_counter--;
}
}
void main() {
printf(" AUDIO TEST 2\n\n");
printf(" Tracker Test in Esecuzione...\n");
init_music();
while(1) {
update_music();
wait_vbl_done();
}
}

72
tests/render_sprites.py

@ -0,0 +1,72 @@
import os
def decode_gb_tile(data):
pixels = [[0]*8 for _ in range(8)]
for y in range(8):
lsb = data[y*2]
msb = data[y*2+1]
for x in range(8):
bit_lsb = (lsb >> (7 - x)) & 1
bit_msb = (msb >> (7 - x)) & 1
pixels[y][x] = (bit_msb << 1) | bit_lsb
return pixels
RatSpriteData = [
0x00, 0x00, 0x07, 0x07, 0x3F, 0x38, 0x5E, 0x71, 0x5B, 0x74, 0x3F, 0x38, 0x07, 0x07, 0x00, 0x00,
0x00, 0x00, 0xF0, 0xF0, 0xC8, 0x08, 0xD4, 0x1C, 0xD4, 0x1C, 0xC8, 0x08, 0xF0, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x18, 0x18, 0x24, 0x3C, 0x3C, 0x3C, 0x3C, 0x24, 0x6E, 0x52, 0x7E, 0x42, 0x76, 0x4A,
0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00
]
tiles = [decode_gb_tile(RatSpriteData[i*16:(i+1)*16]) for i in range(4)]
colors = {0: ' ', 1: '░░', 2: '▓▓', 3: '██'}
rgb_colors = {0: (255,255,255), 1: (170,170,170), 2: (85,85,85), 3: (0,0,0)}
def print_and_save(name, pixels_2d, width, height):
print(f"--- {name} ---")
for y in range(height):
row_str = ""
for x in range(width):
row_str += colors[pixels_2d[y][x]]
print(row_str)
scale = 20
with open(f"/tmp/{name}.ppm", "w") as f:
f.write(f"P3\n{width*scale} {height*scale}\n255\n")
for y in range(height):
for sy in range(scale):
for x in range(width):
c = pixels_2d[y][x]
rgb = rgb_colors[c]
for sx in range(scale):
f.write(f"{rgb[0]} {rgb[1]} {rgb[2]} ")
f.write("\n")
right_pixels = [[0]*16 for _ in range(8)]
for y in range(8):
for x in range(8):
right_pixels[y][x] = tiles[0][y][x]
right_pixels[y][x+8] = tiles[1][y][x]
left_pixels = [[0]*16 for _ in range(8)]
for y in range(8):
for x in range(16):
left_pixels[y][x] = right_pixels[y][15-x]
down_pixels = [[0]*8 for _ in range(16)]
for y in range(8):
for x in range(8):
down_pixels[y][x] = tiles[2][y][x]
down_pixels[y+8][x] = tiles[3][y][x]
up_pixels = [[0]*8 for _ in range(16)]
for y in range(16):
for x in range(8):
up_pixels[y][x] = down_pixels[15-y][x]
print_and_save("sprite_right", right_pixels, 16, 8)
print_and_save("sprite_left", left_pixels, 16, 8)
print_and_save("sprite_down", down_pixels, 8, 16)
print_and_save("sprite_up", up_pixels, 8, 16)

74
tests/scratch_rotate.py

@ -0,0 +1,74 @@
def encode_gb_tile(pixels):
data = []
for y in range(8):
lsb = 0
msb = 0
for x in range(8):
c = pixels[y][x]
if c & 1: lsb |= (1 << (7 - x))
if c & 2: msb |= (1 << (7 - x))
data.extend([lsb, msb])
return data
def print_hex(data):
print(", ".join(f"0x{b:02X}" for b in data))
# Down Top Half
down_top = [
[0,0,0,0,0,0,0,0],
[0,0,0,3,3,0,0,0],
[0,0,3,2,2,3,0,0],
[0,0,3,3,3,3,0,0],
[0,0,3,1,1,3,0,0],
[0,3,1,2,1,1,3,0],
[0,3,1,1,1,1,3,0],
[0,3,1,1,2,1,3,0]
]
# Down Bottom Half
down_bot = [
[0,3,1,1,1,1,3,0],
[0,3,1,1,1,1,3,0],
[0,3,0,0,0,0,3,0],
[0,3,0,3,3,0,3,0],
[0,0,3,2,2,3,0,0],
[0,0,0,3,3,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]
]
# Combine Down
down_full = down_top + down_bot
# Rotate CCW for Right (Wait: earlier we established CCW rotation maps nose to Right side)
# If Nose is at y=12, CCW rotation: new_x = y, new_y = 7 - x?
# For 16x8 from 8x16:
# new_width = 16, new_height = 8
# old_x in 0..7, old_y in 0..15
# Rotation CCW:
# X_new = old_y
# Y_new = 7 - old_x
right_full = [[0]*16 for _ in range(8)]
for oy in range(16):
for ox in range(8):
nx = oy
ny = 7 - ox
right_full[ny][nx] = down_full[oy][ox]
# Split into two 8x8 tiles
right_left = [row[:8] for row in right_full]
right_right = [row[8:] for row in right_full]
data0 = encode_gb_tile(right_left)
data1 = encode_gb_tile(right_right)
data2 = encode_gb_tile(down_top)
data3 = encode_gb_tile(down_bot)
print("Tile 0 (Right Left):")
print_hex(data0)
print("Tile 1 (Right Right):")
print_hex(data1)
print("Tile 2 (Down Top):")
print_hex(data2)
print("Tile 3 (Down Bot):")
print_hex(data3)

28
tests/test_pyboy.py

@ -0,0 +1,28 @@
"""
Script per validare e testare la ROM su emulatore in modalità headless.
Questo file è utile per l'automazione dei test o come integrazione CI.
Utilizza PyBoy per eseguire 5 secondi di emulazione ed esportare uno screenshot.
"""
from pyboy import PyBoy
import sys
def main():
try:
# Avvia l'emulatore PyBoy disattivando la finestra nativa ("null")
pyboy = PyBoy('maze.gb', window='null')
# Effettua l'avanzamento esatto di 5 secondi (300 frame a 60 fps logici)
for _ in range(60 * 5):
pyboy.tick()
# Salva un fotogramma esatto dell'esito della generazione su file
pyboy.screen.image.save('/tmp/maze_gb.png')
pyboy.stop()
print("Test passed: Screenshot saved to /tmp/maze_gb.png")
except Exception as e:
print(f"Test failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Loading…
Cancel
Save