#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 "next_level.h" #include "stamina.h" #include "title_bg.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; // 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); // 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); // 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; } } // SPAWN DEL NEMICO // Cerca randomicamente un punto lontano almeno 3 celle dal giocatore while (1) { uint8_t rx = rand() % MAP_SIZE; uint8_t ry = rand() % MAP_SIZE; if (maze[ry][rx] == 1) { int8_t dx = (int8_t)rx - (int8_t)player_lx; int8_t dy = (int8_t)ry - (int8_t)player_ly; if (dx < 0) dx = -dx; if (dy < 0) dy = -dy; int8_t dist = (dx > dy) ? dx : dy; if (dist >= 3) { enemy_lx = rx; enemy_ly = ry; break; } } } // Reset Variabili di Gameplay enemy_is_moving = 0; enemy_cooldown = 60; game_over = 0; stamina = 100; stamina_recharge_timer = 0; is_jumping = 0; is_moving = 0; // Richiama render.c per forzare un disegno iniziale della scena draw_map(player_lx, player_ly); update_camera(); update_player_sprite(); } /** * 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) { // --- 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) { // ...Svuota lo schermo memset(map_buffer, 0, sizeof(map_buffer)); set_bkg_tiles(0, 0, 32, 32, map_buffer); } else if (game_over == 2) { // Mostra la schermata "Going Deeper" HIDE_SPRITES; // Reset scroll so the full screen image is aligned SCX_REG = 0; SCY_REG = 0; set_bkg_data(0, next_level_TILE_COUNT, next_level_tiles); set_bkg_tiles(0, 0, 20, 18, next_level_map); } // 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) { // Start gameover sequence state // gameover_music_timer/step are handled in play_music_tick inside sound.c automatically // We just trigger a starting dramatic beat. NR21_REG = 0x80; NR22_REG = 0xF3; NR23_REG = 0x2C; NR24_REG = 0x84; } } } else { // Dopo il timer, disegna i metasprite di GAME OVER fissi in mezzo allo schermo if (game_over == 1) { move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 88, 120); } // Il giocatore rimane visibile al centro, il nemico scompare se vinto o appare se sconfitto. update_player_sprite(); int16_t enemy_px = (enemy_lx - enemy_ly) * 16 + 96; int16_t enemy_py = (enemy_lx + enemy_ly) * 8 + 16; int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24; int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16; int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx; int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly; if (edx < 0) edx = -edx; if (edy < 0) edy = -edy; uint8_t ep_dist = (edx > edy) ? edx : edy; if (game_over == 1 && ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) { move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, enemy_screen_x, enemy_screen_y); } else { move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, 0, 0); } // Attende la pressione di START per riavviare if ((keys & J_START) && !(prev_keys & J_START)) { // Nasconde la grafica testuale e ricarica tutto l'engine 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). }