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; } }