diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf145e1 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 6892861..6cdffd8 100644 --- a/AGENTS.md +++ b/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. \ No newline at end of file diff --git a/Makefile b/Makefile index e9d24cc..deff905 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index dbc46a3..4488a49 100644 --- a/README.md +++ b/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`. diff --git a/hello.gb b/hello.gb deleted file mode 100644 index 5a0c9c3..0000000 Binary files a/hello.gb and /dev/null differ diff --git a/main.c b/main.c deleted file mode 100644 index ac34c50..0000000 --- a/main.c +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -void main(void) { - printf("Hello Game Boy!"); - while (1) { - wait_vbl_done(); - } -} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..25e7516 Binary files /dev/null and b/screenshot.png differ diff --git a/src/cursor.c b/src/cursor.c new file mode 100644 index 0000000..7a6eff8 --- /dev/null +++ b/src/cursor.c @@ -0,0 +1,57 @@ +#include "cursor.h" +#include "maze.h" +#include + +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); + } +} diff --git a/src/cursor.h b/src/cursor.h new file mode 100644 index 0000000..060bcd5 --- /dev/null +++ b/src/cursor.h @@ -0,0 +1,9 @@ +#ifndef CURSOR_H +#define CURSOR_H + +#include + +void init_cursor(void); +void update_cursor(void); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..6e38fe2 --- /dev/null +++ b/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 +#include +#include + +#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(); + } +} diff --git a/src/maze.c b/src/maze.c new file mode 100644 index 0000000..03e936f --- /dev/null +++ b/src/maze.c @@ -0,0 +1,95 @@ +/** + * @file maze.c + * @brief Implementazione dell'algoritmo di generazione del labirinto. + */ + +#include "maze.h" +#include + +// 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); + } + } +} diff --git a/src/maze.h b/src/maze.h new file mode 100644 index 0000000..bff0b66 --- /dev/null +++ b/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 + +// 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 diff --git a/src/music.c b/src/music.c new file mode 100644 index 0000000..b18b405 --- /dev/null +++ b/src/music.c @@ -0,0 +1,279 @@ +#include "music.h" +#include +#include + +#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--; + } +} diff --git a/src/music.h b/src/music.h new file mode 100644 index 0000000..3014162 --- /dev/null +++ b/src/music.h @@ -0,0 +1,7 @@ +#ifndef MUSIC_H +#define MUSIC_H + +void init_music(void); +void update_music(void); + +#endif diff --git a/src/rat.c b/src/rat.c new file mode 100644 index 0000000..4ae0bd5 --- /dev/null +++ b/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 +#include + +// 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); + } +} diff --git a/src/rat.h b/src/rat.h new file mode 100644 index 0000000..dab4243 --- /dev/null +++ b/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 + +/** + * @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 diff --git a/src/tiles.c b/src/tiles.c new file mode 100644 index 0000000..f7e99ab --- /dev/null +++ b/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 +}; diff --git a/src/tiles.h b/src/tiles.h new file mode 100644 index 0000000..6e59696 --- /dev/null +++ b/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 diff --git a/test_audio.c b/test_audio.c new file mode 100644 index 0000000..e0d9654 --- /dev/null +++ b/test_audio.c @@ -0,0 +1,59 @@ +#include +#include + +#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(); + } +} diff --git a/tests/render_sprites.py b/tests/render_sprites.py new file mode 100644 index 0000000..bc1ce87 --- /dev/null +++ b/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) diff --git a/tests/scratch_rotate.py b/tests/scratch_rotate.py new file mode 100644 index 0000000..97227b6 --- /dev/null +++ b/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) diff --git a/tests/test_pyboy.py b/tests/test_pyboy.py new file mode 100644 index 0000000..d9f1a22 --- /dev/null +++ b/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()