#include #include #include #include #include #include "engine.h" #include "globals.h" #include "maze.h" #include "sound.h" #include "render.h" #include "player_logic.h" #include "enemy_logic.h" // Generated by png2asset #include "tiles.h" #include "player.h" #include "enemy.h" #include "gameover.h" #include "stamina.h" #include "level.h" #include "title_bg.h" #include "screens/screens.h" /** * Inizializza la schermata del titolo */ void title_init(void) { // Nasconde tutti gli sprite (sia personaggio che HUD) for (uint8_t i = 0; i < 40; i++) move_sprite(i, 0, 0); // Carica l'immagine di sfondo del titolo set_bkg_data(0, title_bg_TILE_COUNT, title_bg_tiles); set_bkg_tiles(0, 0, title_bg_WIDTH / 8, title_bg_HEIGHT / 8, title_bg_map); // Configura la palette standard B/N BGP_REG = 0xE4; // Centra lo scroll della telecamera in alto a sinistra (0,0) move_bkg(0, 0); // Inizializza l'Audio Hardware del Game Boy NR52_REG = 0x80; // Accende il chip audio NR50_REG = 0x77; // Volume master al massimo NR51_REG = 0xFF; // Abilita l'output stereo su tutti i canali // Avvia la musica del titolo riavviando il sequencer sound_reset_music_state(); remove_VBL(play_music_tick); add_VBL(play_music_tick); } /** * Loop di aggiornamento della schermata del titolo (non fa nulla, la pressione di START è gestita in main.c) */ void title_update(uint8_t keys, uint8_t prev_keys) { // Vuoto. La musica continua in background via Interrupt (VBL) } /** * Inizializza l'engine per la fase di Gameplay. * Qui viene orchestrata la "nascita" di un nuovo livello. */ void engine_init(void) { SPRITES_8x16; // Imposta la modalità Sprite Alti (16 pixel invece di 8) // Hard reset Audio NR52_REG = 0x80; NR50_REG = 0x77; NR51_REG = 0xFF; // Reset hint SUBITO (all'inizio della funzione, non alla fine: // la ROM a 32KB e' piena e il linker potrebbe troncare la coda). hint_active = 0; hint_displayed = 0; if (level == 1) hint_active = 1; // Carica il font testuale di sistema (IBM) font_init(); font_t ibm_font = font_load(font_ibm); font_set(ibm_font); // Avvia il sequencer per la musica In-Game "Eerie pulse" sound_reset_music_state(); remove_VBL(play_music_tick); add_VBL(play_music_tick); // Usa il registro divisore hardware (DIV_REG) per seedare l'RNG. Garantisce labirinti diversi ogni volta. initrand(DIV_REG); map_size = MAP_SIZE + 2 * (level - 1); // +2/level from 7, capped at MAX_MAP_SIZE (21) -> level 8 if (map_size > MAX_MAP_SIZE) map_size = MAX_MAP_SIZE; // --- Assi di difficolta' progressivi (scalano col livello) --- fog_radius = (level >= 7) ? 1 : 2; // nebbia 5x5 -> 3x3 dal livello 7 stamina_recharge_rate = 60 + (level - 1) * 12; // ricarica stamina piu' lenta enemy_step_cooldown = 60 - (level - 1) * 7; // fantasma piu' veloce (cooldown piu' corto) if (enemy_step_cooldown < 10) enemy_step_cooldown = 10; num_enemies = (level <= MAX_ENEMIES) ? level : MAX_ENEMIES; // +1 fantasma per livello // DELEGA LA GENERAZIONE: Chiede al modulo maze.c di creare l'array della mappa generate_maze(); // Imposta le palette (DMG compatibile) OBP0_REG = 0xE4; // Palette Normale (Nero, Grigio Scuro, Grigio Chiaro, Bianco) OBP1_REG = 0x1B; // Palette Invertita (per il fantasma: bianco, grigio scuro, nero) BGP_REG = 0xE4; // Palette Background // Carica in memoria VRAM la grafica di Tiles e Sprite set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles); set_sprite_data(0, player_TILE_COUNT, player_tiles); set_sprite_data(player_TILE_COUNT, enemy_TILE_COUNT, enemy_tiles); set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT, gameover_TILE_COUNT, gameover_tiles); // Load stamina_tiles AFTER the background tiles to prevent VRAM overlap in the shared block (indices > 127) set_sprite_data(tiles_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles); // Load the level HUD glyph tiles right after stamina (blank, 'L', '0'..'9'). set_sprite_data(LEVEL_SPRITE_BASE, level_TILE_COUNT, level_tiles); // SPAWN DEL GIOCATORE player_lx = 1; player_ly = 1; // Fallback di sicurezza: Cerca la prima casella libera se 1,1 è occupato (teoricamente impossibile col DFS) if (maze[player_ly][player_lx] == 0) { uint8_t found = 0; 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) { player_lx = x; player_ly = y; found = 1; break; } } if (found) break; } } uint8_t enemy_min_dist = map_size / 2; if (enemy_min_dist < 3) enemy_min_dist = 3; // SPAWN DEI NEMICI (num_enemies fantasmi): cella calpestabile lontana >= map_size/2 // dal giocatore e >= 2 dagli altri fantasmi gia' piazzati; cooldown sfasati (i*8) // cosi' non si muovono tutti in sincrono. Dopo 40 tentativi rilassa la distanza. for (uint8_t i = 0; i < num_enemies; i++) { uint8_t rx = 0, ry = 0, placed = 0; for (uint8_t tries = 0; tries < 80 && !placed; tries++) { uint8_t candx = rand() % map_size; uint8_t candy = rand() % map_size; if (maze[candy][candx] != 1) continue; int8_t dx = (int8_t)candx - (int8_t)player_lx; int8_t dy = (int8_t)candy - (int8_t)player_ly; if (dx < 0) dx = -dx; if (dy < 0) dy = -dy; int8_t dist = (dx > dy) ? dx : dy; uint8_t req = (tries < 40) ? enemy_min_dist : 2; if (dist < (int8_t)req) continue; uint8_t too_close = 0; for (uint8_t j = 0; j < i; j++) { int8_t ex = (int8_t)candx - (int8_t)enemy_lx[j]; int8_t ey = (int8_t)candy - (int8_t)enemy_ly[j]; if (ex < 0) ex = -ex; if (ey < 0) ey = -ey; if (((ex > ey) ? ex : ey) < 2) { too_close = 1; break; } } if (too_close) continue; rx = candx; ry = candy; placed = 1; } if (!placed) { for (uint8_t y = 1; y < map_size - 1 && !placed; y++) for (uint8_t x = 1; x < map_size - 1 && !placed; x++) if (maze[y][x] == 1 && !(x == player_lx && y == player_ly)) { rx = x; ry = y; placed = 1; } } enemy_lx[i] = rx; enemy_ly[i] = ry; enemy_is_moving[i] = 0; enemy_move_progress[i] = 0; enemy_cooldown[i] = (uint8_t)(enemy_step_cooldown + i * 8); } // Nasconde gli sprite dei nemici eccedenti (es. quando num_enemies cala tornando al titolo). for (uint8_t i = num_enemies; i < MAX_ENEMIES; i++) { enemy_is_moving[i] = 0; move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 2 + i * 2, 0, 0); } // Reset Variabili di Gameplay game_over = 0; stamina = 100; stamina_recharge_timer = 0; is_jumping = 0; is_moving = 0; is_running = 0; // Richiama render.c per forzare un disegno iniziale della scena draw_map(player_lx, player_ly); update_camera(); update_player_sprite(); update_level_display(); // Mostra l'indicatore livello (top-left) } /** * Loop Principale di Aggiornamento del Gioco (chiamato ad ogni Frame ~60 FPS). * Questo metodo funge da "Direttore d'Orchestra", delegando i compiti ai moduli specializzati. */ void engine_update(uint8_t keys, uint8_t prev_keys) { // Indicatore livello: aggiornato ogni frame. Si nasconde da solo quando // `game_over` e' attivo (sconfitta/vittoria) grazie al check interno. update_level_display(); // --- SCHERMATA ISTRUZIONI (livello 1 o SELECT per ri-mostrarla) --- // Testuale con font IBM. Stesso pattern del finale (game_over==3) e della // sconfitta (game_over==1). Si chiude con B; SELECT la ri-apre in gioco. if (hint_active) { if (!hint_displayed) { hint_displayed = 1; show_instructions(); } // B chiude la schermata e riprende il gioco if ((keys & J_B) && !(prev_keys & J_B)) { HIDE_WIN; // nascondi il Window: il BG map del gameplay e' intatto BGP_REG = 0xE4; // Ripristina i tile del maze in VRAM (font_load li aveva sovrascritti) set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles); // Ricarica i tile degli sprite (stessa tile RAM condivisa) set_sprite_data(0, player_TILE_COUNT, player_tiles); set_sprite_data(player_TILE_COUNT, enemy_TILE_COUNT, enemy_tiles); set_sprite_data(tiles_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles); set_sprite_data(LEVEL_SPRITE_BASE, level_TILE_COUNT, level_tiles); draw_map(player_lx, player_ly); // ridisegna la scena (tile ripristinati) update_camera(); update_player_sprite(); SHOW_SPRITES; hint_active = 0; } return; // gioco in pausa mentre la schermata e' attiva } // SELECT in gioco: ri-mostra la schermata istruzioni if ((keys & J_SELECT) && !(prev_keys & J_SELECT) && !game_over) { hint_active = 1; hint_displayed = 0; } // --- CHEAT: SELECT+A+B = vinci il livello (per test) --- if ((keys & J_SELECT) && (keys & J_A) && (keys & J_B) && !game_over) { if (level >= 8) { game_over = 3; // Finale tragico (livello 8 completato) } else { game_over = 2; // Victory / Going Deeper } game_over_timer = 30; // timer drammatico (mezzo secondo) sound_reset_music_state(); } // --- GESTIONE STATO DI FINE GIOCO (Sconfitta o Vittoria) --- if (game_over) { if (game_over_timer > 0) { game_over_timer--; // Quando scade il timer drammatico... if (game_over_timer == 0) { if (game_over == 1) { show_death(); } else if (game_over == 2) { show_going_deeper(); } else if (game_over == 3) { show_finale(); } // Imposta i timer audio per far iniziare la musica finale dal modulo sound.c // La musica verrà suonata dall'interrupt play_music_tick in background. if (game_over == 1 || game_over == 3) { // Start gameover sequence state (tragic beat: sconfitta o finale) NR21_REG = 0x80; NR22_REG = 0xF3; NR23_REG = 0x2C; NR24_REG = 0x84; } } } else { // Dopo il timer: nasconde tutti i fantasmi (niente AI durante il game over) for (uint8_t i = 0; i < MAX_ENEMIES; i++) { move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 2 + i * 2, 0, 0); } // Sconfitta: metasprite GAME OVER + giocatore al centro. if (game_over == 1) { move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 88, 120); update_player_sprite(); } // Attende la pressione di START per riavviare if ((keys & J_START) && !(prev_keys & J_START)) { if (game_over == 2) { // Going Deeper -> livello successivo (max 8) if (level < 8) level++; move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 0, 0); SHOW_SPRITES; engine_init(); } else if (game_over == 3) { // Finale: torna al titolo (la prossima partita ripartira' dal livello 1) app_state = 0; title_init(); } else { // Sconfitta: ricomincia dallo stesso livello raggiunto (non si azzera) move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 0, 0); SHOW_SPRITES; engine_init(); } } } return; } // --- GESTIONE DEL LOOP DI GIOCO STANDARD --- // 1. DELEGA L'AI DEL NEMICO (se il fantasma si deve muovere, lo calcola qui) update_enemy_logic(); // 2. DELEGA LA LOGICA DEL GIOCATORE (movimento, tasti, animazioni) update_player_movement(keys, prev_keys); // Note: Il rendering effettivo della mappa e della telecamera avviene già all'interno di // update_player_movement durante l'interpolazione del passo (per fluidità sub-tile). }