Browse Source

docs: add project technical documentation and mkdocs configuration

master
Matteo Benedetto 2 days ago
parent
commit
edd602f0ca
  1. 198
      docs/architecture.md
  2. 127
      docs/hardware.md
  3. 75
      docs/index.md
  4. 199
      docs/main_loop.md
  5. 166
      docs/maze.md
  6. 58
      mkdocs.yml

198
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
```

127
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.

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

199
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.

166
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.

58
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
Loading…
Cancel
Save