Files
gameboy-hello/src/rat.c
T

325 lines
13 KiB
C

#include "rat.h"
#include "maze.h"
#include "music.h"
#include <gb/gb.h>
#include <rand.h>
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,
// 1: Destra - Metà Destra (Testa/Occhi/Naso) ruotata a partire da Giù
0x00, 0x00, 0xF0, 0xF0, 0xC8, 0x08, 0xD4, 0x1C, 0xD4, 0x1C, 0xC8, 0x08, 0xF0, 0xF0, 0x00, 0x00,
// 2: Giù - Metà Superiore (Coda/Schiena)
0x00, 0x00, 0x18, 0x18, 0x24, 0x3C, 0x3C, 0x3C, 0x3C, 0x24, 0x6E, 0x52, 0x7E, 0x42, 0x76, 0x4A,
// 3: Giù - Metà Inferiore (Occhi/Naso)
0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00
};
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];
uint8_t game_over_flag = 0;
static uint8_t get_opposite(uint8_t dir) {
if (dir == 0) return 1;
if (dir == 1) return 0;
if (dir == 2) return 3;
if (dir == 3) return 2;
return 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
// Posizioni iniziali garantite vuote (coordinate dispari)
uint8_t start_pos[4][2] = {
{1, 1},
{17, 15},
{17, 1},
{1, 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 quattro topi
for (uint8_t i = 0; i < 4; 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 spawn_rat(uint8_t x, uint8_t y, uint8_t initial_dir) {
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;
// Imposta la direzione e target in base all'iniziale (se valida e se possibile muoversi in quella dir)
rats[i].current_dir = initial_dir;
rats[i].target_x = x;
rats[i].target_y = y;
if (initial_dir == 0 && y < MAZE_HEIGHT - 1 && maze[y+1][x] == 0) rats[i].target_y++;
else if (initial_dir == 1 && y > 0 && maze[y-1][x] == 0) rats[i].target_y--;
else if (initial_dir == 2 && x > 0 && maze[y][x-1] == 0) rats[i].target_x--;
else if (initial_dir == 3 && x < MAZE_WIDTH - 1 && maze[y][x+1] == 0) rats[i].target_x++;
else rats[i].current_dir = 255; // Nessuna direzione valida trovata
rats[i].pixel_x = x * 8;
rats[i].pixel_y = y * 8;
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!
uint8_t count = 0;
for (uint8_t j = 0; j < MAX_RATS; j++) {
if (rats[j].active) count++;
}
if (count >= MAX_RATS) {
game_over_flag = 1;
}
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 usando una SPATIAL HASH GRID (O(N))
// Questa tecnica rimpiazza il doppio ciclo O(N^2) da 105 iterazioni.
// Usiamo array pre-allocati per creare "Linked Lists" statiche senza usare malloc!
uint8_t head[16];
uint8_t next_rat[MAX_RATS];
// Inizializza i bucket dell'hash grid a vuoto (255)
for (uint8_t i = 0; i < 16; i++) head[i] = 255;
// Popola la grid: ogni topo calcola il suo hash basato sulle coordinate e si inserisce in testa alla lista
for (uint8_t i = 0; i < MAX_RATS; i++) {
if (!rats[i].active || rats[i].reproduce_timer > 0 || rats[i].cooldown_timer > 0) {
next_rat[i] = 255;
continue;
}
uint8_t h = (rats[i].rat_x ^ rats[i].rat_y) & 15; // Hash velocissimo
next_rat[i] = head[h];
head[h] = i;
}
// Ora per ogni topo controlliamo solo i topi che sono finiti nello STESSO bucket!
for (uint8_t i = 0; i < MAX_RATS; i++) {
if (!rats[i].active || rats[i].reproduce_timer > 0 || rats[i].cooldown_timer > 0) continue;
// Attraversa la linked list partendo dal topo "sotto" di lui nel bucket
uint8_t j = next_rat[i];
while (j != 255) {
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.
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;
}
j = next_rat[j]; // Passa al prossimo topo nello stesso bucket
}
}
// 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, get_opposite(r->current_dir));
}
}
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) {
uint8_t rnd;
if (num_filtered == 1) rnd = 0;
else if (num_filtered == 2) rnd = rand() & 1; // 0 o 1
else {
do { rnd = rand() & 3; } while(rnd >= num_filtered);
}
r->current_dir = filtered[rnd];
}
else {
uint8_t rnd;
if (num_valid == 1) rnd = 0;
else if (num_valid == 2) rnd = rand() & 1;
else {
do { rnd = rand() & 3; } while(rnd >= num_valid);
}
r->current_dir = valid_dirs[rnd];
}
} else {
uint8_t rnd;
if (num_valid == 1) rnd = 0;
else if (num_valid == 2) rnd = rand() & 1;
else {
do { rnd = rand() & 3; } while(rnd >= num_valid);
}
r->current_dir = valid_dirs[rnd];
}
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;
}
// Ottimizzazione: aggiorna tile e flag SOLO quando si cambia direzione.
// Possiamo rilevarlo facilmente: se rat_x == target_x e rat_y == target_y, la direzione
// viene scelta di nuovo (o mantenuta, ma è il momento in cui potrebbe cambiare).
// Tuttavia, per essere super sicuri ed evitare sfarfallii iniziali, creiamo una
// variabile "dirty" implicita, o semplicemente aggiorniamo i tile SOLO
// nel blocco in cui viene assegnata current_dir (poco sopra).
// Visto che non vogliamo riscrivere troppo, e sappiamo che la CPU sta faticando
// con le troppe chiamate a set_sprite_*, spostiamo questa logica!
// Invece di farla in base_x/base_y qui sotto, la lasciamo fissa e ottimizziamo:
// Invece di chiamare set_sprite_tile/prop 30 volte a frame (che è devastante per il GBDK),
// aggiorniamo gli sprite in VRAM *solo* quando do_move è vero (ogni 2 frame)
// e solo calcolando l'offset.
if (do_move || r->current_dir == 255) {
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);
}
}
}
}
void kill_rats_at(uint8_t x, uint8_t y) {
for (uint8_t i = 0; i < MAX_RATS; i++) {
if (rats[i].active) {
// Uccidi se il topo è esattamente sulla cella esplosa
// Oppure se ci stava andando.
if ((rats[i].rat_x == x && rats[i].rat_y == y) ||
(rats[i].target_x == x && rats[i].target_y == y)) {
rats[i].active = 0;
move_sprite(rats[i].sprite_base_idx, 0, 0);
move_sprite(rats[i].sprite_base_idx + 1, 0, 0);
}
}
}
}