22 changed files with 1097 additions and 72 deletions
@ -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 |
||||
@ -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
|
||||
|
||||
@ -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. |
||||
|
||||
 |
||||
|
||||
## 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`. |
||||
|
||||
Binary file not shown.
@ -1,9 +0,0 @@
|
||||
#include <gb/gb.h> |
||||
#include <stdio.h> |
||||
|
||||
void main(void) { |
||||
printf("Hello Game Boy!"); |
||||
while (1) { |
||||
wait_vbl_done(); |
||||
} |
||||
} |
||||
|
After Width: | Height: | Size: 772 B |
@ -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); |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@
|
||||
#ifndef CURSOR_H |
||||
#define CURSOR_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
void init_cursor(void); |
||||
void update_cursor(void); |
||||
|
||||
#endif |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
} |
||||
@ -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 |
||||
@ -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--; |
||||
} |
||||
} |
||||
@ -0,0 +1,7 @@
|
||||
#ifndef MUSIC_H |
||||
#define MUSIC_H |
||||
|
||||
void init_music(void); |
||||
void update_music(void); |
||||
|
||||
#endif |
||||
@ -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); |
||||
} |
||||
} |
||||
@ -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 |
||||
@ -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 |
||||
}; |
||||
@ -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 |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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) |
||||
@ -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) |
||||
@ -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…
Reference in new issue