From ae0706ccfb5c2fcd1a5152bc036f6d15a917c294 Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Wed, 24 Jun 2026 11:18:31 +0200 Subject: [PATCH] Place the hatch on any sufficiently-distant tile, not just the south edge The victory hatch (maze[y][x]==2) is now chosen among all walkable cells at Chebyshev distance >= MIN_GOAL_DIST (3) from the player start (1,1), instead of being constrained to the bottom row. This lets the goal appear anywhere in the maze while always staying far from the start. - maze.c: collect all floor cells with chebyshev(1,1) >= 3 and pick one at random; fallback to the farthest floor cell if none qualifies. - doc/generation.md + report: document the new placement rule. --- doc/AScreamFromTheDark_report.md | 2 +- doc/generation.md | 6 +-- src/maze.c | 73 ++++++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/doc/AScreamFromTheDark_report.md b/doc/AScreamFromTheDark_report.md index 099c936..8ace14f 100644 --- a/doc/AScreamFromTheDark_report.md +++ b/doc/AScreamFromTheDark_report.md @@ -58,7 +58,7 @@ Camera centrata: `scroll_x = px - 64`, `scroll_y = py - 72` (160×144 display ### 3.2 Generazione del labirinto (`maze.c`) - **DFS iterativo con stack in WRAM** (`stack_x[49]/stack_y[49]`): evita l'overflow dello stack hardware del LR35902 ricorrendo a un array in RAM. Celle dispari = stanze, celle pari = muri divisori; scava il muro di mezzo con la media aritmetica delle coordinate. - **Fase 2 — loop**: riapre muri residui che collegano due corridoi opposti con probabilità 15%. Vitale per il gameplay d'inseguimento (permette di aggirare il fantasma). -- **Fase 3 — botola**: sceglie un punto **casuale** sul bordo sud (`y = MAP_SIZE-2`) tra le celle calpestabili, invece della "distanza di Manhattan massima" originaria (la doc `generation.md` è rimasta indietro rispetto al codice: vedi §7 Incongruenze). Fallback estremo su `(5,5)` se la riga fosse inaccessibile. +- **Fase 3 — botola**: sceglie una cella calpestabile **a sufficiente distanza** dalla partenza `(1,1)` (distanza di Chebyshev ≥ `MIN_GOAL_DIST` = 3) tra **tutte** le celle del labirinto, non più vincolata al bordo sud. Fallback estremo sulla cella calpestabile più lontana in assoluto da `(1,1)` se nessuna cella fosse a sufficienza distante (teoricamente impossibile in un perfect maze 7x7). ### 3.3 Movimento & DAS (`player_logic.c`) - **State machine rigida**: durante `is_moving` (16 frame di LERP) ogni input è ignorato → movimento strettamente grid-based stile Zelda/Pokémon. diff --git a/doc/generation.md b/doc/generation.md index a88fc5b..038151f 100644 --- a/doc/generation.md +++ b/doc/generation.md @@ -20,7 +20,7 @@ Questo genera anelli (loop) all'interno del labirinto, permettendo al giocatore ## Posizionamento del Traguardo (La Botola) La casella traguardo è una **botola** nel terreno (ID 2) che il giocatore deve raggiungere per "sprofondare più giù" (*Going Deeper*) e avanzare di livello. -Invece di cercare il punto matematicamente più lontano dalla partenza, l'algoritmo attuale sceglie un **punto casuale sul bordo sud** della mappa (`y = MAP_SIZE - 2`) tra le celle calpestabili di quella riga. Questo permette alla botola di apparire in una posizione imprevedibile lungo il bordo sud ad ogni partita. -Se, per un caso limite, la riga in fondo fosse inaccessibile, esiste un **fallback estremo** che posiziona la botola nell'angolo `(MAP_SIZE-2, MAP_SIZE-2)`. +La botola viene piazzata su una qualunque cella calpestabile a **sufficiente distanza** dalla casella di partenza del giocatore `(1,1)`. Si raccolgono tutte le celle `maze[y][x] == 1` la cui distanza di Chebyshev da `(1,1)` sia `>= MIN_GOAL_DIST` (3) e se ne sceglie una a caso. In questo modo il traguardo è sempre lontano dall'inizio ma può trovarsi su una qualunque tile del labirinto, non più vincolato al bordo sud. +Se, per un caso limite, nessuna cella fosse a sufficienza distante (teoricamente impossibile in un perfect maze 7x7 con partenza 1,1), esiste un **fallback estremo** che posiziona la botola nella cella calpestabile più lontana in assoluto da `(1,1)`. -Nota: storicamente il posizionamento usava la **Distanza di Manhattan** massima rispetto a `1,1`; il comportamento è stato cambiato per aumentare la varietà delle partite. La botola è disegnata con un oggetto complesso a maschera 4-vicini nel Pass 2 del renderer (vedi `graphics.md`). +La distanza di Chebyshev è coerente col resto dell'engine (fog of war, attivazione del nemico). La botola è disegnata con un oggetto complesso a maschera 4-vicini nel Pass 2 del renderer (vedi `graphics.md`). diff --git a/src/maze.c b/src/maze.c index 3f618cd..878c4ad 100644 --- a/src/maze.c +++ b/src/maze.c @@ -104,29 +104,66 @@ void generate_maze(void) { } } - // FASE 3: Calcoliamo il traguardo (Casella di Vittoria). - // Invece della coordinata più lontana, scegliamo un punto casuale - // lungo il bordo inferiore del labirinto (y = MAP_SIZE - 2). - // Questo permette alla scala di apparire ovunque sul bordo sud. - uint8_t valid_x[MAP_SIZE]; + // FASE 3: Posizionamento della Botola (Traguardo). + // La botola viene piazzata su una cella calpestabile a "sufficiente distanza" + // dalla casella di partenza del giocatore (1,1), cosi' che il traguardo sia + // sempre lontano ma possa trovarsi su una qualunque tile del labirinto, + // non piu' vincolato al bordo sud. Usiamo la distanza di Chebyshev + // (consistente con il resto dell'engine: fog of war, attivazione nemico). +#define MIN_GOAL_DIST 3 + uint8_t valid_x[MAP_SIZE * MAP_SIZE]; + uint8_t valid_y[MAP_SIZE * MAP_SIZE]; uint8_t num_valid = 0; - - for (uint8_t x = 1; x < MAP_SIZE - 1; x++) { - if (maze[MAP_SIZE - 2][x] == 1) { - valid_x[num_valid] = x; - num_valid++; + + for (uint8_t y = 1; y < MAP_SIZE - 1; y++) { + for (uint8_t x = 1; x < MAP_SIZE - 1; x++) { + if (maze[y][x] == 1) { + int8_t dx = (int8_t)x - 1; + int8_t dy = (int8_t)y - 1; + if (dx < 0) dx = -dx; + if (dy < 0) dy = -dy; + int8_t dist = (dx > dy) ? dx : dy; + if (dist >= MIN_GOAL_DIST) { + valid_x[num_valid] = x; + valid_y[num_valid] = y; + num_valid++; + } + } } } - + if (num_valid > 0) { - uint8_t target_x = valid_x[rand() % num_valid]; - maze[MAP_SIZE - 2][target_x] = 2; + uint8_t pick = rand() % num_valid; + uint8_t target_x = valid_x[pick]; + uint8_t target_y = valid_y[pick]; + maze[target_y][target_x] = 2; stairs_lx = target_x; - stairs_ly = MAP_SIZE - 2; + stairs_ly = target_y; } else { - // Fallback estremo se la riga in fondo fosse inaccessibile - maze[MAP_SIZE - 2][MAP_SIZE - 2] = 2; - stairs_lx = MAP_SIZE - 2; - stairs_ly = MAP_SIZE - 2; + // Fallback estremo: nessuna cella a sufficienza distante (teoricamente + // impossibile in un perfect maze 7x7 con partenza 1,1, ma difensivo). + // Sceglie la cella calpestabile piu' lontana in assoluto da (1,1). + uint8_t best_x = MAP_SIZE - 2; + uint8_t best_y = MAP_SIZE - 2; + int8_t best_dist = -1; + for (uint8_t y = 1; y < MAP_SIZE - 1; y++) { + for (uint8_t x = 1; x < MAP_SIZE - 1; x++) { + if (maze[y][x] == 1) { + int8_t dx = (int8_t)x - 1; + int8_t dy = (int8_t)y - 1; + if (dx < 0) dx = -dx; + if (dy < 0) dy = -dy; + int8_t dist = (dx > dy) ? dx : dy; + if (dist > best_dist) { + best_dist = dist; + best_x = x; + best_y = y; + } + } + } + } + maze[best_y][best_x] = 2; + stairs_lx = best_x; + stairs_ly = best_y; } }