From b4d4b365dba30f50d7f334cc532c19612cb6d034 Mon Sep 17 00:00:00 2001 From: enne2 Date: Wed, 17 Jun 2026 22:26:22 +0200 Subject: [PATCH] fix: Synchronize rat reproduction pause with global tile movement grid to prevent desync --- src/cursor.c | 8 +- src/main.c | 8 +- src/music.c | 38 +++++-- src/music.h | 3 + src/rat.c | 295 +++++++++++++++++++++++++++++++++------------------ src/rat.h | 20 +--- 6 files changed, 239 insertions(+), 133 deletions(-) diff --git a/src/cursor.c b/src/cursor.c index 7a6eff8..b4540e7 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -23,8 +23,8 @@ void init_cursor(void) { // (Gli indici 0-3 sono usati dai tile del topo) set_sprite_data(4, 1, CursorSpriteData); - // Usa lo sprite hardware numero 2 (0 e 1 sono il topo) - set_sprite_tile(2, 4); + // Usa lo sprite hardware numero 39 (lasciando 0-38 liberi per i ratti) + set_sprite_tile(39, 4); } void update_cursor(void) { @@ -49,9 +49,9 @@ void update_cursor(void) { // Effetto lampeggiante: nascondi il cursore per metà del ciclo (ogni 16 frame) blink++; if (blink & 0x10) { - move_sprite(2, 0, 0); // Nascondi spostandolo fuori schermo + move_sprite(39, 0, 0); // Nascondi spostandolo fuori schermo } else { // Posiziona il cursore calcolando offset (12, 20) per centrarlo sulla griglia 8x8 - move_sprite(2, cursor_x * 8 + 12, cursor_y * 8 + 20); + move_sprite(39, cursor_x * 8 + 12, cursor_y * 8 + 20); } } diff --git a/src/main.c b/src/main.c index 628fcac..5e9a4ba 100644 --- a/src/main.c +++ b/src/main.c @@ -58,8 +58,8 @@ void main(void) { // Trucco di centraggio (Hardware Scrolling): move_bkg(252, 252); - // Inizializza il topo autonomo nel punto di partenza (1, 1) - init_rat(1, 1); + // Inizializza i topi autonomi + init_rats(); // Inizializza il cursore controllato dal giocatore init_cursor(); @@ -76,8 +76,8 @@ void main(void) { // Aggiorna la musica in background update_music(); - // Aggiorna l'intelligenza artificiale e l'animazione del topo - update_rat(); + // Aggiorna l'intelligenza artificiale e l'animazione dei topi + update_rats(); // Aggiorna l'input del cursore del giocatore update_cursor(); diff --git a/src/music.c b/src/music.c index b18b405..44094b9 100644 --- a/src/music.c +++ b/src/music.c @@ -192,8 +192,29 @@ const uint8_t* const drum_parts[4] = { trk_drum_0, trk_drum_1, trk_drum_2, trk_d static uint16_t tick = 0; static uint8_t frame_counter = 0; +static uint8_t sfx_timer = 0; #define FRAMES_PER_TICK 8 // Velocità del tracker +void play_sfx_moan(void) { + // Gemito: sweep up e vibrato + NR10_REG = 0x45; + NR11_REG = 0x80; + NR12_REG = 0xF3; + NR13_REG = 0x40; + NR14_REG = 0x86; + sfx_timer = 40; // Blocca CH1 per 40 frame +} + +void play_sfx_plop(void) { + // Plop: tono acuto e brevissimo + NR10_REG = 0x00; + NR11_REG = 0x80; + NR12_REG = 0xF1; + NR13_REG = 0xFF; + NR14_REG = 0x87; + sfx_timer = 15; +} + void init_music(void) { // Reset e attivazione master sound NR52_REG = 0x00; @@ -211,21 +232,26 @@ void init_music(void) { tick = 0; frame_counter = 0; + sfx_timer = 0; } void update_music(void) { + if (sfx_timer > 0) sfx_timer--; + if (frame_counter == 0) { uint8_t part = (tick >> 6) & 0x03; // tick / 64 uint8_t sub = tick & 0x3F; // tick % 64 // --- CH1: Arpeggio --- - uint16_t f1 = arp_parts[part][sub]; - NR10_REG = 0x00; - NR11_REG = 0x80; - NR12_REG = 0x51; // Volume basso, decadimento rapido - NR13_REG = (uint8_t)(f1 & 0xFF); - NR14_REG = 0x80 | ((f1 >> 8) & 0x07); + if (sfx_timer == 0) { + uint16_t f1 = arp_parts[part][sub]; + NR10_REG = 0x00; + NR11_REG = 0x80; + NR12_REG = 0x51; // Volume basso, decadimento rapido + NR13_REG = (uint8_t)(f1 & 0xFF); + NR14_REG = 0x80 | ((f1 >> 8) & 0x07); + } // --- CH2: Melody --- uint16_t f2 = mel_parts[part][sub]; diff --git a/src/music.h b/src/music.h index 3014162..61d9aad 100644 --- a/src/music.h +++ b/src/music.h @@ -4,4 +4,7 @@ void init_music(void); void update_music(void); +void play_sfx_moan(void); +void play_sfx_plop(void); + #endif diff --git a/src/rat.c b/src/rat.c index 4ae0bd5..ac09333 100644 --- a/src/rat.c +++ b/src/rat.c @@ -1,15 +1,9 @@ -/** - * @file rat.c - * @brief Implementazione del topo autonomo usando Meta-Sprite (16x8 / 8x16). - */ - #include "rat.h" #include "maze.h" +#include "music.h" #include #include -// Dati dei 4 tile per creare i meta-sprite del topo. Usa solo la forma "Giù" ruotata per tutte le direzioni! -// Colori: 0=Trasparente (Bianco), 1=Grigio Chiaro (Corpo), 2=Grigio Scuro (Macchie/Naso), 3=Nero (Contorni/Pupille) const unsigned char RatSpriteData[] = { // 0: Destra - Metà Sinistra (Coda/Posteriore) ruotata a partire da Giù 0x00, 0x00, 0x07, 0x07, 0x3F, 0x38, 0x5E, 0x71, 0x5B, 0x74, 0x3F, 0x38, 0x07, 0x07, 0x00, 0x00, @@ -22,13 +16,22 @@ const unsigned char RatSpriteData[] = { 0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }; -static uint8_t rat_x = 1; -static uint8_t rat_y = 1; -static uint8_t target_x = 1; -static uint8_t target_y = 1; -static uint8_t pixel_x = 0; -static uint8_t pixel_y = 0; -static uint8_t current_dir = 255; +typedef struct { + uint8_t active; + uint8_t rat_x; + uint8_t rat_y; + uint8_t target_x; + uint8_t target_y; + uint8_t pixel_x; + uint8_t pixel_y; + uint8_t current_dir; + uint8_t sprite_base_idx; + uint8_t reproduce_timer; + uint8_t cooldown_timer; + uint8_t is_mother; // flag per far partorire un solo ratto +} Rat; + +static Rat rats[MAX_RATS]; static uint8_t get_opposite(uint8_t dir) { if (dir == 0) return 1; @@ -38,103 +41,191 @@ static uint8_t get_opposite(uint8_t dir) { return 255; } -void init_rat(uint8_t start_x, uint8_t start_y) { - rat_x = target_x = start_x; - rat_y = target_y = start_y; - pixel_x = rat_x * 8; - pixel_y = rat_y * 8; - current_dir = 255; - +void init_rats(void) { // Carica tutti i 4 tile per le combinazioni 16x8 e 8x16 set_sprite_data(0, 4, RatSpriteData); OBP0_REG = 0xE4; // Palette standard - // Imposta tile di default (Giù) - set_sprite_tile(0, 2); - set_sprite_tile(1, 3); + // Posizioni iniziali garantite vuote (coordinate dispari) + uint8_t start_pos[2][2] = { + {1, 1}, + {17, 15} + }; + + for (uint8_t i = 0; i < MAX_RATS; i++) { + rats[i].active = 0; + rats[i].reproduce_timer = 0; + rats[i].cooldown_timer = 0; + rats[i].is_mother = 0; + rats[i].sprite_base_idx = i * 2; + // Nascondi sprite + move_sprite(rats[i].sprite_base_idx, 0, 0); + move_sprite(rats[i].sprite_base_idx + 1, 0, 0); + } + + // Attiva i primi due topi + for (uint8_t i = 0; i < 2; i++) { + rats[i].active = 1; + rats[i].rat_x = start_pos[i][0]; + rats[i].rat_y = start_pos[i][1]; + rats[i].target_x = start_pos[i][0]; + rats[i].target_y = start_pos[i][1]; + rats[i].pixel_x = rats[i].rat_x * 8; + rats[i].pixel_y = rats[i].rat_y * 8; + rats[i].current_dir = 255; + } } -void update_rat(void) { - if (rat_x == target_x && rat_y == target_y) { - uint8_t valid_dirs[4]; - uint8_t num_valid = 0; - - if (rat_y < MAZE_HEIGHT - 1 && maze[rat_y + 1][rat_x] == 0) valid_dirs[num_valid++] = 0; - if (rat_y > 0 && maze[rat_y - 1][rat_x] == 0) valid_dirs[num_valid++] = 1; - if (rat_x > 0 && maze[rat_y][rat_x - 1] == 0) valid_dirs[num_valid++] = 2; - if (rat_x < MAZE_WIDTH - 1 && maze[rat_y][rat_x + 1] == 0) valid_dirs[num_valid++] = 3; - - if (num_valid > 0) { - if (num_valid > 1 && current_dir != 255) { - uint8_t opp = get_opposite(current_dir); - uint8_t filtered[4]; - uint8_t num_filtered = 0; - for (uint8_t i = 0; i < num_valid; i++) { - if (valid_dirs[i] != opp) { - filtered[num_filtered++] = valid_dirs[i]; - } - } - if (num_filtered > 0) current_dir = filtered[rand() % num_filtered]; - else current_dir = valid_dirs[rand() % num_valid]; - } else { - current_dir = valid_dirs[rand() % num_valid]; - } +void spawn_rat(uint8_t x, uint8_t y) { + for (uint8_t i = 0; i < MAX_RATS; i++) { + if (!rats[i].active) { + rats[i].active = 1; + rats[i].rat_x = x; + rats[i].rat_y = y; + rats[i].target_x = x; + rats[i].target_y = y; + rats[i].pixel_x = x * 8; + rats[i].pixel_y = y * 8; + rats[i].current_dir = 255; + rats[i].reproduce_timer = 0; + rats[i].cooldown_timer = 120; // Il cucciolo non si riproduce subito + rats[i].is_mother = 0; + play_sfx_plop(); // Suono di nascita! + break; + } + } +} + +void update_rats(void) { + static uint8_t frame_counter = 0; + frame_counter++; + uint8_t do_move = (frame_counter & 1); // Muovi di 1 pixel ogni 2 frame + + // 1. Controlla collisioni per riproduzione (se sono sulla stessa cella e fermi) + for (uint8_t i = 0; i < MAX_RATS; i++) { + if (!rats[i].active || rats[i].reproduce_timer > 0 || rats[i].cooldown_timer > 0) continue; + for (uint8_t j = i + 1; j < MAX_RATS; j++) { + if (!rats[j].active || rats[j].reproduce_timer > 0 || rats[j].cooldown_timer > 0) continue; - if (current_dir == 0) target_y++; - else if (current_dir == 1) target_y--; - else if (current_dir == 2) target_x--; - else if (current_dir == 3) target_x++; + if (rats[i].rat_x == rats[j].rat_x && rats[i].rat_y == rats[j].rat_y && + rats[i].pixel_x == rats[j].pixel_x && rats[i].pixel_y == rats[j].pixel_y) { + // Incontro riproduttivo! + // Usa 64 frames invece di 60 perché è un multiplo esatto di 16 (il tempo che un topo impiega + // per attraversare esattamente un tile di 8 pixel). Così non perdono la sincronia di fase globale! + rats[i].reproduce_timer = 64; + rats[j].reproduce_timer = 64; + rats[i].is_mother = 1; // Solo i farà spawnare il cucciolo + rats[j].is_mother = 0; + play_sfx_moan(); // Suono d'amore + break; + } } } - uint8_t target_px = target_x * 8; - uint8_t target_py = target_y * 8; - - static uint8_t frame_counter = 0; - frame_counter++; - if (frame_counter & 1) { - if (pixel_x < target_px) pixel_x++; - else if (pixel_x > target_px) pixel_x--; - if (pixel_y < target_py) pixel_y++; - else if (pixel_y > target_py) pixel_y--; - } - - if (pixel_x == target_px && pixel_y == target_py) { - rat_x = target_x; - rat_y = target_y; - } - - // Aggiorna gli hardware sprite (Meta-Sprite system) - uint8_t base_x = pixel_x + 12; - uint8_t base_y = pixel_y + 20; - - if (current_dir == 0 || current_dir == 255) { // Giù (Verticale 8x16) - set_sprite_tile(0, 2); - set_sprite_tile(1, 3); - set_sprite_prop(0, 0); - set_sprite_prop(1, 0); - move_sprite(0, base_x, base_y - 4); - move_sprite(1, base_x, base_y + 4); - } else if (current_dir == 1) { // Su (Verticale 8x16, FLIPPATO IN Y) - set_sprite_tile(0, 3); // Naso diventa parte alta - set_sprite_tile(1, 2); // Coda diventa parte bassa - set_sprite_prop(0, S_FLIPY); - set_sprite_prop(1, S_FLIPY); - move_sprite(0, base_x, base_y - 4); - move_sprite(1, base_x, base_y + 4); - } else if (current_dir == 2) { // Sinistra (Flippato orizzontalmente per puntare a sinistra) - set_sprite_tile(0, 1); // Naso flippato (va a sinistra) - set_sprite_tile(1, 0); // Coda flippata (va a destra) - set_sprite_prop(0, S_FLIPX); - set_sprite_prop(1, S_FLIPX); - move_sprite(0, base_x - 4, base_y); - move_sprite(1, base_x + 4, base_y); - } else if (current_dir == 3) { // Destra (Punta a destra nativamente) - set_sprite_tile(0, 0); // Coda (metà sinistra) - set_sprite_tile(1, 1); // Naso (metà destra) - set_sprite_prop(0, 0); - set_sprite_prop(1, 0); - move_sprite(0, base_x - 4, base_y); - move_sprite(1, base_x + 4, base_y); + // 2. Aggiorna lo stato di ogni topo + for (uint8_t i = 0; i < MAX_RATS; i++) { + Rat* r = &rats[i]; + if (!r->active) continue; + + if (r->cooldown_timer > 0) { + r->cooldown_timer--; + } + + // Se si stanno riproducendo, aspetta + if (r->reproduce_timer > 0) { + r->reproduce_timer--; + if (r->reproduce_timer == 0) { + // Finito di riprodursi, imposta il cooldown + r->cooldown_timer = 120; // 2 secondi di pausa + if (r->is_mother) { + r->is_mother = 0; + spawn_rat(r->rat_x, r->rat_y); + } + } + continue; + } + + if (r->rat_x == r->target_x && r->rat_y == r->target_y) { + uint8_t valid_dirs[4]; + uint8_t num_valid = 0; + + if (r->rat_y < MAZE_HEIGHT - 1 && maze[r->rat_y + 1][r->rat_x] == 0) valid_dirs[num_valid++] = 0; + if (r->rat_y > 0 && maze[r->rat_y - 1][r->rat_x] == 0) valid_dirs[num_valid++] = 1; + if (r->rat_x > 0 && maze[r->rat_y][r->rat_x - 1] == 0) valid_dirs[num_valid++] = 2; + if (r->rat_x < MAZE_WIDTH - 1 && maze[r->rat_y][r->rat_x + 1] == 0) valid_dirs[num_valid++] = 3; + + if (num_valid > 0) { + if (num_valid > 1 && r->current_dir != 255) { + uint8_t opp = get_opposite(r->current_dir); + uint8_t filtered[4]; + uint8_t num_filtered = 0; + for (uint8_t j = 0; j < num_valid; j++) { + if (valid_dirs[j] != opp) { + filtered[num_filtered++] = valid_dirs[j]; + } + } + if (num_filtered > 0) r->current_dir = filtered[rand() % num_filtered]; + else r->current_dir = valid_dirs[rand() % num_valid]; + } else { + r->current_dir = valid_dirs[rand() % num_valid]; + } + + if (r->current_dir == 0) r->target_y++; + else if (r->current_dir == 1) r->target_y--; + else if (r->current_dir == 2) r->target_x--; + else if (r->current_dir == 3) r->target_x++; + } + } + + uint8_t target_px = r->target_x * 8; + uint8_t target_py = r->target_y * 8; + + if (do_move) { + if (r->pixel_x < target_px) r->pixel_x++; + else if (r->pixel_x > target_px) r->pixel_x--; + if (r->pixel_y < target_py) r->pixel_y++; + else if (r->pixel_y > target_py) r->pixel_y--; + } + + if (r->pixel_x == target_px && r->pixel_y == target_py) { + r->rat_x = r->target_x; + r->rat_y = r->target_y; + } + + // Aggiorna gli hardware sprite (Meta-Sprite system) + uint8_t base_x = r->pixel_x + 12; + uint8_t base_y = r->pixel_y + 20; + uint8_t s0 = r->sprite_base_idx; + uint8_t s1 = r->sprite_base_idx + 1; + + if (r->current_dir == 0 || r->current_dir == 255) { // Giù + set_sprite_tile(s0, 2); + set_sprite_tile(s1, 3); + set_sprite_prop(s0, 0); + set_sprite_prop(s1, 0); + move_sprite(s0, base_x, base_y - 4); + move_sprite(s1, base_x, base_y + 4); + } else if (r->current_dir == 1) { // Su + set_sprite_tile(s0, 3); + set_sprite_tile(s1, 2); + set_sprite_prop(s0, S_FLIPY); + set_sprite_prop(s1, S_FLIPY); + move_sprite(s0, base_x, base_y - 4); + move_sprite(s1, base_x, base_y + 4); + } else if (r->current_dir == 2) { // Sinistra + set_sprite_tile(s0, 1); + set_sprite_tile(s1, 0); + set_sprite_prop(s0, S_FLIPX); + set_sprite_prop(s1, S_FLIPX); + move_sprite(s0, base_x - 4, base_y); + move_sprite(s1, base_x + 4, base_y); + } else if (r->current_dir == 3) { // Destra + set_sprite_tile(s0, 0); + set_sprite_tile(s1, 1); + set_sprite_prop(s0, 0); + set_sprite_prop(s1, 0); + move_sprite(s0, base_x - 4, base_y); + move_sprite(s1, base_x + 4, base_y); + } } } diff --git a/src/rat.h b/src/rat.h index dab4243..7abbad0 100644 --- a/src/rat.h +++ b/src/rat.h @@ -1,25 +1,11 @@ -/** - * @file rat.h - * @brief Header per la gestione del personaggio "ratto" autonomo. - */ - #ifndef RAT_H #define RAT_H #include -/** - * @brief Inizializza il ratto alle coordinate logiche specificate. - * @param start_x Coordinata X (in tiles) nel labirinto. - * @param start_y Coordinata Y (in tiles) nel labirinto. - */ -void init_rat(uint8_t start_x, uint8_t start_y); +#define MAX_RATS 15 -/** - * @brief Aggiorna la posizione e la logica del ratto. - * - * Da chiamare ogni frame nel game loop principale. - */ -void update_rat(void); +void init_rats(void); +void update_rats(void); #endif