diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..916e36e --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,198 @@ +# Architettura dei Moduli + +Il codice di MICE! è organizzato in moduli C indipendenti, ognuno responsabile di una singola area funzionale. Questa separazione è fondamentale su un sistema embedded dove la RAM è un bene rarissimo. + +--- + +## Grafico delle Dipendenze + +```mermaid +graph TD + main["main.c\nEntry point + Game Loop"] + + maze["maze.c/h\nGenerazione labirinto"] + rat["rat.c/h\nAI topi + Spawn + Kill"] + cursor["cursor.c/h\nInput giocatore + DAS"] + bomb["bomb.c/h\nMeccanica bomba"] + music["music.c/h\nTracker audio + SFX"] + + tiles["tiles.c/h\nTileset grafico"] + title_bg["title_bg.c/h\nSfondo titolo"] + rat_bg["rat_bg.c/h\nSfondo game over"] + victory_bg["victory_bg.c/h\nSfondo vittoria"] + pause_gfx["pause_gfx.c/h\nSprite P-A-U-S-E"] + numbers_gfx["numbers_gfx.c/h\nSprite cifre 0-9"] + bomb_gfx["bomb_gfx.c\nSprite bomba animata"] + mockup_gfx["mockup_gfx.c/h\nTileset cespugli"] + + main --> maze + main --> rat + main --> cursor + main --> bomb + main --> music + main --> tiles + main --> title_bg + main --> rat_bg + main --> victory_bg + main --> pause_gfx + main --> numbers_gfx + + rat --> maze + rat --> music + + cursor --> maze + cursor --> bomb + cursor --> music + cursor --> rat + + bomb --> maze + bomb --> rat + bomb --> music + + bomb --> bomb_gfx + tiles --> mockup_gfx +``` + +--- + +## Descrizione dei Moduli + +### `main.c` — Collante e Loop Principale + +**Responsabilità**: inizializzazione hardware, sequenza di schermate (titolo → gioco → vittoria/sconfitta), game loop principale. + +Non contiene logica di gameplay propria: si limita a inizializzare i sotto-sistemi e richiamarli ogni frame nel corretto ordine. + +```c +// Ordine di chiamata nel game loop +wait_vbl_done(); // Sincronizza con VBlank +update_music(); // Avanza il tracker audio +update_rats(); // AI + movimento + riproduzione +update_cursor(); // Input giocatore + armi +update_bombs(); // Stato bomba + esplosioni +``` + +--- + +### `maze.c/h` — Generazione Procedurale + +Espone: +- `uint8_t maze[MAZE_HEIGHT][MAZE_PITCH]` — mappa globale in WRAM +- `void generate_maze(void)` — popola la mappa + +Non ha dipendenze esterne. È il modulo più puro. + +--- + +### `rat.c/h` — AI e Logica Topi + +Espone: +- `void init_rats(void)` — inizializza i 4 topi di partenza +- `void update_rats(void)` — movimento, riproduzione, collisioni +- `void kill_rats_at(uint8_t x, uint8_t y)` — elimina topi su una cella +- `void spawn_rat(uint8_t x, uint8_t y, uint8_t dir)` — spawna un cucciolo + +Dipende da `maze.c` (per il pathfinding) e `music.c` (per SFX nascita/morte). + +--- + +### `cursor.c/h` — Input del Giocatore + +Espone: +- `void init_cursor(void)` — carica il tile del cursore, posizione iniziale +- `void update_cursor(void)` — legge joypad, gestisce DAS, bomba, fucile, pausa audio + +Dipende da `bomb.c`, `music.c`, `rat.c` (per il fucile). + +--- + +### `bomb.c/h` — Meccanica Bomba + +Espone: +- `void init_bombs(void)` — carica sprite bomba, nasconde tutto +- `void drop_bomb(uint8_t x, uint8_t y)` — attiva una bomba sulla cella +- `void update_bombs(void)` — avanza timer, gestisce esplosione propagante + +Dipende da `maze.c` (per propagazione), `rat.c` (per kill), `music.c` (per SFX). + +--- + +### `music.c/h` — Tracker Audio + +Espone: +- `void init_music(void)` — configura registri APU, inizia la musica di gioco +- `void update_music(void)` — avanza il sequencer di un tick se necessario +- `void toggle_music(void)` — mute/unmute della musica di sottofondo +- `void play_sfx_*(void)` — effetti sonori istantanei (esplosione, fucile, ecc.) +- `void play_game_over_music(void)` / `play_victory_music(void)` / `play_title_music(void)` + +Non dipende da nessun altro modulo di gioco. + +--- + +### Moduli Asset (Sola Lettura) + +Tutti questi moduli espongono esclusivamente **array `const`** in ROM, generati da script Python offline. + +| Modulo | Contenuto | Dimensione tipica | +|--------|-----------|------------------| +| `tiles.c/h` | 22 tile 8×8: pavimenti + 16 varianti autotile cespugli | ~352 byte | +| `title_bg.c/h` | Tileset + mappa 20×18 dello sfondo titolo | ~8 KB | +| `rat_bg.c/h` | Tileset + mappa 20×18 dello sfondo game over | ~8 KB | +| `victory_bg.c/h` | Tileset + mappa 20×18 dello sfondo vittoria | ~8 KB | +| `pause_gfx.c/h` | 5 tile per le lettere P-A-U-S-E | ~80 byte | +| `numbers_gfx.c/h` | 10 tile per le cifre 0–9 | ~160 byte | +| `bomb_gfx.c` | Sprite bomba (3 fasi ticking + centro esplosione + fiamme) | ~176 byte | +| `mockup_gfx.c/h` | Tileset alternativo cespugli | ~varia | + +--- + +## Mappa della VRAM Sprite (Tile Indices) + +``` +Indice Contenuto +───────────────────────────────────────────────────── + 0 Topo: metà destra vista SINISTRA/DESTRA + 1 Topo: metà sinistra vista SINISTRA/DESTRA + 2 Topo: metà superiore vista SU/GIÙ + 3 Topo: metà inferiore vista SU/GIÙ + 4 Cursore (bordo quadrato lampeggiante) + 5 Bomba frame 3 (spenta) + 6 Bomba frame 2 (miccia lenta) + 7 Bomba frame 1 (miccia veloce, lampeggia) + 8 Centro esplosione (stella) + 9 Fiamma orizzontale (braccio est/ovest) + 10 Fiamma verticale (braccio nord/sud) + 11 Lettera P (PAUSE) + 12 Lettera A + 13 Lettera U + 14 Lettera S + 15 Lettera E + 16–24 (non usati o riservati) + 25–34 Cifre 0–9 (timer HUD) +``` + +--- + +## Mappa dell'OAM (Sprite Hardware Indices) + +``` +Slot Uso +────────────────────────────────────────────────────── + 0–1 Topo 0 (metà sinistra, metà destra) + 2–3 Topo 1 + 4–5 Topo 2 + 6–7 Topo 3 + 8–9 Topo 4 +10–11 Topo 5 +12–13 Topo 6 +14–15 Topo 7 +16–17 Topo 8 +18–19 Topo 9 +20–23 Pool esplosione bomba (gruppo A) +24 Flash sparo fucile +25–28 Timer HUD (4 cifre) +29–37 Pool esplosione bomba (gruppo B) +38 Sprite bomba principale (animato) +39 Cursore del giocatore +``` diff --git a/docs/hardware.md b/docs/hardware.md new file mode 100644 index 0000000..6c2ab97 --- /dev/null +++ b/docs/hardware.md @@ -0,0 +1,127 @@ +# Hardware Game Boy DMG + +Capire i vincoli dell'hardware è indispensabile per capire ogni scelta architetturale del codice. Il Game Boy DMG del 1989 è una macchina meravigliosamente vincolata. + +--- + +## CPU: Sharp SM83 + +Il processore è una via di mezzo tra lo Z80 e l'Intel 8080, con un set di istruzioni ridotto. + +| Proprietà | Valore | +|-----------|--------| +| **Architettura** | 8-bit (con registri a 16-bit per indirizzi) | +| **Clock** | ~4.194304 MHz | +| **Moltiplicazione hardware** | ❌ Assente | +| **Divisione hardware** | ❌ Assente | +| **Virgola mobile (FPU)** | ❌ Assente | +| **Stack** | 16-bit, in HRAM o WRAM | +| **Registri generali** | A, B, C, D, E, H, L (più F flags) | + +!!! warning "Niente moltiplicazioni!" + Qualsiasi `a * b` in C viene compilato da SDCC in una subroutine di addizioni ripetute. + Su questo hardware, `y * 19` può richiedere **20+ cicli di clock**. Il codice di MICE! + usa esclusivamente potenze di 2 (`y << 5`) per eliminare questo costo. + +--- + +## Memoria + +``` +0x0000 - 0x3FFF ROM Bank 0 (16 KB, fisso) +0x4000 - 0x7FFF ROM Bank 1+ (16 KB, switchable via MBC) +0x8000 - 0x9FFF VRAM (8 KB) + 0x8000 - 0x97FF Tile Data (384 tile × 16 byte = 6144 byte) + 0x9800 - 0x9BFF BG Map 0 (32×32 tile) + 0x9C00 - 0x9FFF BG Map 1 (32×32 tile) +0xA000 - 0xBFFF External RAM (cartuccia, non usata) +0xC000 - 0xDFFF WRAM (8 KB) +0xFE00 - 0xFE9F OAM (160 byte = 40 sprite × 4 byte) +0xFF00 - 0xFF7F I/O Registers +0xFF80 - 0xFFFE HRAM (127 byte, velocissima) +``` + +!!! info "VRAM e accesso sicuro" + La VRAM è accessibile **solo durante HBlank o VBlank**. GBDK gestisce questo + automaticamente tramite la funzione `wait_vbl_done()` che il game loop chiama + ogni frame per sincronizzarsi con il VBlank. + +--- + +## Video: PPU (Pixel Processing Unit) + +| Parametro | Valore | +|-----------|--------| +| **Risoluzione** | 160×144 pixel | +| **Colori** | 4 sfumature di grigio (palette software 2bpp) | +| **Tile size** | 8×8 pixel, 2 bit per pixel | +| **Tile in VRAM** | 256 (modalità $8000) o 256 (modalità $8800) | +| **Background map** | 32×32 tile (256×256 px), visibile 20×18 | +| **Sprite (OAM)** | Max **40 totali**, max **10 per scanline** | +| **Sprite size** | 8×8 o 8×16 (configurabile via LCDC) | +| **Layer** | Background + Window + Sprites | + +### Palette Hardware + +Il Game Boy non ha "colori" — ha 4 livelli di intensità per layer. Le palette sono configurate via registri: + +```c +BGP_REG = 0b11100100; // BG: 3=Nero, 2=Grigio Sc., 1=Grigio Ch., 0=Bianco +OBP0_REG = 0b11100100; // Sprite palette 0 (topi, cursore) +OBP1_REG = 0b11000000; // Sprite palette 1 (timer HUD - inverte contrasto) +``` + +Il bit 0 di ogni sprite in OAM seleziona quale palette usare (`S_PALETTE` flag). + +--- + +## Audio: APU (Audio Processing Unit) + +L'APU del Game Boy ha **4 canali** indipendenti, ciascuno controllato da registri hardware mappati in memoria. + +| Canale | Tipo | Registri | Uso in MICE! | +|--------|------|----------|--------------| +| **CH1** | Square Wave con Sweep | NR10–NR14 | Arpeggio melodico | +| **CH2** | Square Wave | NR21–NR24 | Melodia principale | +| **CH3** | Wave RAM (forma d'onda custom) | NR30–NR34 + 0xFF30 | Basso | +| **CH4** | Noise (LFSR) | NR41–NR44 | Percussioni + SFX | + +### Frequenza Note + +Le note musicali vengono codificate come valori di frequenza nei registri `NR_3` (low byte) e `NR_4` (high 3 bit): + +``` +f_reg = 2048 - (131072 / Hz) +``` + +Esempio per C4 (261.63 Hz): + +```c +#define N_C4 0x60B // = 2048 - (131072 / 261.63) ≈ 0x60B +``` + +--- + +## OAM: Object Attribute Memory + +Ogni sprite occupa **4 byte** nell'OAM: + +| Byte | Contenuto | +|------|-----------| +| 0 | Y position (schermo + 16) | +| 1 | X position (schermo + 8) | +| 2 | Tile index (in VRAM sprite tiles) | +| 3 | Flags (palette, flip X/Y, priorità) | + +Uno sprite è "nascosto" posizionandolo a coordinate `(0, 0)` — fuori dall'area visibile +(lo schermo inizia a Y=16, X=8 in coordinate OAM assolute). + +```c +// Nascondere uno sprite in GBDK +move_sprite(sprite_index, 0, 0); +``` + +!!! danger "Limite 10 sprite per scanline" + Se più di 10 sprite si trovano sulla stessa riga orizzontale (scanline), + gli ultimi vengono semplicemente ignorati dalla PPU. In MICE! i topi sono + disposti verticalmente nel labirinto per ridurre la probabilità di conflitti. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..05d22bb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,75 @@ +# MICE! — Documentazione Tecnica + +Benvenuto nella documentazione tecnica di **MICE!**, un gioco completo per **Game Boy DMG (1989)** scritto interamente in C con la toolchain [GBDK-2020](https://github.com/gbdk-2020/gbdk-2020). + +![Schermata Titolo e Gameplay](../screenshot.png) + +--- + +## Cos'è MICE! + +MICE! è un gioco di strategia in tempo reale a schermata fissa. Il labirinto viene generato proceduralmente ad ogni partita. Il giocatore controlla un cursore di mira e deve eliminare tutti i topi prima che il tempo scada. I topi si muovono autonomamente, trovano percorsi nel labirinto e si riproducono se si incontrano. + +### Meccaniche principali + +| Meccanica | Descrizione | +|-----------|-------------| +| **Generazione labirinto** | Recursive Backtracker iterativo, sempre risolvibile | +| **AI topi** | Pathfinding casuale senza backtracking, spatial hash per collisioni | +| **Riproduzione** | Spawn di un cucciolo a ogni incontro tra topi adulti | +| **Bomba (A)** | Esplosione a croce che si propaga lungo tutto il corridoio | +| **Fucile (B)** | Sparo istantaneo con cooldown di 3 secondi | +| **Timer** | Countdown visualizzato come HUD sprite nella barra inferiore | +| **Musica** | Tracker chiptune 4 canali nativo, jingle dedicati per vittoria/sconfitta | + +--- + +## Struttura della Documentazione + +| Sezione | Contenuto | +|---------|-----------| +| [Hardware Game Boy](hardware.md) | Vincoli fisici: SM83, VRAM, OAM, APU | +| [Architettura Moduli](architecture.md) | Grafico dei moduli C e dipendenze | +| [Loop Principale](main_loop.md) | Sequenza di inizializzazione e game loop | +| [Generazione Labirinto](maze.md) | Algoritmo Recursive Backtracker, MAZE_PITCH | +| [AI dei Topi](ai.md) | Movimento, pathfinding, riproduzione, spatial hash | +| [Rendering e Autotiling](rendering.md) | Tile system, bitmask 4-bit, hardware scrolling | +| [Sprite e OAM](sprites.md) | Allocazione 40 sprite hardware, meta-sprite 16×8 | +| [Armi e Gameplay](weapons.md) | Cursore DAS, bomba propagante, fucile con cooldown | +| [Audio](audio.md) | Mini-tracker 4 canali, SFX, Wave RAM | +| [Pipeline Asset](assets.md) | Script Python, conversione PNG → header C 2bpp | +| [Ottimizzazioni](optimizations.md) | Riepilogo di tutte le tecniche di ottimizzazione | + +--- + +## Quick Start + +```bash +# Compila tutto +make + +# Gioca +pyboy -w SDL2 -s 3 --sound-volume 100 maze.gb + +# Test audio interattivo +pyboy -w SDL2 -s 3 --sound-volume 100 test_audio.gb + +# Validazione headless +python3 tests/test_pyboy.py +``` + +--- + +## Dati Tecnici + +| Parametro | Valore | +|-----------|--------| +| **Target hardware** | Nintendo Game Boy DMG (1989) | +| **Toolchain** | GBDK-2020 + SDCC | +| **Linguaggio** | C (SM83/Z80-compatible) | +| **ROM size** | 32 KB (MBC1 `0x00`, solo ROM) | +| **Tile in VRAM** | 256 tile 8×8 @ 2bpp | +| **Sprite OAM** | 40 slot hardware (8×8 px ciascuno) | +| **RAM disponibile** | 8 KB (WRAM) | +| **FPS target** | 60 Hz (sincronizzato su VBlank) | +| **Frequenza CPU** | ~4.19 MHz | diff --git a/docs/main_loop.md b/docs/main_loop.md new file mode 100644 index 0000000..70e505f --- /dev/null +++ b/docs/main_loop.md @@ -0,0 +1,199 @@ +# Loop Principale e Inizializzazione + +`src/main.c` è il punto di ingresso del programma. Contiene la sequenza completa di boot, l'inizializzazione di tutti i sotto-sistemi e il game loop principale. + +--- + +## Sequenza di Avvio + +```mermaid +sequenceDiagram + participant HW as Hardware GB + participant main as main.c + participant sub as Sotto-sistemi + + HW->>main: Boot (entry point void main()) + + Note over main: === SCHERMATA TITOLO === + main->>HW: set_bkg_data(title_bg_tiles) + main->>HW: set_bkg_tiles(title_bg_map) + main->>HW: SHOW_BKG + DISPLAY_ON + main->>sub: play_title_music() + main->>HW: loop: joypad() → attendi START + + Note over main: === INIZIALIZZAZIONE GIOCO === + main->>HW: DISPLAY_OFF (safe VRAM write) + main->>HW: initrand(DIV_REG) + main->>HW: set_bkg_data(TileData) — tileset di gioco + main->>HW: Riempi mappa BG con siepi + main->>sub: generate_maze() + main->>HW: Rendering autotile del labirinto + main->>HW: move_bkg(252, 252) — centra il labirinto + main->>sub: init_rats() + main->>sub: init_cursor() + main->>sub: init_bombs() + main->>sub: init_music() + main->>HW: DISPLAY_ON + + Note over main: === GAME LOOP === + loop Ogni frame (60 Hz) + main->>HW: wait_vbl_done() + main->>sub: update_music() + main->>sub: update_rats() + main->>sub: update_cursor() + main->>sub: update_bombs() + main->>HW: Aggiorna timer HUD + main->>main: check: game_over_flag || victory_flag + end +``` + +--- + +## Seme Casuale da `DIV_REG` + +```c +// src/main.c, riga 65 +initrand(DIV_REG); +``` + +Il registro `DIV_REG` (`0xFF04`) è un timer hardware che si incrementa a ~16384 Hz indipendentemente dalla CPU. Il tempo esatto tra il boot e la pressione del tasto START da parte del giocatore è imprevedibile, rendendo il valore di `DIV_REG` un ottimo seme entropico per il generatore di numeri pseudo-casuali. + +!!! tip "Perché DIV_REG e non una costante?" + Se il seme fosse fisso, ogni partita genererebbe **esattamente lo stesso labirinto**. + `DIV_REG` garantisce che ogni run produca un labirinto diverso. + +--- + +## Caricamento VRAM in Sicurezza (DISPLAY_OFF) + +```c +// src/main.c +DISPLAY_OFF; // Disattiva PPU → VRAM scrivibile in qualsiasi momento +set_bkg_data(0, 22, TileData); // Carica tileset di gioco +// ... setup sprite data ... +generate_maze(); +// ... rendering del labirinto nella mappa BG ... +DISPLAY_ON; +``` + +La VRAM è accessibile **solo durante HBlank o VBlank** quando il display è acceso. Disattivare la PPU prima di caricare grandi blocchi di dati è il metodo più sicuro per evitare glitch visivi durante l'inizializzazione. + +--- + +## Rendering Autotile del Labirinto + +Dopo la generazione, ogni cella del labirinto viene tradotta in un indice tile in base ai suoi vicini: + +```c +// src/main.c, righe 94-112 +for (y = 0; y < MAZE_HEIGHT; y++) { + for (x = 0; x < MAZE_WIDTH; x++) { + uint8_t tile_idx = maze[y][x]; + + if (tile_idx == 1) { // Muro: calcola autotile + uint8_t mask = 0; + if (y > 0 && maze[y-1][x] == 1) mask |= 8; // Nord + if (x < MAZE_WIDTH-1 && maze[y][x+1] == 1) mask |= 4; // Est + if (y < MAZE_HEIGHT-1&& maze[y+1][x] == 1) mask |= 2; // Sud + if (x > 0 && maze[y][x-1] == 1) mask |= 1; // Ovest + tile_idx = 6 + mask; // tile 6-21: 16 varianti di cespuglio + } else { + // Percorso: variante di pavimento casuale + uint8_t r = rand() % 10; + if (r < 5) tile_idx = 0; // Pavimento liscio (più comune) + else tile_idx = 1 + (r % 5); // Variante decorata + } + set_bkg_tiles(x, y, 1, 1, &tile_idx); + } +} +``` + +Questo si traduce in 16 varianti di cespuglio (angolo, bordo N/S/E/O, T-junction, croce, ecc.) che si connettono visivamente in modo coerente. + +--- + +## Centratura con Hardware Scrolling + +La mappa del Background hardware è `32×32 tile` (256×256 px), ma il labirinto è `19×17 tile` (152×136 px). Per centrarlo nella finestra di 160×144: + +```c +// src/main.c, riga 115 +move_bkg(252, 252); +``` + +`252 = 256 - 4`, dove 4 è il margine di 4 pixel a sinistra. Questo sfrutta il wrap-around modulare della mappa hardware: lo scroll a `(252, 252)` equivale a iniziare la visualizzazione dal pixel `(-4, -4)` del tilemap, centrando il labirinto con un bordo di 4 px su tutti i lati. + +--- + +## Game Loop e Transizione agli Stati Finali + +```c +// src/main.c, game loop +while (1) { + wait_vbl_done(); // (1) Sync VBlank — punto sicuro per VRAM + update_music(); // (2) Audio — priorità alta per continuità sonora + update_rats(); // (3) AI — logica + sprite + update_cursor(); // (4) Input + armi + update_bombs(); // (5) Esplosioni + + // Aggiornamento HUD timer... + + if (game_over_flag || victory_flag) { + HIDE_SPRITES; // Nasconde tutti gli sprite + move_bkg(0, 0); // Azzera lo scrolling hardware + + // Carica lo sfondo dedicato in VRAM + if (victory_flag) { + set_bkg_data(0, 256, victory_bg_tiles); + set_bkg_tiles(0, 0, 20, 18, victory_bg_map); + play_victory_music(); + } else { + set_bkg_data(0, 256, rat_bg_tiles); + set_bkg_tiles(0, 0, 20, 18, rat_bg_map); + play_game_over_music(); + } + + SHOW_SPRITES; + while(1) { // Loop finale — solo audio e punteggio + update_music(); + wait_vbl_done(); + } + } +} +``` + +!!! note "Perché HIDE_SPRITES prima di caricare la VRAM?" + Quando si carica `set_bkg_data(0, 256, ...)` si sovrascrivono **tutti** i tile in VRAM, + compresi quelli usati dagli sprite. Se la PPU stesse ancora disegnando gli sprite in quel + momento, vedremmo un singolo frame di glitch grafico. `HIDE_SPRITES` sposta tutti gli sprite + a coordinate `(0,0)` atomicamente prima del caricamento. + +--- + +## Timer HUD con Aritmetica BCD + +Il timer usa un array di 4 cifre decimali invece di un singolo intero a 16-bit, evitando la divisione hardware: + +```c +// src/main.c +uint8_t timer_digits[4] = {0, 0, 0, 0}; // Rappresenta "0000" +uint8_t timer_frames = 0; + +// Ogni 60 frame (1 secondo), incrementa con carry BCD manuale +timer_frames++; +if (timer_frames >= 60) { + timer_frames = 0; + timer_digits[3]++; // Unità secondi + if (timer_digits[3] >= 10) { timer_digits[3] = 0; timer_digits[2]++; } + if (timer_digits[2] >= 6) { timer_digits[2] = 0; timer_digits[1]++; } // Decine secondi + if (timer_digits[1] >= 10) { timer_digits[1] = 0; timer_digits[0]++; } // Minuti +} + +// Render: ogni cifra usa un tile sprite (indice 25+digit) +set_sprite_tile(25, 25 + timer_digits[0]); +set_sprite_tile(26, 25 + timer_digits[1]); +set_sprite_tile(27, 25 + timer_digits[2]); +set_sprite_tile(28, 25 + timer_digits[3]); +``` + +Questa tecnica è identica a quella usata nei computer degli anni '80 per i contatori di punteggio, ed elimina completamente le divisioni a 16 bit che sarebbero necessarie per estrarre le singole cifre da un intero. diff --git a/docs/maze.md b/docs/maze.md new file mode 100644 index 0000000..3d96d31 --- /dev/null +++ b/docs/maze.md @@ -0,0 +1,166 @@ +# Generazione Procedurale del Labirinto + +**File**: `src/maze.c`, `src/maze.h` + +Il labirinto è generato ogni volta che si avvia una partita usando l'algoritmo **Recursive Backtracker** (noto anche come DFS su griglia), implementato in versione **iterativa** per evitare di esaurire lo stack hardware del Game Boy. + +--- + +## Struttura Dati + +```c +// src/maze.h +#define MAZE_WIDTH 19 // Larghezza logica (celle dispari per simmetria) +#define MAZE_HEIGHT 17 // Altezza logica +#define MAZE_PITCH 32 // Larghezza allocata (potenza di 2 — ottimizzazione critica) + +extern uint8_t maze[MAZE_HEIGHT][MAZE_PITCH]; +``` + +La matrice è allocata in **WRAM** (Work RAM), uno dei beni più preziosi del Game Boy. Con `MAZE_PITCH = 32`, occupa `17 × 32 = 544 byte` su un totale di 8192 byte disponibili. + +### Perché MAZE_PITCH = 32 e non 19? + +L'accesso a `maze[y][x]` viene compilato da SDCC come: + +``` +address = base_address + (y * MAZE_PITCH) + x +``` + +Con `MAZE_PITCH = 19`, SDCC emette una subroutine di moltiplicazione software (~20 cicli). Con `MAZE_PITCH = 32` (potenza di 2), la moltiplicazione diventa un **bit-shift**: + +```asm +; y * 32 = y << 5 +; Generato da SDCC con MAZE_PITCH = 32: +LD A, y_reg +ADD A, A ; ×2 +ADD A, A ; ×4 +ADD A, A ; ×8 +ADD A, A ; ×16 +ADD A, A ; ×32 ← 5 istruzioni, ~5 cicli +``` + +I 13 byte "sprecati" per riga valgono ampiamente il risparmio in cicli CPU su ogni accesso. + +--- + +## L'Algoritmo: Recursive Backtracker + +L'algoritmo scava i percorsi del labirinto partendo da una cella iniziale ed espandendosi in modo pseudo-casuale, tornando sui propri passi quando si trova in un vicolo cieco. + +### Garanzie dell'algoritmo + +1. **Labirinto perfetto**: ogni cella è raggiungibile da ogni altra tramite un unico percorso (albero di copertura). +2. **Nessun ciclo**: non esistono percorsi circolari. +3. **Nessuna isola**: non esistono sezioni disconnesse. + +### Implementazione Iterativa + +```c +// src/maze.c — stack customizzato in WRAM +uint8_t stack_x[100]; +uint8_t stack_y[100]; +uint8_t stack_ptr = 0; +``` + +!!! warning "Perché non la ricorsione?" + La versione ricorsiva di questo algoritmo richiede uno stack frame per ogni cella visitata. + Per un labirinto 19×17 = 323 celle, lo stack potrebbe crescere di **300+ livelli**. + Lo stack del Game Boy è in HRAM (127 byte) o WRAM, ed è condiviso con GBDK. + Una ricorsione profonda causerebbe uno **stack overflow** e un crash immediato. + +### Codice Completo + +```c +// src/maze.c +void generate_maze(void) { + uint8_t x, y, nx, ny; + uint8_t dirs[4]; + uint8_t count, r; + + // Fase 1: Riempi tutto con muri + for (y = 0; y < MAZE_HEIGHT; y++) + for (x = 0; x < MAZE_WIDTH; x++) + maze[y][x] = 1; + + // Fase 2: Cella iniziale in (1,1) + stack_ptr = 0; + push(1, 1); + maze[1][1] = 0; + + // Fase 3: DFS iterativo + while (stack_ptr > 0) { + x = stack_x[stack_ptr - 1]; + y = stack_y[stack_ptr - 1]; + + // Cerca vicini non visitati a distanza 2 + count = 0; + if (x >= 2 && maze[y][x-2] == 1) dirs[count++] = 0; // Sinistra + if (x <= MAZE_WIDTH-3 && maze[y][x+2] == 1) dirs[count++] = 1; // Destra + if (y >= 2 && maze[y-2][x] == 1) dirs[count++] = 2; // Su + if (y <= MAZE_HEIGHT-3&& maze[y+2][x] == 1) dirs[count++] = 3; // Giù + + if (count > 0) { + r = rand() & 3; // ← & invece di %, vedi Ottimizzazioni + while (r >= count) r = rand() & 3; + + nx = x; ny = y; + if (dirs[r] == 0) { nx -= 2; maze[y][x-1] = 0; } // Sinistra + else if (dirs[r] == 1) { nx += 2; maze[y][x+1] = 0; } // Destra + else if (dirs[r] == 2) { ny -= 2; maze[y-1][x] = 0; } // Su + else if (dirs[r] == 3) { ny += 2; maze[y+1][x] = 0; } // Giù + + maze[ny][nx] = 0; + push(nx, ny); + } else { + pop(&x, &y); // Backtrack + } + } +} +``` + +--- + +## Visualizzazione dell'Algoritmo + +```mermaid +flowchart TD + A[Inizializza: tutto muri] --> B[Push cella 1,1\nSegna come visitata] + B --> C{Stack vuoto?} + C -- No --> D[Leggi cella in cima allo stack] + D --> E{Vicini non visitati?} + E -- Sì --> F[Scegli direzione casuale\nDemolisci muro intermedio\nSegna nuova cella\nPush nuova cella] + E -- No --> G[Pop: backtrack] + F --> C + G --> C + C -- Sì --> H[Labirinto completo!] +``` + +--- + +## Convenzione Celle + +``` +1 = Muro (blocco di siepe) +0 = Percorso (camminabile) +``` + +Il labirinto usa coordinate con **indici dispari** per le celle percorribili e **indici pari** per i muri: + +``` +(1,1) (1,3) (1,5) ... ← Celle percorribili +(1,2) ← Muro tra (1,1) e (1,3) — può essere abbattuto +(0,*) ← Sempre muro (bordo) +``` + +Questo garantisce che il bordo esterno (riga 0, colonna 0, ultima riga, ultima colonna) sia sempre un muro solido. + +--- + +## Entropía: `initrand(DIV_REG)` + +```c +initrand(DIV_REG); // Seme dal timer hardware +``` + +Il registro `DIV_REG` (0xFF04) si incrementa a ~16384 Hz, indipendentemente dalla CPU. Il tempo tra il boot e la pressione di START da parte del giocatore è imprevedibile, garantendo un seme diverso ad ogni partita e quindi un labirinto sempre unico. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..955ebae --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,58 @@ +site_name: "MICE! — Documentazione Tecnica" +site_description: "Architettura e implementazione di un gioco completo per Game Boy DMG in C con GBDK-2020" +site_author: "Matteo" +docs_dir: docs + +theme: + name: material + language: it + palette: + - scheme: slate + primary: black + accent: green + font: + text: Roboto Mono + code: Roboto Mono + features: + - navigation.tabs + - navigation.sections + - navigation.top + - search.suggest + - content.code.annotate + - content.code.copy + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.tabbed: + alternate_style: true + - tables + - toc: + permalink: true + +nav: + - Home: index.md + - Hardware Game Boy: + - Panoramica Hardware: hardware.md + - Architettura del Codice: + - Struttura dei Moduli: architecture.md + - Loop Principale: main_loop.md + - Generazione del Labirinto: maze.md + - Intelligenza Artificiale dei Topi: ai.md + - Rendering e Grafica: + - Tile System e Autotiling: rendering.md + - Sprite e OAM Management: sprites.md + - Armi e Gameplay: + - Cursore, Bomba e Fucile: weapons.md + - Audio: + - Mini-Tracker Musicale: audio.md + - Pipeline Asset: assets.md + - Ottimizzazioni: optimizations.md