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
|
CC = /home/enne2/.local/gbdk/bin/lcc
|
||||||
CFLAGS = -Wl-yt1 -Wl-ya4
|
CFLAGS = -Wl-yt1 -Wl-ya4 -Isrc
|
||||||
|
|
||||||
hello.gb: main.c |
SRC_DIR = src
|
||||||
$(CC) $(CFLAGS) -o $@ $^
|
OBJ_DIR = obj
|
||||||
|
|
||||||
clean: |
SRCS = $(wildcard $(SRC_DIR)/*.c)
|
||||||
rm -f *.gb *.ihx *.cdb *.adb *.noi *.map *.lst *.sym *.rel
|
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 |
# Game Boy Maze Generator & Rat Hunt |
||||||
|
|
||||||
Questa sottocartella contiene un minimo esempio di "Hello World" per Game Boy originale, compilato con **GBDK-2020** in C. |
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. |
||||||
|
|
||||||
## Struttura |
 |
||||||
|
|
||||||
- `main.c` — sorgente C. |
## Funzionalità Aggiunte |
||||||
- `Makefile` — regola per compilare con `lcc` (GBDK-2020). |
- **Intelligenza Artificiale (Rat)**: Un topolino animato (costruito tramite *Meta-Sprite* 16x8 per ottimizzare la memoria) esplora autonomamente e fluidamente i percorsi del labirinto. |
||||||
- `hello.gb` — ROM Game Boy risultante (32 KB). |
- **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. |
||||||
## Requisiti |
|
||||||
|
## Struttura del Progetto |
||||||
GBDK-2020 e installato in `/home/enne2/.local/gbdk`. Se vuoi spostarlo altrove, aggiorna il `CC` nel `Makefile`. |
Al fine di mantenere un codice ordinato, pulito ed espandibile, i sorgenti sono stati suddivisi: |
||||||
|
|
||||||
## Build |
- `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 |
```bash |
||||||
cd gameboy-hello |
|
||||||
make |
make |
||||||
``` |
``` |
||||||
|
|
||||||
## Test con emulatore |
## Come Testare |
||||||
|
Se vuoi validare la compilazione senza aprire GUI o se sei su un server remoto, puoi lanciare lo script headless: |
||||||
### PyBoy (senza finestra, screenshot) |
|
||||||
|
|
||||||
```bash |
```bash |
||||||
python3 - <<'PY' |
python3 tests/test_pyboy.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 |
|
||||||
``` |
``` |
||||||
|
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`. |
||||||
### 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. |
|
||||||
|
|||||||
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