8-level progression with scaling difficulty and a finale

Maze size grows +2/level from 7x7 to 21x21 (cap MAX_MAP_SIZE=21, reached
at level 8). Additional difficulty axes scale with level:
- +1 ghost per level (up to MAX_ENEMIES=8); enemy state moved to arrays,
  enemy_logic.c rewritten as a multi-entity loop (own AI, render at OAM
  2+i*2, staggered cooldowns).
- Ghost cooldown shrinks: 60 -> ~11 frames between steps.
- Stamina recharge slows: 60 -> 144 frames per point.
- Fog tightens to 3x3 (fog_radius=1) from level 7.
Fog rendering uses a dynamic 16-row centered flush (with wrap) instead of
the fixed rows 2-17 / full 32x32, so the fog stays correct on big mazes
without the multi-frame stall of the full flush.

Game ends after level 8: reaching the hatch at level 8 sets game_over=3
(finale) instead of 2. Finale screen reloads the IBM font and writes
'YOU ESCAPED / THE DARKNESS / LEVEL 8 CLEARED / PRESS START' into the BG
map; START returns to the title (new game starts at level 1).

Verified via PyBoy: sizes 7..21, fog 2->1 at L7, enemies 1..8 (distinct,
on floor, can catch the player), cooldown/recharge scaling, L1 hatch ->
game_over=2, L8 hatch -> game_over=3, finale renders + START->title->L1.
This commit is contained in:
2026-06-24 13:04:39 +02:00
parent 5e8709ade0
commit 4968d2e885
10 changed files with 317 additions and 264 deletions
+11 -3
View File
@@ -39,10 +39,18 @@ Se la Stamina scende sotto 10, B+direzione **ripiega silenziosamente su camminat
Il gioco parte dal **livello 1**. Raggiungere la botola (casella traguardo) significa "sprofondare più giù" (*Going Deeper*): alla pressione di START il livello viene **incrementato** e viene generato un nuovo labirinto. In caso di sconfitta (cattura da parte del fantasma) si **ricomincia dallo stesso livello raggiunto**: il contatore non si azzera, solo il passaggio per il titolo (nuova partita) riparte da 1.
## Dimensione crescente del labirinto
## Dimensione crescente del labirinto e difficoltà progressiva
Ad ogni livello la **dimensione del labirinto cresce di 2 tile per lato**: si parte da 7x7 (livello 1) e si arriva a 9x9, 11x11, 13x13, 15x15, fino al cap di **17x17**. La dimensione corrente è la variabile globale `map_size` (l'array `maze` è allocato con bound `MAX_MAP_SIZE` = 17 in modo da contenere qualunque dimensione). Il DFS in `maze.c` genera un perfect maze su `map_size`x`map_size` (le celle dispari sono le stanze, per questo `map_size` è sempre dispari), poi lo rompe con i loop al 15% e posiziona la botola a distanza di Chebyshev ≥ `map_size/2` dalla partenza. Anche lo spawn del fantasma scala: parte a distanza ≥ `map_size/2`, così nelle mappe grandi non si attiva subito (il suo cono visivo resta comunque 5x5, fissato dal fog of war).
Ad ogni livello la **dimensione del labirinto cresce di 2 tile per lato**: 7x7 (livello 1) fino a **21x21** (livello 8). La dimensione corrente è la variabile globale `map_size` (l'array `maze` è allocato con bound `MAX_MAP_SIZE` = 21). Il DFS genera un perfect maze su `map_size`x`map_size` (le celle dispari sono le stanze, per questo `map_size` è sempre dispari), poi lo rompe con i loop al 15% e posiziona la botola a distanza di Chebyshev ≥ `map_size/2` dalla partenza.
Poiché con labirinti grandi la finestra fog-of-war 5x5, proiettata in coordinate isometriche assolute, può cadere fuori dal vecchio range di righe flussato (2-17) a causa del wrapping della mappa 32x32, `draw_map` ora flussa l'intera mappa 32x32 al hardware background. `draw_map` è chiamato solo ai passi del movimento (non ogni frame), quindi il costo è sostenibile.
Oltre alla dimensione, ci sono altri **assi di difficoltà** che scalano col livello:
- **Numero di fantasmi**: `num_enemies = level` (capped a `MAX_ENEMIES` = 8). Ogni fantasma ha stato indipendente (array) e AI greedy propria; cooldown iniziali sfasati per non sincronizzarli.
- **Fantasma più veloce**: `enemy_step_cooldown = 60 - 7*(level-1)` (floor 10) — pausa più breve tra i passi ai livelli alti.
- **Stamina più lenta**: `stamina_recharge_rate = 60 + 12*(level-1)` — la barra si ricarica più lentamente.
- **Nebbia più stretta**: `fog_radius = 1` (3x3) dal livello 7 (invece di 2 / 5x5).
**Il gioco finisce dopo il livello 8**: superare la botola al livello 8 setta `game_over = 3` (finale) invece di 2 (Going Deeper). La schermata finale usa il font IBM ricaricato per stampare "YOU ESCAPED / THE DARKNESS / LEVEL 8 CLEARED / PRESS START"; START torna al titolo (nuova partita dal livello 1).
Poiché con labirinti grandi la finestra fog-of-war, proiettata in coordinate isometriche assolute, può cadere fuori dal vecchio range di righe flussato (2-17) a causa del wrapping della mappa 32x32, `draw_map` ora usa un flush **dinamico a 16 righe** centrato sulla iso_y del centro di disegno (con gestione del wrap via due `set_bkg_tiles`). Questo mantiene le prestazioni del progetto originale (16 righe = 512 byte) coprendo la nebbia in qualunque posizione.
L'indicatore `L<n>` è mostrato in **alto a sinistra** tramite tre sprite hardware (OAM ID 2325), disegnati dall'asset `level.png` (11 glifi 8x16: 'L', '0''9'). Le decine vengono mostrate solo dal livello 10 in poi (sotto, lo sprite decine è spostato fuori schermo). Come la barra stamina (in alto a destra), l'indicatore è a coordinate-schermo fisse e indipendente dallo scroll isometrico, e viene nascosto durante il game over e sul titolo. La base VRAM dei glifi è allineata a un indice **pari** perché in modalità sprite 8x16 l'hardware ignora il bit meno significativo dell'indice tile.