diff --git a/README.md b/README.md index c2a7ee8..2344706 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,235 @@ -# MICE! +# 🐀 MICE! -Un progetto per Game Boy, scritto in linguaggio C usando **GBDK-2020**. Nata come semplice generazione procedurale di un labirinto perfetto (Recursive Backtracker), l'applicazione si è evoluta integrando intelligenza artificiale, interattività e un motore audio custom. +Un gioco completo per **Game Boy DMG (1989)**, scritto interamente in C tramite la toolchain [GBDK-2020](https://github.com/gbdk-2020/gbdk-2020). Nato come esperimento di generazione procedurale, si è evoluto in un gioco completo con AI, musica chiptune custom, effetti sonori, schermate di titolo, vittoria e game over. -![Screenshot del Gioco](screenshot.png) +![Schermata Titolo e Gameplay](screenshot.png) -## Funzionalità Aggiunte -- **Intelligenza Artificiale (Rat)**: Un topolino animato (costruito tramite *Meta-Sprite* 16x8 per ottimizzare la memoria) esplora autonomamente e fluidamente i percorsi del labirinto. -- **Combattimento (Bomberman-style)**: Un cursore lampeggiante controllabile tramite D-Pad permette di sganciare bombe (tasto A) con conto alla rovescia ed esplosioni a croce letali per i topi. -- **Toggle Audio**: Premi SELECT in qualsiasi momento per silenziare la colonna sonora e ascoltare in solitaria gli effetti sonori. -- **Tracker Musicale a 4 Canali**: Una sontuosa colonna sonora chiptune generata da un sequencer nativo scritto in C, senza l'uso di engine esterni. Sfrutta l'onda quadra, la Wave RAM customizzata per il basso, e il Noise per le percussioni, ruotando su una traccia in quattro parti da ~35 secondi. +--- + +## Gameplay + +Sei un cacciatore di topi. Il labirinto viene generato proceduralmente ad ogni partita. I topi si muovono autonomamente nei corridoi, si riproducono e ti travolgono se non li elimini in tempo. + +### Controlli + +| Tasto | Funzione | +|-------|----------| +| **D-Pad** | Muove il cursore di mira nel labirinto | +| **A** | Sgancia una **bomba** (esplosione a croce, filo intero corridoio) | +| **B** | Sparo secco con il **fucile a pompa** (cooldown 3 secondi) | +| **START** | Pausa / Riprendi | +| **SELECT** | Toggle musica ON/OFF | + +### Obiettivo + +Elimina **tutti i topi** prima che il timer scada o che ne arrivino troppi. Vinci se il contatore topi raggiunge zero; perdi se il tempo è esaurito o vieni sopraffatto. + +--- ## Struttura del Progetto -Al fine di mantenere un codice ordinato, pulito ed espandibile, i sorgenti sono stati suddivisi: - -- `src/` : Contiene tutto il codice sorgente del gioco. - - `main.c`: Entry point, loop principale, inizializzazione hardware e rendering (scrolling). - - `maze.c` / `maze.h`: Core algoritmico per la generazione del labirinto. - - `rat.c` / `rat.h`: Logica dell'automa e gestione degli sprite hardware per l'animazione del topo. - - `cursor.c` / `cursor.h`: Logica interattiva del selettore del giocatore. - - `music.c` / `music.h`: Mini-tracker musicale, sequencer e definizione dei registri audio. - - `tiles.c` / `tiles.h`: Dichiarazione e definizione della grafica dei tile utilizzati. -- `obj/` : Cartella autogenerata durante il processo di compilazione per immagazzinare i file intermedi (`.o` e metadati). -- `tests/` : Script e utilities di testing. - - `test_pyboy.py`: Esegue l'emulatore PyBoy in modalità headless, salvando la prova del funzionamento in un'immagine temporanea. -- `Makefile` : Sistema di build preconfigurato per GBDK-2020. -- `maze.gb` : La ROM finale giocabile (generata dopo il build). -- `test_audio.gb` / `test_audio.c`: Micro-rom dedicata alla diagnostica dell'hardware sonoro. + +``` +. +├── src/ # Codice sorgente completo del gioco +│ ├── main.c # Entry point, loop principale, hardware init +│ ├── maze.c/h # Generazione procedurale del labirinto (Recursive Backtracker) +│ ├── rat.c/h # Logica AI dei topi, pathfinding, sprite, riproduzione +│ ├── cursor.c/h # Cursore del giocatore, bomba, fucile, input DAS +│ ├── bomb.c/h # Meccanica bomba: ticking, esplosione a croce propagante +│ ├── music.c/h # Mini-tracker musicale 4-canali, SFX, Victory/GameOver jingle +│ ├── tiles.c/h # Tileset grafico principale (muri autotiled, percorsi) +│ ├── rat_bg.c/h # Sfondo schermata Game Over +│ ├── title_bg.c/h # Sfondo schermata Titolo +│ ├── victory_bg.c/h # Sfondo schermata Vittoria (teschio-trofeo) +│ ├── pause_gfx.c/h # Sprite lettere "P-A-U-S-E" +│ ├── numbers_gfx.c/h # Sprite cifre 0-9 per il timer HUD +│ ├── mockup_gfx.c/h # Tileset esteso (autotiling cespugli) +│ └── bomb_gfx.c # Sprite animati della bomba (3 fasi + esplosione) +├── tests/ +│ └── test_pyboy.py # Test headless: avvia la ROM via PyBoy e salva screenshot +├── prepare_title.py # Genera title_bg.c/h dal PNG sorgente +├── prepare_bg.py # Genera rat_bg.c/h (schermata game over) +├── prepare_victory.py # Genera victory_bg.c/h con overlay testuale +├── test_audio.c # ROM diagnostica interattiva per tutti i canali audio +├── Makefile # Build system preconfigurato per GBDK-2020 +├── maze.gb # ROM finale giocabile (32 KB) +└── test_audio.gb # ROM diagnostica audio +``` + +--- ## Come Compilare -Assicurati di aver installato la toolchain **GBDK-2020** nel path corretto (solitamente configurato su `~/.local/gbdk` in base a questo repository). -Per compilare la ROM, basterà lanciare: + +Assicurati di avere la toolchain **GBDK-2020** installata in `~/.local/gbdk`: + ```bash make ``` -## Come Testare -Se vuoi validare la compilazione senza aprire GUI o se sei su un server remoto, puoi lanciare lo script headless: +Questo compila due ROM: +- `maze.gb` — il gioco completo +- `test_audio.gb` — diagnostica interattiva di tutti gli SFX e la musica + +### Testare senza interfaccia grafica (headless) + ```bash python3 tests/test_pyboy.py +# Salva uno screenshot in /tmp/maze_gb.png ``` -Questo genererà un file PNG in locale (`/tmp/maze_gb.png`) per farti visualizzare l'output atteso della ROM. Puoi anche testare le ROM su emulatori diretti da terminale come `pyboy maze.gb`. - -## Architettura e Ottimizzazioni Hardware (Retro-Engineering) -Poiché il processore custom del Game Boy (SM83, simile allo Z80) lavora a soli 4.19 MHz e non è provvisto di hardware dedicato per le moltiplicazioni o le divisioni (FPU o ALU avanzata), il codice sorgente fa un uso intensivo di "trucchi" dell'epoca per garantire i 60 FPS costanti, anche con 15 sprite complessi (Meta-Sprite) a schermo che eseguono pathfinding indipendente: - -1. **Allocazione Memoria in Potenze di 2 (`MAZE_PITCH = 32`)** - In C, per leggere un elemento da un array bidimensionale come `maze[y][x]`, il compilatore esegue un'operazione matematica: `y * LARGHEZZA_RIGA + x`. Nelle prime versioni, una larghezza di 20 richiedeva una lenta routine di moltiplicazione software. Per ovviare al problema, la riga logica in RAM è stata allargata a `32` (una potenza di due). In questo modo il compilatore SDCC risolve la moltiplicazione in un singolo, velocissimo *bit-shift* a sinistra (`y << 5`), azzerando del tutto il carico del processore. - -2. **Divisioni sostituite da Maschere Bitwise (Bitmasks)** - La funzione `rand() % num` usa l'operatore Modulo (`%`), che su un'architettura a 8-bit invoca un disastroso ciclo di sottrazioni ripetute per trovare il resto. Poiché la scelta della direzione richiede valori da 0 a 3, l'operatore modulo è stato rimosso in favore di un `& 3` (Bitwise AND). È istantaneo e produce un numero da 0 a 3 in un singolo colpo di clock. - -3. **Collisioni "Lazy" (Early-Exit Evaluation)** - Piuttosto che testare le sovrapposizioni millimetriche (`pixel_x / pixel_y`) su O(N²) iterazioni (105 combinazioni) per frame, la logica confronta in *short-circuit* soltanto le coordinate grossolane in griglia (`rat_x != rat_y`). Se i topi non si trovano nemmeno sulla stessa mattonella, l'algoritmo ignora istantaneamente tutto il resto. Questa singola riga taglia l'80% delle istruzioni necessarie per i check di collisione. - -4. **DAS (Delayed Auto Shift) per l'Input** - Per rendere fluida la navigazione del cursore sulla griglia, è stata implementata una logica a stati temporizzati ereditata dai classici come Tetris. Invece di far scattare il cursore a ogni micro-pressione o farlo "scivolare" in modo incontrollabile, il codice legge l'input continuo (`joypad()`) e utilizza un timer a due fasi: - - *Initial Delay*: alla prima pressione lo spostamento è istantaneo, dopodiché la logica va in "pausa" per 12 frame (~0.2 secondi). Questo previene i doppi scatti accidentali. - - *Auto-Repeat Delay*: se il giocatore continua a tenere premuto oltre la soglia iniziale, la pausa tra un movimento e l'altro si accorcia a 6 frame, permettendo uno scorrimento automatico e fulmineo lungo l'intero labirinto. - -Queste tecniche mostrano la filosofia del vero **retro-programming**, dove ogni ciclo di CPU conta. + +### Testare con emulatore SDL2 + +```bash +pyboy -w SDL2 -s 3 --sound-volume 100 maze.gb +``` + +### Diagnosticare l'audio + +```bash +pyboy -w SDL2 -s 3 --sound-volume 100 test_audio.gb +``` + +Nella ROM audio, ogni tasto attiva un suono diverso: + +| Tasto | Suono | +|-------|-------| +| ↑ | Esplosione bomba | +| ↓ | Sparo fucile | +| ← | Miccia bomba | +| → | Gemito topo | +| A | Plop (morte topo) | +| B | Fanfara di vittoria | +| START | Tema game over | +| SELECT | Toggle musica | + +--- + +## Architettura e Ottimizzazioni Hardware + +Il processore del Game Boy (Sharp **SM83**, simile allo Z80) gira a soli **4.19 MHz**, senza FPU, senza moltiplicatore hardware, con soli **8 KB di RAM** e **8 KB di VRAM**. Ogni ciclo di CPU conta. + +Di seguito le principali tecniche adottate per garantire i 60 FPS costanti con 10 entità AI indipendenti a schermo. + +--- + +### 1. Accesso a Matrici 2D senza Moltiplicazione — `MAZE_PITCH = 32` + +L'accesso a `maze[y][x]` compila in `base + y * LARGHEZZA + x`. Con una larghezza logica di 19, SDCC emette una **lenta routine di moltiplicazione software** (ciclo di addizioni ripetute, ~20+ cicli di clock). + +**Soluzione**: la riga logica in RAM è stata estesa a `32` (potenza di 2). Il compilatore sostituisce automaticamente `y * 32` con `y << 5` (un singolo bit-shift, **2 cicli**), eliminando completamente il costo. + +```c +// maze.h +#define MAZE_WIDTH 19 // larghezza logica del labirinto +#define MAZE_PITCH 32 // larghezza allocata in RAM (potenza di 2) + +uint8_t maze[MAZE_HEIGHT][MAZE_PITCH]; // accesso: maze[y][x] → base + (y<<5) + x +``` + +--- + +### 2. Modulo Rimosso — Bitwise AND al Posto di `%` + +La funzione `rand() % N` su architettura 8-bit emette una routine di **divisione software** (sottrazioni ripetute, ~60+ cicli). Poiché il pathfinding sceglie tra al massimo 4 direzioni (0–3), il modulo è stato rimpiazzato con un `& 3`: + +```c +// Prima: r = rand() % count; // lento, divisione software +// Dopo: r = rand() & 3; // istantaneo, singolo AND bitwise +``` + +--- + +### 3. Collision Check "Lazy" con Short-Circuit + +La collision detection tra 10 topi richiederebbe 45 coppie da testare a ogni frame. Il controllo è strutturato come segue: + +```c +// Se non sono nemmeno sulla stessa tile, salta tutto il resto +if (rats[i].rat_x != rats[j].rat_x || rats[i].rat_y != rats[j].rat_y) continue; +// Solo qui si fa il check pixel-preciso +``` + +Nella stragrande maggioranza dei frame, la guard condition è falsa per tutte le coppie e il codice di collisione non viene mai raggiunto. + +--- + +### 4. Meta-Sprite 16×8 per i Topi + +Il Game Boy può gestire al massimo **40 sprite hardware** (8×8 px ciascuno), con un limite di **10 sprite per scanline**. Ogni topo è una coppia di sprite 8×8 affiancati (meta-sprite 16×8), per un totale di 20 sprite per 10 topi. Il pool di sprite è: + +| Sprite HW | Uso | +|-----------|-----| +| 0–19 | Corpo dei topi (2 sprite × 10 topi) | +| 20–23 | Pool esplosione bomba (primo gruppo) | +| 24 | Flash sparo fucile | +| 25–28 | Timer HUD (4 cifre) | +| 29–37 | Pool esplosione bomba (secondo gruppo) | +| 38 | Bomba (sprite animato, 3 frame + esplosione) | +| 39 | Cursore del giocatore (bordo lampeggiante) | + +--- + +### 5. Autotiling a 4-bit dei Muri del Labirinto + +I muri del labirinto vengono scelti a runtime in base ai **4 vicini cardinali** (bitmask 4-bit → 16 varianti). Questo permette di avere giunzioni visivamente coerenti (angoli, T, croce) senza allocare dati extra in VRAM, che è un bene rarissimo: ne abbiamo solo 8 KB. + +--- + +### 6. DAS (Delayed Auto Shift) per l'Input + +Il cursore implementa il meccanismo DAS ereditato dai classici dell'epoca (Tetris originale DMG): + +- **Initial delay**: 12 frame (~0.2 s) alla prima pressione — previene i doppi scatti accidentali +- **Auto-repeat**: 6 frame per spostamento successivo — permette traversata rapida del labirinto + +```c +if (cursor_timer == 0) { + moved = 1; + cursor_timer = (first_press) ? 12 : 6; +} +``` + +--- + +### 7. Mini-Tracker Musicale a 4 Canali Nativo + +La musica è generata da un sequencer scritto interamente in C, senza librerie esterne. Ogni frame `update_music()` viene chiamata nel VBlank interrupt: + +- **CH1 (Square 1)**: arpeggio melodico +- **CH2 (Square 2)**: melodia principale +- **CH3 (Wave RAM)**: basso a onda custom (forma d'onda caricata in Wave RAM `0xFF30`) +- **CH4 (Noise)**: percussioni (kick, snare, hi-hat) + +Le note sono codificate come frequenze nei registri `NRx3/NRx4` tramite la formula: `f_reg = 2048 - (131072 / Hz)`. Una tabella di macro `N_C4`, `N_D4`, ecc. precompila i valori corretti. + +Gli SFX (esplosione, fucile, miccia, morte topo) interrompono temporaneamente i canali rilevanti tramite un `sfx_timer` di protezione, per poi restituire il controllo al tracker. + +--- + +### 8. Generazione Asset via Python (Pipeline Offline) + +La VRAM del Game Boy può contenere al massimo **256 tile da 8×8 pixel** (in formato 2bpp, 2 bit per pixel). Tutti gli asset grafici (sfondi del titolo, vittoria, game over) sono convertiti **offline** da PNG a header C tramite script Python dedicati: + +- `prepare_title.py` → `src/title_bg.c/h` +- `prepare_bg.py` → `src/rat_bg.c/h` +- `prepare_victory.py` → `src/victory_bg.c/h` (con overlay testo "VICTORY!") + +Questo mantiene il Makefile semplice e i sorgenti C puliti dai blob binari. + +--- + +## Toolchain e Requisiti + +| Componente | Versione | +|-----------|---------| +| GBDK-2020 | ≥ 4.3.0 | +| lcc (frontend SDCC) | incluso in GBDK | +| Python | ≥ 3.9 (per gli script di asset) | +| Pillow | `pip install pillow` | +| PyBoy (opzionale) | `pip install pyboy` | + +La ROM finale è compatibile con qualsiasi emulatore Game Boy DMG accurato (BGB, Sameboy, Gambatte) e con hardware originale tramite flash cart. + +--- + +*"A game by Matteo, because he was bored."* diff --git a/build_autotiles.py b/build_autotiles.py deleted file mode 100644 index c6c2b01..0000000 --- a/build_autotiles.py +++ /dev/null @@ -1,91 +0,0 @@ -import os - -# 0: Floor -# 1: Light Green -# 2: Dark Green -# 3: Black - -floor_tile = [ - "00000000", - "00010000", - "00100000", - "00000000", - "00000000", - "00000010", - "00001000", - "00000000", -] - -def make_autotile(n, e, s, w): - # base is floor - t = [list("00000000") for _ in range(8)] - - # define boundaries - top = 0 if n else 2 - bottom = 8 if s else 6 - left = 0 if w else 2 - right = 8 if e else 6 - - # fill with dark green - for r in range(top, bottom): - for c in range(left, right): - t[r][c] = "2" - - # Add highlight (1) to the top edge - if not n: - for c in range(left, right): - t[1][c] = "1" - t[2][c] = "1" - - # Add shadow (3) to the bottom edge - if not s: - for c in range(left, right): - t[6][c] = "3" - t[7][c] = "3" - - # Add rounded corners - if not n and not w: - t[1][2] = "0"; t[1][3] = "1"; t[2][2] = "1" - if not n and not e: - t[1][7] = "0"; t[1][6] = "1"; t[1][5] = "1"; t[2][7] = "0"; t[2][6] = "1" - if not s and not w: - t[7][2] = "0"; t[7][3] = "3"; t[6][2] = "3" - if not s and not e: - t[7][7] = "0"; t[7][6] = "3"; t[6][7] = "0"; t[6][6] = "3" - - # Add some texture - if n and s and e and w: - t[3][3] = "1"; t[4][5] = "3" - - return ["".join(row) for row in t] - -def to_gb_format(lines): - bytes_arr = [] - for line in lines: - lo = hi = 0 - for i, char in enumerate(line): - val = int(char) - if val & 1: lo |= (1 << (7 - i)) - if val & 2: hi |= (1 << (7 - i)) - bytes_arr.append(lo) - bytes_arr.append(hi) - return bytes_arr - -tiles = [floor_tile] -for mask in range(16): - n = (mask & 8) != 0 - e = (mask & 4) != 0 - s = (mask & 2) != 0 - w = (mask & 1) != 0 - tiles.append(make_autotile(n, e, s, w)) - -with open('src/tiles.c', 'w') as f: - f.write('#include "tiles.h"\n\n') - f.write(f'const unsigned char TileData[{len(tiles)*16}] = {{\n') - for i, t in enumerate(tiles): - f.write(f' // Tile {i} (Mask {i-1 if i>0 else "Floor"})\n') - b = to_gb_format(t) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - f.write('};\n') - -print("tiles.c generated with autotiles!") diff --git a/build_bush_tiles.py b/build_bush_tiles.py deleted file mode 100644 index 7bfc43e..0000000 --- a/build_bush_tiles.py +++ /dev/null @@ -1,214 +0,0 @@ -import random -import numpy as np -from PIL import Image - -# 0: Floor -# 1: Highlight -# 2: Midtone -# 3: Shadow - -random.seed(1234) # deterministic - -# Base seamless vegetation pattern (8x8) -base_veg = [ - [2, 1, 2, 2, 3, 2, 2, 1], - [1, 2, 2, 1, 2, 2, 3, 2], - [2, 2, 3, 2, 2, 1, 2, 2], - [3, 2, 2, 1, 2, 2, 2, 3], - [2, 1, 2, 2, 3, 2, 2, 2], - [2, 2, 2, 3, 2, 1, 2, 2], - [2, 3, 2, 2, 2, 2, 1, 2], - [1, 2, 2, 1, 2, 3, 2, 2] -] -base_veg = np.array(base_veg, dtype=np.uint8) - -# To avoid repeating exactly the same pattern, we can roll it slightly per mask -# Or we just use it. -walls = [] - -def make_bush(n, e, s, w, mask_idx): - # Start with base vegetation - t = np.roll(np.roll(base_veg, mask_idx % 8, axis=0), (mask_idx // 2) % 8, axis=1).copy() - - # Carve N - if not n: - # Jagged top edge: - # col 0..7 randomly floor or highlight - for c in range(8): - depth = random.choice([0, 1]) - for r in range(depth): - t[r, c] = 0 - t[depth, c] = 1 # highlight on the leaf tip - t[depth+1, c] = 1 # more highlight - - # Carve S - if not s: - for c in range(8): - depth = random.choice([0, 1]) - for r in range(8 - depth, 8): - t[r, c] = 0 - t[7 - depth, c] = 3 # shadow on the bottom leaf - t[6 - depth, c] = 3 - - # Carve W - if not w: - for r in range(8): - depth = random.choice([0, 1]) - for c in range(depth): - t[r, c] = 0 - # Ensure the edge isn't floor, make it a midtone/shadow - if t[r, depth] == 0: t[r, depth] = 2 - - # Carve E - if not e: - for r in range(8): - depth = random.choice([0, 1]) - for c in range(8 - depth, 8): - t[r, c] = 0 - if t[r, 7 - depth] == 0: t[r, 7 - depth] = 2 - - # Fix Corners if isolated - if not n and not w: - t[0, 0] = t[0, 1] = t[1, 0] = 0 - t[1, 1] = 1 - if not n and not e: - t[0, 7] = t[0, 6] = t[1, 7] = 0 - t[1, 6] = 1 - if not s and not w: - t[7, 0] = t[7, 1] = t[6, 0] = 0 - t[6, 1] = 3 - if not s and not e: - t[7, 7] = t[7, 6] = t[6, 7] = 0 - t[6, 6] = 3 - - return t - -for mask in range(16): - n = (mask & 8) != 0 - e = (mask & 4) != 0 - s = (mask & 2) != 0 - w = (mask & 1) != 0 - walls.append(make_bush(n, e, s, w, mask)) - -floors = [ - # 0: Clean floor - np.array([ - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0] - ], dtype=np.uint8), - # 1: Tiny pebble/crack - np.array([ - [0,0,0,0,0,0,0,0], - [0,0,1,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,1], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0] - ], dtype=np.uint8), - # 2: Subtle horizontal vein - np.array([ - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,1,1,0,0,0,0,0], - [0,0,0,1,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0] - ], dtype=np.uint8), - # 3: Diagonal crack - np.array([ - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,1,0], - [0,0,0,0,0,1,0,0], - [0,0,0,0,1,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0] - ], dtype=np.uint8), - # 4: Forked crack - np.array([ - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,1,0,0,0,0,0], - [0,0,0,1,1,0,0,0], - [0,0,0,0,0,1,0,0], - [0,0,0,0,0,0,0,0] - ], dtype=np.uint8), - # 5: Dots / dust - np.array([ - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,1,0,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,1,0,0,0], - [0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0] - ], dtype=np.uint8) -] - -def to_gb_format(tile_np): - bytes_arr = [] - for r in range(8): - lo = hi = 0 - for c in range(8): - val = int(tile_np[r, c]) - if val & 1: lo |= (1 << (7 - c)) - if val & 2: hi |= (1 << (7 - c)) - bytes_arr.append(lo) - bytes_arr.append(hi) - return bytes_arr - -with open('src/tiles.c', 'w') as f: - f.write('#include "tiles.h"\n\n') - f.write(f'const unsigned char TileData[{(6+16)*16}] = {{\n') - - for i, fl in enumerate(floors): - f.write(f' // Floor variant {i}\n') - b = to_gb_format(fl) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - for i, w in enumerate(walls): - f.write(f' // Wall {i}\n') - b = to_gb_format(w) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - f.write('};\n') - -colors = { - 0: (224, 248, 208), - 1: (136, 192, 112), - 2: (52, 104, 86), - 3: (8, 24, 32) -} - -cols = 6 -rows = 4 -img = Image.new('RGB', (cols * 10, rows * 10), (255, 255, 255)) -tiles = floors + walls -for idx, t in enumerate(tiles): - c = idx % cols - r = idx // cols - x_offset = c * 10 + 1 - y_offset = r * 10 + 1 - for ty in range(8): - for tx in range(8): - color = colors[int(t[ty, tx])] - img.putpixel((x_offset + tx, y_offset + ty), color) - -img = img.resize((cols * 10 * 4, rows * 10 * 4), Image.Resampling.NEAREST) -img.save('/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/bush_tiles_preview.png') - -print("tiles.c generated with bush tiles!") diff --git a/build_pause.py b/build_pause.py deleted file mode 100644 index 01f9b16..0000000 --- a/build_pause.py +++ /dev/null @@ -1,80 +0,0 @@ -letters = { - 'P': [ - "00000000", - "01111100", - "01000100", - "01111100", - "01000000", - "01000000", - "01000000", - "00000000" - ], - 'A': [ - "00000000", - "00111000", - "01000100", - "01000100", - "01111100", - "01000100", - "01000100", - "00000000" - ], - 'U': [ - "00000000", - "01000100", - "01000100", - "01000100", - "01000100", - "01000100", - "00111000", - "00000000" - ], - 'S': [ - "00000000", - "00111100", - "01000000", - "00111000", - "00000100", - "01000100", - "00111000", - "00000000" - ], - 'E': [ - "00000000", - "01111100", - "01000000", - "01111000", - "01000000", - "01000000", - "01111100", - "00000000" - ] -} - -def to_gb(tile): - b = [] - for r in tile: - lo = hi = 0 - for i, c in enumerate(r): - # 3 for text (black), 1 for background (white using OBP1) - val = 3 if c == '1' else 1 - if val & 1: lo |= (1 << (7 - i)) - if val & 2: hi |= (1 << (7 - i)) - b.append(lo) - b.append(hi) - return b - -with open('src/pause_gfx.c', 'w') as f: - f.write('#include "pause_gfx.h"\n\n') - f.write('const unsigned char PauseTiles[] = {\n') - for k in ['P', 'A', 'U', 'S', 'E']: - b = to_gb(letters[k]) - f.write(' ' + ','.join(f'0x{x:02X}' for x in b) + ',\n') - f.write('};\n\n') - -with open('src/pause_gfx.h', 'w') as f: - f.write('#ifndef PAUSE_GFX_H\n#define PAUSE_GFX_H\n\n') - f.write('extern const unsigned char PauseTiles[];\n\n') - f.write('#endif\n') - -print("Generated pause_gfx files with solid white backgrounds.") diff --git a/build_pro_tiles.py b/build_pro_tiles.py deleted file mode 100644 index ac8239d..0000000 --- a/build_pro_tiles.py +++ /dev/null @@ -1,330 +0,0 @@ -import os -from PIL import Image - -# Meticulously crafted 8x8 organic tiles matching the mockup style -floors = [ - # 0: Clean floor - [ - "00000000", - "00000000", - "00000000", - "00000000", - "00000000", - "00000000", - "00000000", - "00000000" - ], - # 1: Tiny pebble/crack - [ - "00000000", - "00100000", - "00000000", - "00000000", - "00000000", - "00000001", - "00000000", - "00000000" - ], - # 2: Subtle horizontal vein - [ - "00000000", - "00000000", - "01100000", - "00010000", - "00000000", - "00000000", - "00000000", - "00000000" - ], - # 3: Diagonal crack - [ - "00000000", - "00000000", - "00000000", - "00000010", - "00000100", - "00001000", - "00000000", - "00000000" - ], - # 4: Forked crack - [ - "00000000", - "00000000", - "00000000", - "00000000", - "00100000", - "00011000", - "00000100", - "00000000" - ], - # 5: Dots / dust - [ - "00000000", - "00000000", - "00000000", - "00010000", - "00000000", - "00001000", - "00000000", - "00000000" - ] -] - -walls = [ - # 0: Isolated bush - [ - "00111100", - "01122110", - "11222211", - "12222221", - "12222221", - "03333330", - "00333300", - "00000000" - ], - # 1: W connected (End pointing East) - [ - "11111100", - "11222110", - "22222211", - "22222221", - "22222221", - "33333330", - "33333300", - "00000000" - ], - # 2: S connected (End pointing North) - [ - "00111100", - "01122110", - "11222211", - "12222221", - "12222221", - "12222221", - "12222221", - "12222221" - ], - # 3: S, W connected (Top-Right Corner) - [ - "11111100", - "11222110", - "22222211", - "22222221", - "22222221", - "22222221", - "22222221", - "22222221" - ], - # 4: E connected (End pointing West) - [ - "00111111", - "01122211", - "11222222", - "12222222", - "12222222", - "03333333", - "00333333", - "00000000" - ], - # 5: E, W connected (Horizontal Straight) - [ - "11111111", - "11222211", - "22222222", - "22222222", - "22222222", - "33333333", - "33333333", - "00000000" - ], - # 6: E, S connected (Top-Left Corner) - [ - "00111111", - "01122211", - "11222222", - "12222222", - "12222222", - "12222222", - "12222222", - "12222222" - ], - # 7: E, S, W connected (Top T-Junction) - [ - "11111111", - "11222211", - "22222222", - "22222222", - "22222222", - "22222222", - "22222222", - "22222222" - ], - # 8: N connected (End pointing South) - [ - "12222221", - "12222221", - "12222221", - "12222221", - "12222221", - "03333330", - "00333300", - "00000000" - ], - # 9: N, W connected (Bottom-Right Corner) - [ - "22222221", - "22222221", - "22222221", - "22222221", - "22222221", - "33333330", - "33333300", - "00000000" - ], - # 10: N, S connected (Vertical Straight) - [ - "12222221", - "12222221", - "12222221", - "12222221", - "12222221", - "12222221", - "12222221", - "12222221" - ], - # 11: N, S, W connected (Right T-Junction) - [ - "22222221", - "22222221", - "22222221", - "22222221", - "22222221", - "22222221", - "22222221", - "22222221" - ], - # 12: N, E connected (Bottom-Left Corner) - [ - "12222222", - "12222222", - "12222222", - "12222222", - "12222222", - "03333333", - "00333333", - "00000000" - ], - # 13: N, E, W connected (Bottom T-Junction) - [ - "22222222", - "22222222", - "22222222", - "22222222", - "22222222", - "33333333", - "33333333", - "00000000" - ], - # 14: N, E, S connected (Left T-Junction) - [ - "12222222", - "12222222", - "12222222", - "12222222", - "12222222", - "12222222", - "12222222", - "12222222" - ], - # 15: N, E, S, W connected (Cross Junction) - [ - "22222222", - "22222222", - "22222222", - "22222222", - "22222222", - "22222222", - "22222222", - "22222222" - ] -] - -# Randomize texture inside the solid walls slightly for variation -import random -random.seed(12345) -for i in range(16): - w = [list(r) for r in walls[i]] - for r in range(1, 6): - for c in range(1, 7): - if w[r][c] == '2': - if random.random() < 0.1: - w[r][c] = '1' - elif random.random() < 0.1: - w[r][c] = '3' - walls[i] = ["".join(row) for row in w] - -# Add texture to horizontal/vertical segments -for r in range(1, 6): - for c in range(1, 7): - if walls[5][r][c] == '2' and random.random() < 0.15: walls[5][r][c] = '1' - if walls[10][r][c] == '2' and random.random() < 0.15: walls[10][r][c] = '3' - -tiles = floors + walls - -def to_gb_format(lines): - bytes_arr = [] - for line in lines: - lo = hi = 0 - for i, char in enumerate(line): - val = int(char) - if val & 1: lo |= (1 << (7 - i)) - if val & 2: hi |= (1 << (7 - i)) - bytes_arr.append(lo) - bytes_arr.append(hi) - return bytes_arr - -with open('src/tiles.c', 'w') as f: - f.write('#include "tiles.h"\n\n') - f.write(f'const unsigned char TileData[{len(tiles)*16}] = {{\n') - - # 6 Floors - for i in range(6): - f.write(f' // Floor variant {i}\n') - b = to_gb_format(floors[i]) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - # 16 Walls - for i in range(16): - f.write(f' // Wall {i}\n') - b = to_gb_format(walls[i]) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - f.write('};\n') - -# Also generate a PNG preview so the user can see them -colors = { - 0: (224, 248, 208), - 1: (136, 192, 112), - 2: (52, 104, 86), - 3: (8, 24, 32) -} - -cols = 6 -rows = 4 -img = Image.new('RGB', (cols * 10, rows * 10), (255, 255, 255)) - -for idx, t in enumerate(tiles): - c = idx % cols - r = idx // cols - x_offset = c * 10 + 1 - y_offset = r * 10 + 1 - for ty in range(8): - for tx in range(8): - color = colors[int(t[ty][tx])] - img.putpixel((x_offset + tx, y_offset + ty), color) - -img = img.resize((cols * 10 * 4, rows * 10 * 4), Image.Resampling.NEAREST) -img.save('/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/pro_tiles_preview.png') - -print("tiles.c and pro_tiles_preview.png generated!") diff --git a/convert_mockup.py b/convert_mockup.py deleted file mode 100644 index 2f1b5fd..0000000 --- a/convert_mockup.py +++ /dev/null @@ -1,74 +0,0 @@ -from PIL import Image -import numpy as np - -img_path = '/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/gb_mockup_1781772641040.png' -img = Image.open(img_path).convert('RGB') -img = img.resize((160, 144), Image.Resampling.LANCZOS) - -gray = img.convert('L') -arr = np.array(gray) - -quantized = np.zeros_like(arr, dtype=np.uint8) -quantized[arr >= 192] = 0 -quantized[(arr >= 128) & (arr < 192)] = 1 -quantized[(arr >= 64) & (arr < 128)] = 2 -quantized[arr < 64] = 3 - -tile_map = np.zeros((18, 20), dtype=np.uint16) - -def tile_to_bytes(tile): - res = [] - for row in range(8): - lo = hi = 0 - for col in range(8): - val = tile[row, col] - if val & 1: lo |= (1 << (7 - col)) - if val & 2: hi |= (1 << (7 - col)) - res.append(lo) - res.append(hi) - return tuple(res) - -unique_tiles_map = {} -unique_tiles_list = [] - -for y in range(18): - for x in range(20): - tile = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - t_bytes = tile_to_bytes(tile) - if t_bytes not in unique_tiles_map: - unique_tiles_map[t_bytes] = len(unique_tiles_list) - unique_tiles_list.append(t_bytes) - tile_map[y, x] = unique_tiles_map[t_bytes] - -print(f"Total unique tiles before reduction: {len(unique_tiles_list)}") - -if len(unique_tiles_list) > 256: - print("WARNING: More than 256 unique tiles! Reducing...") - # Very simple reduction: keep first 256. For the rest, map to tile 0. - for y in range(18): - for x in range(20): - if tile_map[y, x] >= 256: - tile_map[y, x] = 0 - unique_tiles_list = unique_tiles_list[:256] - -print(f"Reduced to {len(unique_tiles_list)} unique tiles.") - -with open('src/mockup_gfx.c', 'w') as f: - f.write('#include "mockup_gfx.h"\n\n') - f.write(f'const unsigned char MockupTileData[{len(unique_tiles_list) * 16}] = {{\n') - for t in unique_tiles_list: - f.write(' ' + ', '.join(f"0x{b:02X}" for b in t) + ',\n') - f.write('};\n\n') - - f.write('const unsigned char MockupMapData[360] = {\n') - for y in range(18): - f.write(' ' + ', '.join(f"{tile_map[y, x]}" for x in range(20)) + ',\n') - f.write('};\n') - -with open('src/mockup_gfx.h', 'w') as f: - f.write('#ifndef MOCKUP_GFX_H\n#define MOCKUP_GFX_H\n\n') - f.write('extern const unsigned char MockupTileData[];\n') - f.write('extern const unsigned char MockupMapData[];\n') - f.write('#endif\n') - -print("Mockup C files generated.") diff --git a/extract_and_compare.py b/extract_and_compare.py deleted file mode 100644 index 1b27efd..0000000 --- a/extract_and_compare.py +++ /dev/null @@ -1,134 +0,0 @@ -from PIL import Image, ImageDraw -import numpy as np - -# 1. GENERATE MY TILES -floor_tile = [ - "00000000", "00010000", "00100000", "00000000", - "00000000", "00000010", "00001000", "00000000", -] - -def make_autotile(n, e, s, w): - t = [list("00000000") for _ in range(8)] - top = 0 if n else 2 - bottom = 8 if s else 6 - left = 0 if w else 2 - right = 8 if e else 6 - for r in range(top, bottom): - for c in range(left, right): t[r][c] = "2" - if not n: - for c in range(left, right): t[1][c] = "1"; t[2][c] = "1" - if not s: - for c in range(left, right): t[6][c] = "3"; t[7][c] = "3" - if not n and not w: t[1][2] = "0"; t[1][3] = "1"; t[2][2] = "1" - if not n and not e: t[1][7] = "0"; t[1][6] = "1"; t[1][5] = "1"; t[2][7] = "0"; t[2][6] = "1" - if not s and not w: t[7][2] = "0"; t[7][3] = "3"; t[6][2] = "3" - if not s and not e: t[7][7] = "0"; t[7][6] = "3"; t[6][7] = "0"; t[6][6] = "3" - if n and s and e and w: t[3][3] = "1"; t[4][5] = "3" - return ["".join(row) for row in t] - -my_tiles = [floor_tile] -for mask in range(16): - n = (mask & 8) != 0; e = (mask & 4) != 0; s = (mask & 2) != 0; w = (mask & 1) != 0 - my_tiles.append(make_autotile(n, e, s, w)) - -# 2. EXTRACT MOCKUP TILES -img_path = '/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/test_mockup_clean.png' -img = Image.open(img_path).convert('RGB') -img = img.resize((160, 144), Image.Resampling.LANCZOS) -gray = img.convert('L') -arr = np.array(gray) -quantized = np.zeros_like(arr, dtype=np.uint8) -quantized[arr >= 192] = 0 -quantized[(arr >= 128) & (arr < 192)] = 1 -quantized[(arr >= 64) & (arr < 128)] = 2 -quantized[arr < 64] = 3 - -is_wall = np.zeros((18, 20), dtype=bool) -for y in range(18): - for x in range(20): - tile = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - # A tile is a wall if it has enough dark pixels (> 50% non-zero) - if np.sum(tile > 0) > 32: - is_wall[y, x] = True - -# We shouldn't count the UI frame at the bottom (y >= 16) and border -# as normal walls for extraction, or they might mess up the neighbors. -for y in range(18): - is_wall[y, 0] = False - is_wall[y, 19] = False -for x in range(20): - is_wall[0, x] = False - is_wall[16, x] = False - is_wall[17, x] = False - -mockup_extracted = {} -# find floor -for y in range(2, 15): - for x in range(2, 18): - if not is_wall[y, x]: - mockup_extracted['floor'] = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - break - if 'floor' in mockup_extracted: break - -for y in range(1, 16): - for x in range(1, 19): - if is_wall[y, x]: - n = is_wall[y-1, x] - s = is_wall[y+1, x] - e = is_wall[y, x+1] - w = is_wall[y, x-1] - mask = (8 if n else 0) | (4 if e else 0) | (2 if s else 0) | (1 if w else 0) - if mask not in mockup_extracted: - mockup_extracted[mask] = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - -# 3. RENDER THE COMPARISON -colors = { - 0: (224, 248, 208), - 1: (136, 192, 112), - 2: (52, 104, 86), - 3: (8, 24, 32) -} - -cols = 4 -rows = 5 # 17 pairs: row 0 is floor, then 16 masks -cell_w = 20 # 8 for my tile + 2 padding + 8 for mockup + 2 padding -cell_h = 10 - -out_img = Image.new('RGB', (cols * cell_w, rows * cell_h), (255, 255, 255)) - -def draw_my_tile(t_idx, x_off, y_off): - t = my_tiles[t_idx] - for ty in range(8): - for tx in range(8): - color = colors[int(t[ty][tx])] - out_img.putpixel((x_off + tx, y_off + ty), color) - -def draw_mockup_tile(m_idx, x_off, y_off): - key = 'floor' if m_idx == 0 else (m_idx - 1) - if key in mockup_extracted: - t = mockup_extracted[key] - for ty in range(8): - for tx in range(8): - color = colors[int(t[ty, tx])] - out_img.putpixel((x_off + tx, y_off + ty), color) - else: - # draw a red cross if missing - for ty in range(8): - for tx in range(8): - if ty == tx or ty == 7 - tx: - out_img.putpixel((x_off + tx, y_off + ty), (255, 0, 0)) - else: - out_img.putpixel((x_off + tx, y_off + ty), (200, 200, 200)) - -for idx in range(17): - c = idx % cols - r = idx // cols - x_offset = c * cell_w - y_offset = r * cell_h - - draw_my_tile(idx, x_offset + 1, y_offset + 1) - draw_mockup_tile(idx, x_offset + 11, y_offset + 1) - -out_img = out_img.resize((cols * cell_w * 4, rows * cell_h * 4), Image.Resampling.NEAREST) -out_img.save('/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/comparison_preview.png') -print("Generated comparison_preview.png") diff --git a/extract_to_tiles_c.py b/extract_to_tiles_c.py deleted file mode 100644 index 724440e..0000000 --- a/extract_to_tiles_c.py +++ /dev/null @@ -1,120 +0,0 @@ -import os -from PIL import Image -import numpy as np - -img_path = '/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/test_mockup_clean.png' -img = Image.open(img_path).convert('RGB') -img = img.resize((160, 144), Image.Resampling.LANCZOS) -gray = img.convert('L') -arr = np.array(gray) - -quantized = np.zeros_like(arr, dtype=np.uint8) -quantized[arr >= 192] = 0 -quantized[(arr >= 128) & (arr < 192)] = 1 -quantized[(arr >= 64) & (arr < 128)] = 2 -quantized[arr < 64] = 3 - -is_wall = np.zeros((18, 20), dtype=bool) -for y in range(18): - for x in range(20): - tile = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - if np.sum(tile > 0) > 32: - is_wall[y, x] = True - -for y in range(18): - is_wall[y, 0] = False - is_wall[y, 19] = False -for x in range(20): - is_wall[0, x] = False - is_wall[16, x] = False - is_wall[17, x] = False - -mockup_extracted = {} - -# We'll extract 6 floor tiles to give variety! -floors = [] -for y in range(2, 15): - for x in range(2, 18): - if not is_wall[y, x]: - floors.append(quantized[y*8:(y+1)*8, x*8:(x+1)*8]) - if len(floors) == 6: - break - if len(floors) == 6: break - -# If we couldn't find 6, just pad -while len(floors) < 6: - floors.append(np.zeros((8, 8), dtype=np.uint8)) - -for y in range(1, 16): - for x in range(1, 19): - if is_wall[y, x]: - n = is_wall[y-1, x] - s = is_wall[y+1, x] - e = is_wall[y, x+1] - w = is_wall[y, x-1] - mask = (8 if n else 0) | (4 if e else 0) | (2 if s else 0) | (1 if w else 0) - if mask not in mockup_extracted: - mockup_extracted[mask] = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - -# Ensure all 16 masks exist -# If a mask is missing, try to synthesize it by rotating/flipping or just defaulting to mask 15 -base_wall = mockup_extracted.get(15, np.full((8,8), 2, dtype=np.uint8)) - -def patch_mask(mask): - if mask in mockup_extracted: - return mockup_extracted[mask] - - # Very simple synthesis using the base wall 15, and floor tiles to cut the edges - t = np.copy(base_wall) - f = floors[0] - n = (mask & 8) != 0 - e = (mask & 4) != 0 - s = (mask & 2) != 0 - w = (mask & 1) != 0 - if not n: - t[0,:] = f[0,:] - t[1,:] = 1 # highlight - if not s: - t[6,:] = 3 - t[7,:] = f[7,:] - if not w: - t[:,0] = f[:,0] - t[:,1] = 2 - if not e: - t[:,7] = f[:,7] - t[:,6] = 2 - return t - -walls = [] -for mask in range(16): - walls.append(patch_mask(mask)) - -def to_gb_format(tile_np): - bytes_arr = [] - for r in range(8): - lo = hi = 0 - for c in range(8): - val = int(tile_np[r, c]) - if val & 1: lo |= (1 << (7 - c)) - if val & 2: hi |= (1 << (7 - c)) - bytes_arr.append(lo) - bytes_arr.append(hi) - return bytes_arr - -with open('src/tiles.c', 'w') as f: - f.write('#include "tiles.h"\n\n') - f.write(f'const unsigned char TileData[{(6+16)*16}] = {{\n') - - for i, fl in enumerate(floors): - f.write(f' // Floor variant {i}\n') - b = to_gb_format(fl) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - for i, w in enumerate(walls): - f.write(f' // Wall {i}\n') - b = to_gb_format(w) - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - f.write('};\n') - -print("tiles.c generated directly from mockup!") diff --git a/fix_mockup.py b/fix_mockup.py deleted file mode 100644 index 0b16387..0000000 --- a/fix_mockup.py +++ /dev/null @@ -1,120 +0,0 @@ -from PIL import Image -import numpy as np - -img_path = '/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/gb_mockup_1781772641040.png' -img = Image.open(img_path).convert('RGB') -img = img.resize((160, 144), Image.Resampling.LANCZOS) - -gray = img.convert('L') -arr = np.array(gray) - -quantized = np.zeros_like(arr, dtype=np.uint8) -quantized[arr >= 192] = 0 -quantized[(arr >= 128) & (arr < 192)] = 1 -quantized[(arr >= 64) & (arr < 128)] = 2 -quantized[arr < 64] = 3 - -# Patch out rats and bombs with floor color (0) or hedge color depending on position -def patch(x1, x2, y1, y2, color=0): - quantized[y1:y2, x1:x2] = color - -# Rat 1 (left) -patch(8, 24, 76, 92, 0) -# Rat 2 (top mid) -patch(52, 68, 44, 56, 0) -# Rat 3 (right mid) -patch(108, 124, 60, 72, 0) -# Rat 4 (bottom right) -patch(124, 140, 88, 100, 0) -# Bomb -patch(106, 118, 104, 116, 0) -# UI icons -patch(120, 140, 0, 10, 0) # Bomb/Rat UI - -tile_map = np.zeros((18, 20), dtype=np.uint16) - -def tile_to_bytes(tile): - res = [] - for row in range(8): - lo = hi = 0 - for col in range(8): - val = tile[row, col] - if val & 1: lo |= (1 << (7 - col)) - if val & 2: hi |= (1 << (7 - col)) - res.append(lo) - res.append(hi) - return tuple(res) - -unique_tiles_map = {} -unique_tiles_list = [] - -for y in range(18): - for x in range(20): - tile = quantized[y*8:(y+1)*8, x*8:(x+1)*8] - t_bytes = tile_to_bytes(tile) - if t_bytes not in unique_tiles_map: - unique_tiles_map[t_bytes] = len(unique_tiles_list) - unique_tiles_list.append(t_bytes) - tile_map[y, x] = unique_tiles_map[t_bytes] - -print(f"Total unique tiles before reduction: {len(unique_tiles_list)}") - -def tile_diff(t1, t2): - return sum(bin(a^b).count('1') for a, b in zip(t1, t2)) - -if len(unique_tiles_list) > 256: - print("Reducing tiles...") - - # Precompute diffs - diffs = {} - for i in range(len(unique_tiles_list)): - for j in range(i+1, len(unique_tiles_list)): - diffs[(i, j)] = tile_diff(unique_tiles_list[i], unique_tiles_list[j]) - - while len(unique_tiles_list) > 256: - # Find best pair - best_pair = min(diffs, key=diffs.get) - i, j = best_pair - - # Merge j into i - for r in range(18): - for c in range(20): - if tile_map[r, c] == j: - tile_map[r, c] = i - elif tile_map[r, c] > j: - tile_map[r, c] -= 1 - - unique_tiles_list.pop(j) - - # Recompute diffs dict (remove keys with j, shift > j) - new_diffs = {} - for k, v in diffs.items(): - ki, kj = k - if ki == j or kj == j: continue - if ki > j: ki -= 1 - if kj > j: kj -= 1 - new_diffs[(ki, kj)] = v - - diffs = new_diffs - -print(f"Reduced to {len(unique_tiles_list)} unique tiles.") - -with open('src/mockup_gfx.c', 'w') as f: - f.write('#include "mockup_gfx.h"\n\n') - f.write(f'const unsigned char MockupTileData[{len(unique_tiles_list) * 16}] = {{\n') - for t in unique_tiles_list: - f.write(' ' + ', '.join(f"0x{b:02X}" for b in t) + ',\n') - f.write('};\n\n') - - f.write('const unsigned char MockupMapData[360] = {\n') - for y in range(18): - f.write(' ' + ', '.join(f"{tile_map[y, x]}" for x in range(20)) + ',\n') - f.write('};\n') - -with open('src/mockup_gfx.h', 'w') as f: - f.write('#ifndef MOCKUP_GFX_H\n#define MOCKUP_GFX_H\n\n') - f.write('extern const unsigned char MockupTileData[];\n') - f.write('extern const unsigned char MockupMapData[];\n') - f.write('#endif\n') - -print("Mockup C files generated.") diff --git a/make_bomb_sprites.py b/make_bomb_sprites.py deleted file mode 100644 index f62ce0c..0000000 --- a/make_bomb_sprites.py +++ /dev/null @@ -1,80 +0,0 @@ -# Bomb and Explosion Sprites -sprites_ascii = { - "bomb_3": [ - " ## ", - " #### ", - " ###### ", - " ###### ", - " ###### ", - " ###### ", - " #### ", - " " - ], - "bomb_2": [ - " ", - " ## ", - " #### ", - " ###### ", - " ###### ", - " #### ", - " ", - " " - ], - "bomb_1": [ - " ", - " ", - " ## ", - " #### ", - " #### ", - " ## ", - " ", - " " - ], - "exp_center": [ - "## ##", - "# # # #", - " #### ", - " ###### ", - " ###### ", - " #### ", - "# # # #", - "## ##" - ], - "exp_horiz": [ - " ", - " ", - "########", - " ###### ", - " ###### ", - "########", - " ", - " " - ], - "exp_vert": [ - " #### ", - " #### ", - " ###### ", - " ###### ", - " ###### ", - " ###### ", - " #### ", - " #### " - ] -} - -print("const unsigned char BombSpriteData[] = {") -for name, data in sprites_ascii.items(): - print(f" // {name}") - bytes_arr = [] - for row in data: - lb = 0 - hb = 0 - for x, char in enumerate(row): - if char == '#': - lb |= (1 << (7 - x)) - hb |= (1 << (7 - x)) - bytes_arr.append(lb) - bytes_arr.append(hb) - hex_str = ", ".join([f"0x{b:02X}" for b in bytes_arr]) - print(f" {hex_str},") -print("};") diff --git a/make_mockup_tiles.py b/make_mockup_tiles.py deleted file mode 100644 index 8c340d7..0000000 --- a/make_mockup_tiles.py +++ /dev/null @@ -1,147 +0,0 @@ -import os - -tiles = { - "floor": [ - "00000000", - "00010000", - "00100000", - "00000000", - "00000001", - "00000010", - "00000000", - "00001000", - ], - "hedge": [ - "12121121", - "22322322", - "12112112", - "23223223", - "11212112", - "22322322", - "12112121", - "23223223", - ], - "hedge_top": [ - "00000000", - "01111111", - "12121121", - "22322322", - "11212112", - "23223223", - "12112121", - "22322322", - ], - "frame_tl": [ - "33333333", - "31111111", - "31222222", - "31200000", - "31200000", - "31200000", - "31200000", - "31200000", - ], - "frame_tr": [ - "33333333", - "11111113", - "22222213", - "00000213", - "00000213", - "00000213", - "00000213", - "00000213", - ], - "frame_bl": [ - "31200000", - "31200000", - "31200000", - "31200000", - "31200000", - "31222222", - "31111111", - "33333333", - ], - "frame_br": [ - "00000213", - "00000213", - "00000213", - "00000213", - "00000213", - "22222213", - "11111113", - "33333333", - ], - "frame_t": [ - "33333333", - "11111111", - "22222222", - "00000000", - "00000000", - "00000000", - "00000000", - "00000000", - ], - "frame_b": [ - "00000000", - "00000000", - "00000000", - "00000000", - "00000000", - "22222222", - "11111111", - "33333333", - ], - "frame_l": [ - "31200000", - "31200000", - "31200000", - "31200000", - "31200000", - "31200000", - "31200000", - "31200000", - ], - "frame_r": [ - "00000213", - "00000213", - "00000213", - "00000213", - "00000213", - "00000213", - "00000213", - "00000213", - ], -} - -def to_gb_format(lines): - bytes_arr = [] - for line in lines: - lo = 0 - hi = 0 - for i, char in enumerate(line): - val = int(char) - if val & 1: - lo |= (1 << (7 - i)) - if val & 2: - hi |= (1 << (7 - i)) - bytes_arr.append(lo) - bytes_arr.append(hi) - return bytes_arr - -with open("src/mockup_gfx.c", "w") as f: - f.write('#include "mockup_gfx.h"\n\n') - f.write('const unsigned char MockupTileData[] = {\n') - - for name, art in tiles.items(): - b = to_gb_format(art) - f.write(' // ' + name + '\n') - f.write(' ' + ', '.join(f"0x{val:02X}" for val in b) + ',\n') - - f.write('};\n') - -with open("src/mockup_gfx.h", "w") as f: - f.write('#ifndef MOCKUP_GFX_H\n#define MOCKUP_GFX_H\n\n') - f.write('extern const unsigned char MockupTileData[];\n') - f.write('#endif\n') - -print("Generato mockup_gfx.c e .h") diff --git a/make_rat.py b/make_rat.py deleted file mode 100644 index d85dd8f..0000000 --- a/make_rat.py +++ /dev/null @@ -1,81 +0,0 @@ -import sys - -rat_art = [ -" ###### ###### ", -" ######## ######## ", -"########## ##########", -" ########## ########## ", -" ######## ######## ", -" ###### .................. ###### ", -" .................... ", -" ...................... ", -" ........................ ", -" ....++++..........++++.... ", -" ....++++++........++++++.... ", -" ...++++++++......++++++++... ", -" ...++++++++++....++++++++++... ", -" ...+++####+++....+++####+++... ", -" ...++######++....++######++... ", -" ...++######++....++######++... ", -" ...+++####+++....+++####+++... ", -" ...++++++++......++++++++... ", -" ...++++++++......++++++++... ", -" ...++++++........++++++... ", -" .......................... ", -" ........................ ", -" .....++++++++++++..... ", -" ...+############+... ", -" ..+##++####++##+.. ", -" ..+##++####++##+.. ", -" .+############+. ", -" .++++++++++++. ", -" ............ ", -" .......... ", -" ........ ", -" ...... " -] - -# 40x32 pixels = 5x4 tiles -WIDTH_TILES = len(rat_art[0]) // 8 -HEIGHT_TILES = len(rat_art) // 8 - -def char_to_color(c): - if c == ' ': return 0 - if c == '.': return 1 - if c == '+': return 2 - if c == '#': return 3 - return 0 - -tiles = [] -for ty in range(HEIGHT_TILES): - for tx in range(WIDTH_TILES): - tile_data = [] - for y in range(8): - row_str = rat_art[ty*8 + y][tx*8 : tx*8 + 8] - low_byte = 0 - high_byte = 0 - for x in range(8): - color = char_to_color(row_str[x]) - if color & 1: - low_byte |= (1 << (7 - x)) - if color & 2: - high_byte |= (1 << (7 - x)) - tile_data.append(low_byte) - tile_data.append(high_byte) - tiles.append(tile_data) - -with open('src/game_over_face.h', 'w') as f: - f.write("#ifndef GAME_OVER_FACE_H\n") - f.write("#define GAME_OVER_FACE_H\n\n") - f.write("#include \n\n") - f.write(f"extern const uint8_t game_over_face_tiles[{len(tiles) * 16}];\n\n") - f.write("#endif\n") - -with open('src/game_over_face.c', 'w') as f: - f.write("#include \"game_over_face.h\"\n\n") - f.write(f"const uint8_t game_over_face_tiles[{len(tiles) * 16}] = {{\n") - for tile in tiles: - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile]) + ",\n") - f.write("};\n") - -print("Generated src/game_over_face.h and src/game_over_face.c") diff --git a/prepare_bg.py b/prepare_bg.py deleted file mode 100644 index 99757bb..0000000 --- a/prepare_bg.py +++ /dev/null @@ -1,169 +0,0 @@ -import sys -from PIL import Image -import numpy as np -import collections - -img_path = '/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/rat_king_boss_1781730936484.png' -try: - img = Image.open(img_path).convert('L') -except Exception as e: - print(f"Error opening image: {e}") - sys.exit(1) - -w, h = img.size -target_ratio = 160 / 144 -img_ratio = w / h -if img_ratio > target_ratio: - new_w = int(h * target_ratio) - left = (w - new_w) // 2 - img = img.crop((left, 0, left + new_w, h)) -else: - new_h = int(w / target_ratio) - top = (h - new_h) // 2 - img = img.crop((0, top, w, top + new_h)) - -img = img.resize((160, 144), Image.Resampling.LANCZOS) -np_img = np.array(img) -palette_img = np.zeros_like(np_img) -palette_img[np_img < 64] = 3 -palette_img[(np_img >= 64) & (np_img < 128)] = 2 -palette_img[(np_img >= 128) & (np_img < 192)] = 1 -palette_img[np_img >= 192] = 0 - -# Draw GAME OVER text directly onto the pixel data -font = { - 'G': [" ### ", "# #", "# ", "# ###", "# #", "# #", " ### "], - 'A': [" ### ", "# #", "# #", "#####", "# #", "# #", "# #"], - 'M': ["# #", "## ##", "# # #", "# #", "# #", "# #", "# #"], - 'E': ["#####", "# ", "# ", "#### ", "# ", "# ", "#####"], - 'O': [" ### ", "# #", "# #", "# #", "# #", "# #", " ### "], - 'V': ["# #", "# #", "# #", "# #", "# #", " # # ", " # "], - 'R': ["#### ", "# #", "# #", "#### ", "# # ", "# # ", "# #"], - '!': [" # ", " # ", " # ", " # ", " ", " # ", " # "], - ' ': [" ", " ", " ", " ", " ", " ", " "] -} - -text_str = "GAME OVER!" -scale = 2 -char_w = 5 * scale -char_h = 7 * scale -spacing = 1 * scale -start_x = (160 - len(text_str) * (char_w + spacing)) // 2 -start_y = 144 - char_h - 4 - -# Keep track of which pixels are text -is_text_mask = np.zeros_like(palette_img, dtype=bool) - -box_pad = 2 -for y in range(start_y - box_pad, start_y + char_h + box_pad): - for x in range(start_x - box_pad, start_x + len(text_str) * (char_w + spacing) + box_pad): - if 0 <= y < 144 and 0 <= x < 160: - palette_img[y, x] = 3 # Black background - is_text_mask[y, x] = True - -for i, char in enumerate(text_str): - if char in font: - glyph = font[char] - for row in range(7): - for col in range(5): - if glyph[row][col] == '#': - for sy in range(scale): - for sx in range(scale): - py = start_y + row * scale + sy - px = start_x + i * (char_w + spacing) + col * scale + sx - if 0 <= py < 144 and 0 <= px < 160: - palette_img[py, px] = 0 # White text - is_text_mask[py, px] = True - -tiles = [] -is_text_tile = [] -for y in range(0, 144, 8): - for x in range(0, 160, 8): - tile = palette_img[y:y+8, x:x+8] - t_tuple = tuple(map(tuple, tile)) - tiles.append(t_tuple) - is_text_tile.append(np.any(is_text_mask[y:y+8, x:x+8])) - -unique_tiles = list(collections.OrderedDict.fromkeys(tiles)) - -if len(unique_tiles) > 256: - print("WARNING: More than 256 unique tiles! Reducing...") - - # Priority 1: Text tiles - text_unique = list(collections.OrderedDict.fromkeys([tiles[i] for i in range(len(tiles)) if is_text_tile[i]])) - - # Priority 2: Most frequent tiles - counts = collections.Counter(tiles) - for t in text_unique: - del counts[t] # already added - - most_common = [t for t, c in counts.most_common(256 - len(text_unique))] - - reduced_unique = text_unique + most_common - - for i in range(len(tiles)): - if tiles[i] not in reduced_unique: - t_arr = np.array(tiles[i]) - best_match = 0 - best_dist = float('inf') - for j, ru in enumerate(reduced_unique): - dist = np.sum((t_arr - np.array(ru))**2) - if dist < best_dist: - best_dist = dist - best_match = j - tiles[i] = reduced_unique[best_match] - unique_tiles = reduced_unique - print(f"Reduced to {len(unique_tiles)} unique tiles.") - -# Reconstruct image to check visual quality -preview_img = np.zeros((144, 160), dtype=np.uint8) -for i, t in enumerate(tiles): - y = (i // 20) * 8 - x = (i % 20) * 8 - preview_img[y:y+8, x:x+8] = np.array(t) - -# Save preview as image (colors 0=White, 1=LightGray, 2=DarkGray, 3=Black) -color_map = {0: 255, 1: 170, 2: 85, 3: 0} -preview_rgb = np.zeros((144, 160, 3), dtype=np.uint8) -for y in range(144): - for x in range(160): - c = color_map[preview_img[y, x]] - preview_rgb[y, x] = [c, c, c] - -Image.fromarray(preview_rgb).save('/tmp/preview_gameover.png') -print("Saved preview to /tmp/preview_gameover.png") - -tile_map = [unique_tiles.index(t) for t in tiles] - -c_tiles = [] -for tile in unique_tiles: - tile_data = [] - for row in tile: - low_byte = 0 - high_byte = 0 - for x, color in enumerate(row): - if color & 1: - low_byte |= (1 << (7 - x)) - if color & 2: - high_byte |= (1 << (7 - x)) - tile_data.append(low_byte) - tile_data.append(high_byte) - c_tiles.append(tile_data) - -with open('src/rat_bg.h', 'w') as f: - f.write("#ifndef RAT_BG_H\n#define RAT_BG_H\n\n#include \n\n") - f.write(f"extern const uint8_t rat_bg_tiles[{len(c_tiles) * 16}];\n") - f.write(f"extern const uint8_t rat_bg_map[{len(tile_map)}];\n\n#endif\n") - -with open('src/rat_bg.c', 'w') as f: - f.write("#include \"rat_bg.h\"\n\n") - f.write(f"const uint8_t rat_bg_tiles[{len(c_tiles) * 16}] = {{\n") - for tile in c_tiles: - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile]) + ",\n") - f.write("};\n\n") - f.write(f"const uint8_t rat_bg_map[{len(tile_map)}] = {{\n") - for i in range(0, len(tile_map), 20): - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile_map[i:i+20]]) + ",\n") - f.write("};\n") - -print("Generated src/rat_bg.h and src/rat_bg.c") diff --git a/prepare_title.py b/prepare_title.py deleted file mode 100644 index 7d9dc14..0000000 --- a/prepare_title.py +++ /dev/null @@ -1,188 +0,0 @@ -import sys -from PIL import Image -import numpy as np -import collections - -img_path = '/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/rat_bomb_escape_1781732127266.png' -try: - img = Image.open(img_path).convert('L') -except Exception as e: - print(f"Error opening image: {e}") - sys.exit(1) - -w, h = img.size -target_ratio = 160 / 144 -img_ratio = w / h -if img_ratio > target_ratio: - new_w = int(h * target_ratio) - left = (w - new_w) // 2 - img = img.crop((left, 0, left + new_w, h)) -else: - new_h = int(w / target_ratio) - top = (h - new_h) // 2 - img = img.crop((0, top, w, top + new_h)) - -img = img.resize((160, 144), Image.Resampling.LANCZOS) -np_img = np.array(img) -palette_img = np.zeros_like(np_img) -palette_img[np_img < 64] = 3 -palette_img[(np_img >= 64) & (np_img < 128)] = 2 -palette_img[(np_img >= 128) & (np_img < 192)] = 1 -palette_img[np_img >= 192] = 0 - -font = { - 'A': [" ### ", "# #", "# #", "#####", "# #", "# #", "# #"], - 'B': ["#### ", "# #", "# #", "#### ", "# #", "# #", "#### "], - 'C': [" ### ", "# #", "# ", "# ", "# ", "# #", " ### "], - 'D': ["#### ", "# #", "# #", "# #", "# #", "# #", "#### "], - 'E': ["#####", "# ", "# ", "#### ", "# ", "# ", "#####"], - 'F': ["#####", "# ", "# ", "#### ", "# ", "# ", "# "], - 'G': [" ### ", "# #", "# ", "# ###", "# #", "# #", " ### "], - 'H': ["# #", "# #", "# #", "#####", "# #", "# #", "# #"], - 'I': [" ### ", " # ", " # ", " # ", " # ", " # ", " ### "], - 'M': ["# #", "## ##", "# # #", "# #", "# #", "# #", "# #"], - 'Z': ["#####", " #", " # ", " # ", " # ", "# ", "#####"], - 'P': ["#### ", "# #", "# #", "#### ", "# ", "# ", "# "], - 'R': ["#### ", "# #", "# #", "#### ", "# # ", "# # ", "# #"], - 'O': [" ### ", "# #", "# #", "# #", "# #", "# #", " ### "], - 'W': ["# #", "# #", "# #", "# # #", "## ##", "# #", "# #"], - 'S': [" ### ", "# #", "# ", " ### ", " #", "# #", " ### "], - 'Y': ["# #", "# #", " # # ", " # ", " # ", " # ", " # "], - 'T': ["#####", " # ", " # ", " # ", " # ", " # ", " # "], - 'U': ["# #", "# #", "# #", "# #", "# #", "# #", " ### "], - 'N': ["# #", "## #", "# # #", "# ##", "# #", "# #", "# #"], - 'V': ["# #", "# #", "# #", "# #", "# #", " # # ", " # "], - 'L': ["# ", "# ", "# ", "# ", "# ", "# ", "#####"], - '!': [" # ", " # ", " # ", " # ", " ", " # ", " # "], - ' ': [" ", " ", " ", " ", " ", " ", " "] -} - -is_text_mask = np.zeros_like(palette_img, dtype=bool) - -def draw_text(text, start_y_tiles, scale=1): - tile_w = 8 * scale - tile_h = 8 * scale - start_x_tiles = (20 - len(text) * scale) // 2 - - start_y = start_y_tiles * 8 - start_x = start_x_tiles * 8 - - # Draw black box perfectly aligned to tile grid - for y in range(start_y, start_y + tile_h): - for x in range(start_x, start_x + len(text) * tile_w): - if 0 <= y < 144 and 0 <= x < 160: - palette_img[y, x] = 3 - is_text_mask[y, x] = True - - for i, char in enumerate(text): - if char in font: - glyph = font[char] - # Center the 5x7 glyph inside the 8x8 logic - offset_x = (8 - 5) // 2 - offset_y = (8 - 7) // 2 - - for row in range(7): - for col in range(5): - if glyph[row][col] == '#': - for sy in range(scale): - for sx in range(scale): - py = start_y + (row + offset_y) * scale + sy - px = start_x + i * tile_w + (col + offset_x) * scale + sx - if 0 <= py < 144 and 0 <= px < 160: - palette_img[py, px] = 0 - is_text_mask[py, px] = True - -# Align to 8x8 grid strictly -draw_text("MICE!", 1, scale=2) -draw_text("A GAME BY MATTEO", 5, scale=1) -draw_text("BECAUSE HE WAS BORED", 6, scale=1) -draw_text("PRESS START", 16, scale=1) - -tiles = [] -is_text_tile = [] -for y in range(0, 144, 8): - for x in range(0, 160, 8): - tile = palette_img[y:y+8, x:x+8] - t_tuple = tuple(map(tuple, tile)) - tiles.append(t_tuple) - is_text_tile.append(np.any(is_text_mask[y:y+8, x:x+8])) - -unique_tiles = list(collections.OrderedDict.fromkeys(tiles)) - -print(f"Total tiles: {len(tiles)}") -print(f"Unique tiles before reduction: {len(unique_tiles)}") - -if len(unique_tiles) > 256: - print("WARNING: More than 256 unique tiles! Reducing...") - text_unique = list(collections.OrderedDict.fromkeys([tiles[i] for i in range(len(tiles)) if is_text_tile[i]])) - print(f"Text unique tiles: {len(text_unique)}") - - counts = collections.Counter(tiles) - for t in text_unique: - del counts[t] - - most_common = [t for t, c in counts.most_common(256 - len(text_unique))] - reduced_unique = text_unique + most_common - - for i in range(len(tiles)): - if tiles[i] not in reduced_unique: - t_arr = np.array(tiles[i]) - best_match = 0 - best_dist = float('inf') - for j, ru in enumerate(reduced_unique): - dist = np.sum((t_arr - np.array(ru))**2) - if dist < best_dist: - best_dist = dist - best_match = j - tiles[i] = reduced_unique[best_match] - unique_tiles = reduced_unique - print(f"Reduced to {len(unique_tiles)} unique tiles.") - -tile_map = [unique_tiles.index(t) for t in tiles] - -# Preview to ensure it's not broken -preview_img = np.zeros((144, 160), dtype=np.uint8) -for i, t in enumerate(tiles): - y = (i // 20) * 8 - x = (i % 20) * 8 - preview_img[y:y+8, x:x+8] = np.array(t) - -color_map = {0: 255, 1: 170, 2: 85, 3: 0} -preview_rgb = np.zeros((144, 160, 3), dtype=np.uint8) -for y in range(144): - for x in range(160): - c = color_map[preview_img[y, x]] - preview_rgb[y, x] = [c, c, c] - -Image.fromarray(preview_rgb).save('/tmp/preview_title_fixed.png') - -c_tiles = [] -for tile in unique_tiles: - tile_data = [] - for row in tile: - low_byte = 0 - high_byte = 0 - for x, color in enumerate(row): - if color & 1: - low_byte |= (1 << (7 - x)) - if color & 2: - high_byte |= (1 << (7 - x)) - tile_data.append(low_byte) - tile_data.append(high_byte) - c_tiles.append(tile_data) - -with open('src/title_bg.h', 'w') as f: - f.write("#ifndef TITLE_BG_H\n#define TITLE_BG_H\n\n#include \n\n") - f.write(f"extern const uint8_t title_bg_tiles[{len(c_tiles) * 16}];\n") - f.write(f"extern const uint8_t title_bg_map[{len(tile_map)}];\n\n#endif\n") - -with open('src/title_bg.c', 'w') as f: - f.write("#include \"title_bg.h\"\n\n") - f.write(f"const uint8_t title_bg_tiles[{len(c_tiles) * 16}] = {{\n") - for tile in c_tiles: - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile]) + ",\n") - f.write("};\n\n") - f.write(f"const uint8_t title_bg_map[{len(tile_map)}] = {{\n") - for i in range(0, len(tile_map), 20): - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile_map[i:i+20]]) + ",\n") - f.write("};\n") diff --git a/prepare_victory.py b/prepare_victory.py deleted file mode 100644 index e3050a7..0000000 --- a/prepare_victory.py +++ /dev/null @@ -1,143 +0,0 @@ -import sys -from PIL import Image -import numpy as np -import collections - -img_path = '/tmp/victory_cropped.png' -try: - img = Image.open(img_path).convert('L') -except Exception as e: - print(f"Error opening image: {e}") - sys.exit(1) - -w, h = img.size -target_ratio = 160 / 144 -img_ratio = w / h -if img_ratio > target_ratio: - new_w = int(h * target_ratio) - left = (w - new_w) // 2 - img = img.crop((left, 0, left + new_w, h)) -else: - new_h = int(w / target_ratio) - top = (h - new_h) // 2 - img = img.crop((0, top, w, top + new_h)) - -img = img.resize((160, 144), Image.Resampling.LANCZOS) -np_img = np.array(img) -palette_img = np.zeros_like(np_img) -palette_img[np_img < 64] = 3 -palette_img[(np_img >= 64) & (np_img < 128)] = 2 -palette_img[(np_img >= 128) & (np_img < 192)] = 1 -palette_img[np_img >= 192] = 0 - -# Draw VICTORY! text -font = { - 'V': ["# #", "# #", "# #", "# #", "# #", " # # ", " # "], - 'I': [" ### ", " # ", " # ", " # ", " # ", " # ", " ### "], - 'C': [" ### ", "# #", "# ", "# ", "# ", "# #", " ### "], - 'T': ["#####", " # ", " # ", " # ", " # ", " # ", " # "], - 'O': [" ### ", "# #", "# #", "# #", "# #", "# #", " ### "], - 'R': ["#### ", "# #", "# #", "#### ", "# # ", "# # ", "# #"], - 'Y': ["# #", "# #", " # # ", " # ", " # ", " # ", " # "], - '!': [" # ", " # ", " # ", " # ", " ", " # ", " # "] -} - -text_str = "VICTORY!" -scale = 2 -char_w = 5 * scale -char_h = 7 * scale -spacing = 1 * scale -start_x = (160 - len(text_str) * (char_w + spacing)) // 2 -start_y = 144 - char_h - 24 # A bit higher to leave room for the timer - -is_text_mask = np.zeros_like(palette_img, dtype=bool) - -box_pad = 2 -for y in range(start_y - box_pad, start_y + char_h + box_pad): - for x in range(start_x - box_pad, start_x + len(text_str) * (char_w + spacing) + box_pad): - if 0 <= y < 144 and 0 <= x < 160: - palette_img[y, x] = 3 # Black background - is_text_mask[y, x] = True - -for i, char in enumerate(text_str): - if char in font: - glyph = font[char] - for row in range(7): - for col in range(5): - if glyph[row][col] == '#': - for sy in range(scale): - for sx in range(scale): - py = start_y + row * scale + sy - px = start_x + i * (char_w + spacing) + col * scale + sx - if 0 <= py < 144 and 0 <= px < 160: - palette_img[py, px] = 0 # White text - is_text_mask[py, px] = True - -tiles = [] -is_text_tile = [] -for y in range(0, 144, 8): - for x in range(0, 160, 8): - tile = palette_img[y:y+8, x:x+8] - t_tuple = tuple(map(tuple, tile)) - tiles.append(t_tuple) - is_text_tile.append(np.any(is_text_mask[y:y+8, x:x+8])) - -unique_tiles = list(collections.OrderedDict.fromkeys(tiles)) - -if len(unique_tiles) > 256: - print("WARNING: More than 256 unique tiles! Reducing...") - text_unique = list(collections.OrderedDict.fromkeys([tiles[i] for i in range(len(tiles)) if is_text_tile[i]])) - counts = collections.Counter(tiles) - for t in text_unique: - if t in counts: - del counts[t] - most_common = [t for t, c in counts.most_common(256 - len(text_unique))] - reduced_unique = text_unique + most_common - for i in range(len(tiles)): - if tiles[i] not in reduced_unique: - t_arr = np.array(tiles[i]) - best_match = 0 - best_dist = float('inf') - for j, ru in enumerate(reduced_unique): - dist = np.sum((t_arr - np.array(ru))**2) - if dist < best_dist: - best_dist = dist - best_match = j - tiles[i] = reduced_unique[best_match] - unique_tiles = reduced_unique - print(f"Reduced to {len(unique_tiles)} unique tiles.") - -tile_map = [unique_tiles.index(t) for t in tiles] - -c_tiles = [] -for tile in unique_tiles: - tile_data = [] - for row in tile: - low_byte = 0 - high_byte = 0 - for x, color in enumerate(row): - if color & 1: - low_byte |= (1 << (7 - x)) - if color & 2: - high_byte |= (1 << (7 - x)) - tile_data.append(low_byte) - tile_data.append(high_byte) - c_tiles.append(tile_data) - -with open('src/victory_bg.h', 'w') as f: - f.write("#ifndef VICTORY_BG_H\n#define VICTORY_BG_H\n\n#include \n\n") - f.write(f"extern const uint8_t victory_bg_tiles[{len(c_tiles) * 16}];\n") - f.write(f"extern const uint8_t victory_bg_map[{len(tile_map)}];\n\n#endif\n") - -with open('src/victory_bg.c', 'w') as f: - f.write("#include \"victory_bg.h\"\n\n") - f.write(f"const uint8_t victory_bg_tiles[{len(c_tiles) * 16}] = {{\n") - for tile in c_tiles: - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile]) + ",\n") - f.write("};\n\n") - f.write(f"const uint8_t victory_bg_map[{len(tile_map)}] = {{\n") - for i in range(0, len(tile_map), 20): - f.write(" " + ", ".join([f"0x{b:02X}" for b in tile_map[i:i+20]]) + ",\n") - f.write("};\n") - -print("Generated src/victory_bg.h and src/victory_bg.c") diff --git a/render_tiles.py b/render_tiles.py deleted file mode 100644 index 5ea6053..0000000 --- a/render_tiles.py +++ /dev/null @@ -1,81 +0,0 @@ -from PIL import Image -import numpy as np - -# 0: Floor (Lightest) -# 1: Light Green -# 2: Dark Green -# 3: Black -colors = { - 0: (224, 248, 208), # GB Lightest - 1: (136, 192, 112), # GB Light - 2: (52, 104, 86), # GB Dark - 3: (8, 24, 32) # GB Darkest -} - -floor_tile = [ - "00000000", - "00010000", - "00100000", - "00000000", - "00000000", - "00000010", - "00001000", - "00000000", -] - -def make_autotile(n, e, s, w): - t = [list("00000000") for _ in range(8)] - top = 0 if n else 2 - bottom = 8 if s else 6 - left = 0 if w else 2 - right = 8 if e else 6 - for r in range(top, bottom): - for c in range(left, right): - t[r][c] = "2" - if not n: - for c in range(left, right): - t[1][c] = "1" - t[2][c] = "1" - if not s: - for c in range(left, right): - t[6][c] = "3" - t[7][c] = "3" - if not n and not w: - t[1][2] = "0"; t[1][3] = "1"; t[2][2] = "1" - if not n and not e: - t[1][7] = "0"; t[1][6] = "1"; t[1][5] = "1"; t[2][7] = "0"; t[2][6] = "1" - if not s and not w: - t[7][2] = "0"; t[7][3] = "3"; t[6][2] = "3" - if not s and not e: - t[7][7] = "0"; t[7][6] = "3"; t[6][7] = "0"; t[6][6] = "3" - if n and s and e and w: - t[3][3] = "1"; t[4][5] = "3" - return ["".join(row) for row in t] - -tiles = [floor_tile] -for mask in range(16): - n = (mask & 8) != 0 - e = (mask & 4) != 0 - s = (mask & 2) != 0 - w = (mask & 1) != 0 - tiles.append(make_autotile(n, e, s, w)) - -# Render to a 17x1 grid (or 4x5) -cols = 4 -rows = 5 -img = Image.new('RGB', (cols * 10, rows * 10), (255, 255, 255)) - -for idx, t in enumerate(tiles): - c = idx % cols - r = idx // cols - x_offset = c * 10 + 1 - y_offset = r * 10 + 1 - for ty in range(8): - for tx in range(8): - color = colors[int(t[ty][tx])] - img.putpixel((x_offset + tx, y_offset + ty), color) - -# Scale up by 4x for visibility -img = img.resize((cols * 10 * 4, rows * 10 * 4), Image.Resampling.NEAREST) -img.save('/home/enne2/.gemini/antigravity/brain/69a2b2a9-088f-47ee-9f3c-9ba7ac332992/tileset_preview.png') -print("Generated tileset_preview.png") diff --git a/screenshot.png b/screenshot.png index 92515a2..ba53741 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/src/bomb.c b/src/bomb.c index 2774826..8afb4b5 100644 --- a/src/bomb.c +++ b/src/bomb.c @@ -19,17 +19,11 @@ typedef struct { static Bomb bomb; -static const uint8_t exp_sprite_pool[] = { - 20, 21, 22, 23, 29, 30, 31, 32, 33, 34, 35, 36, 37 -}; -#define EXP_POOL_SIZE 13 - void init_bombs(void) { bomb.state = BOMB_STATE_INACTIVE; set_sprite_data(5, 6, BombSpriteData); - move_sprite(38, 0, 0); // Bomba centrale - for(uint8_t i = 0; i < EXP_POOL_SIZE; i++) { - move_sprite(exp_sprite_pool[i], 0, 0); + for(uint8_t i = 34; i <= 38; i++) { + move_sprite(i, 0, 0); } } @@ -43,10 +37,9 @@ void drop_bomb(uint8_t x, uint8_t y) { } } -static void hide_explosion(void) { - move_sprite(38, 0, 0); - for(uint8_t i = 0; i < EXP_POOL_SIZE; i++) { - move_sprite(exp_sprite_pool[i], 0, 0); +static void hide_explosion() { + for(uint8_t i = 34; i <= 38; i++) { + move_sprite(i, 0, 0); } } @@ -81,59 +74,39 @@ void update_bombs(void) { play_sfx_explosion(); - set_sprite_tile(38, 8); // Centro esplosione + set_sprite_tile(38, 8); move_sprite(38, px, py); do_explosion_damage(bomb.x, bomb.y); - uint8_t pool_idx = 0; - - // Su - uint8_t yy = bomb.y; - while (yy > 0) { - yy--; - if (maze[yy][bomb.x] != 0) break; - if (pool_idx < EXP_POOL_SIZE) { - uint8_t sp = exp_sprite_pool[pool_idx++]; - set_sprite_tile(sp, 10); // Fiamma verticale - move_sprite(sp, px, (yy * 8) + 20); - } - do_explosion_damage(bomb.x, yy); + if (bomb.y > 0 && maze[bomb.y - 1][bomb.x] == 0) { + set_sprite_tile(34, 10); + move_sprite(34, px, py - 8); + do_explosion_damage(bomb.x, bomb.y - 1); } - - // Giù - yy = bomb.y; - while (yy < MAZE_HEIGHT - 1) { - yy++; - if (maze[yy][bomb.x] != 0) break; - if (pool_idx < EXP_POOL_SIZE) { - uint8_t sp = exp_sprite_pool[pool_idx++]; - set_sprite_tile(sp, 10); // Fiamma verticale - move_sprite(sp, px, (yy * 8) + 20); - } - do_explosion_damage(bomb.x, yy); + if (bomb.y < MAZE_HEIGHT - 1 && maze[bomb.y + 1][bomb.x] == 0) { + set_sprite_tile(35, 10); + move_sprite(35, px, py + 8); + do_explosion_damage(bomb.x, bomb.y + 1); } - - // Sinistra - uint8_t xx = bomb.x; - while (xx > 0) { - xx--; - if (maze[bomb.y][xx] != 0) break; - if (pool_idx < EXP_POOL_SIZE) { - uint8_t sp = exp_sprite_pool[pool_idx++]; - set_sprite_tile(sp, 9); // Fiamma orizzontale - move_sprite(sp, (xx * 8) + 12, py); - } - do_explosion_damage(xx, bomb.y); + if (bomb.x > 0 && maze[bomb.y][bomb.x - 1] == 0) { + set_sprite_tile(36, 9); + move_sprite(36, px - 8, py); + do_explosion_damage(bomb.x - 1, bomb.y); } - - // Destra - xx = bomb.x; - while (xx < MAZE_WIDTH - 1) { - xx++; - if (maze[bomb.y][xx] != 0) break; - if (pool_idx < EXP_POOL_SIZE) { - uint8_t sp = exp_sprite_pool[pool_idx++]; - set_sprite_tile(sp, 9); // Fiamma orizzontale + if (bomb.x < MAZE_WIDTH - 1 && maze[bomb.y][bomb.x + 1] == 0) { + set_sprite_tile(37, 9); + move_sprite(37, px + 8, py); + do_explosion_damage(bomb.x + 1, bomb.y); + } + } + } else if (bomb.state == BOMB_STATE_EXPLODING) { + bomb.timer--; + if (bomb.timer == 0) { + bomb.state = BOMB_STATE_INACTIVE; + hide_explosion(); + } + } +} move_sprite(sp, (xx * 8) + 12, py); } do_explosion_damage(xx, bomb.y); diff --git a/src/cursor.c b/src/cursor.c index 25fe5b4..a8599e2 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -3,7 +3,6 @@ #include #include "bomb.h" #include "music.h" -#include "rat.h" const unsigned char CursorSpriteData[] = { // Bordo quadrato 8x8 (Nero, colore 3 -> 11) @@ -73,32 +72,6 @@ void update_cursor(void) { drop_bomb(cursor_x, cursor_y); } - // Arma secondaria: Fucile a pompa (Tasto B) - static uint16_t shotgun_cooldown = 0; - static uint8_t shotgun_blast_timer = 0; - - if (shotgun_cooldown > 0) shotgun_cooldown--; - if (shotgun_blast_timer > 0) { - shotgun_blast_timer--; - if (shotgun_blast_timer == 0) { - move_sprite(24, 0, 0); // Nascondi l'effetto sparo - } - } - - if ((keys & J_B) && !(previous_keys & J_B)) { - if (shotgun_cooldown == 0) { - play_sfx_shotgun(); - kill_rats_at(cursor_x, cursor_y); - - // Mostra l'effetto dello sparo (usiamo lo sprite 24 e il tile 8 che è il centro dell'esplosione) - set_sprite_tile(24, 8); - move_sprite(24, cursor_x * 8 + 12, cursor_y * 8 + 20); - shotgun_blast_timer = 15; // Visibile per un quarto di secondo - - shotgun_cooldown = 180; // 3 secondi di ricarica a 60 FPS - } - } - // Tasto SELECT per attivare/disattivare la musica if ((keys & J_SELECT) && !(previous_keys & J_SELECT)) { toggle_music(); @@ -115,3 +88,17 @@ void update_cursor(void) { move_sprite(39, cursor_x * 8 + 12, cursor_y * 8 + 20); } } + toggle_music(); + } + + previous_keys = keys; + + // Effetto lampeggiante: nascondi il cursore per metà del ciclo (ogni 16 frame) + blink++; + if (blink & 0x10) { + move_sprite(39, 0, 0); // Nascondi spostandolo fuori schermo + } else { + // Posiziona il cursore calcolando offset (12, 20) per centrarlo sulla griglia 8x8 + move_sprite(39, cursor_x * 8 + 12, cursor_y * 8 + 20); + } +} diff --git a/src/main.c b/src/main.c index 55abee8..9097066 100644 --- a/src/main.c +++ b/src/main.c @@ -154,14 +154,6 @@ void main(void) { while (1) { if (game_over_flag || victory_flag) { HIDE_SPRITES; - - // Nascondi tutti gli sprite (spostandoli fuori dallo schermo visibile) - // Questo spiega perché prima vedevi i topi fermi: lo schermo era - // cambiato, ma gli sprite erano rimasti nelle loro posizioni. - for (uint8_t i = 0; i < 40; i++) { - move_sprite(i, 0, 0); - } - move_bkg(0, 0); // Resetta lo scrolling hardware // Ripristina la palette normale (Nero=3, Bianco=0) @@ -247,8 +239,6 @@ void main(void) { uint8_t keys = joypad(); - // Debug cheat rimosso: il tasto B ora è usato per l'arma secondaria - // Controllo per la Pausa if ((keys & J_START) && !(main_prev_keys & J_START)) { // Effetto sonoro pausa diff --git a/src/music.c b/src/music.c index 6640cc0..e1b57db 100644 --- a/src/music.c +++ b/src/music.c @@ -43,7 +43,6 @@ #define A_G N_G4 #define A_GS N_GS4 #define A_A N_A4 -#define A_AS N_AS4 #define A_B N_B4 #define A_D N_D4 #define A_C5 N_C5 @@ -89,11 +88,9 @@ const uint16_t* const arp_parts[4] = { trk_arp_0, trk_arp_1, trk_arp_2, trk_arp_ #define M_G N_G5 #define M_GS N_GS5 #define M_A N_A5 -#define M_AS N_AS5 #define M_B N_B5 #define M_C6 N_C6 #define M_D6 N_D6 -#define M_DS N_DS5 #define M_E6 N_E6 #define M__ N_REST @@ -133,7 +130,6 @@ const uint16_t* const mel_parts[4] = { trk_mel_0, trk_mel_1, trk_mel_2, trk_mel_ #define B_G N_G4 #define B_GS N_GS4 #define B_A N_A4 -#define B_AS N_AS4 #define B_B N_B4 #define B_C5 N_C5 #define B_E5 N_E5 @@ -263,54 +259,15 @@ const uint16_t title_bass[16] = { }; // ========================================== -// MACABRE VICTORY FANFARE (64 ticks, 8 frames/tick) +// VICTORY TRACK (16 ticks loop) // ========================================== -const uint16_t vic_mel[64] = { - M_C, N_REST, M_C, N_REST, M_C, N_REST, M_C, N_SUST, - N_GS4, N_SUST, N_SUST, N_SUST, N_AS4, N_SUST, N_SUST, N_SUST, - M_C, N_SUST, N_SUST, N_SUST, N_AS4, N_REST, M_C, N_SUST, - N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, - - M_C, N_REST, M_C, N_REST, M_C, N_REST, M_C, N_SUST, - N_GS4, N_SUST, N_SUST, N_SUST, N_AS4, N_SUST, N_SUST, N_SUST, - M_C, N_SUST, N_SUST, N_SUST, M_DS, N_REST, M_C, N_SUST, - N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, N_SUST, N_SUST -}; - -const uint16_t vic_bass[64] = { - B_C, N_REST, B_C, N_REST, B_C, N_REST, B_C, N_REST, - B_GS, N_REST, B_GS, N_REST, B_AS, N_REST, B_AS, N_REST, - B_C, N_REST, B_C, N_REST, B_C, N_REST, B_C, N_REST, - B_C, N_REST, B_C, N_REST, B_C, N_REST, B_C, N_REST, - - B_C, N_REST, B_C, N_REST, B_C, N_REST, B_C, N_REST, - B_GS, N_REST, B_GS, N_REST, B_AS, N_REST, B_AS, N_REST, - B_C, N_REST, B_C, N_REST, B_C, N_REST, B_C, N_REST, - B_C, N_REST, B_C, N_REST, B_C, N_REST, B_C, N_REST +const uint16_t victory_mel[16] = { + M_C, M_E, M_G, M__, M_C6, M_G, M_E, M__, + M_F, M_A, M_C6, M__, M_E6, M_C6, M_G, M__ }; - -const uint16_t vic_arp[64] = { - A_C, N_SUST, A_DS4, N_SUST, A_G, N_SUST, A_C5, N_SUST, - A_GS, N_SUST, A_C, N_SUST, A_AS, N_SUST, A_D4, N_SUST, - A_C, N_SUST, A_DS4, N_SUST, A_G, N_SUST, A_C5, N_SUST, - A_C, N_SUST, A_DS4, N_SUST, A_G, N_SUST, A_C5, N_SUST, - - A_C, N_SUST, A_DS4, N_SUST, A_G, N_SUST, A_C5, N_SUST, - A_GS, N_SUST, A_C, N_SUST, A_AS, N_SUST, A_D4, N_SUST, - A_C, N_SUST, A_DS4, N_SUST, A_G, N_SUST, A_C5, N_SUST, - A_C, N_REST, N_REST, N_REST, N_REST, N_REST, N_REST, N_REST -}; - -const uint8_t vic_drum[64] = { - 1, 0, 1, 0, 1, 0, 1, 0, - 1, 0, 1, 0, 1, 0, 1, 0, - 1, 2, 1, 2, 1, 2, 1, 2, - 1, 0, 2, 0, 1, 0, 2, 0, - - 1, 0, 1, 0, 1, 0, 1, 0, - 1, 0, 1, 0, 1, 0, 1, 0, - 1, 2, 1, 2, 1, 2, 1, 2, - 1, 0, 0, 0, 0, 0, 0, 0 +const uint16_t victory_bass[16] = { + B_C, B_E, B_G, B_E, B_C, B_E, B_G, B_E, + B_F, B_A, B_C5, B_A, B_C, B_G, B_C, B__ }; static uint16_t tick = 0; @@ -413,51 +370,35 @@ void update_music(void) { tick++; frame_counter = 8; // Veloce e allegro } else if (game_over_mode == 4) { - // MACABRE VICTORY TRACK - if (tick >= 64) { - game_over_mode = 2; // Fine - NR22_REG = 0x00; NR32_REG = 0x00; NR42_REG = 0x00; NR12_REG = 0x00; - return; - } - - uint16_t f1 = vic_arp[tick]; - if (f1 != N_SUST) { - if (f1 == N_REST) { - NR12_REG = 0x00; - } else { - NR10_REG = 0x00; NR11_REG = 0x80; NR12_REG = 0x51; - NR13_REG = (uint8_t)(f1 & 0xFF); NR14_REG = 0x80 | ((f1 >> 8) & 0x07); - } + // VICTORY TRACK + if (tick >= 16) { + tick = 0; // LOOP } - uint16_t f2 = vic_mel[tick]; - if (f2 != N_SUST) { - if (f2 == N_REST) { - NR22_REG = 0x00; - } else { - NR21_REG = 0x80; NR22_REG = 0xF7; - NR23_REG = (uint8_t)(f2 & 0xFF); NR24_REG = 0x80 | ((f2 >> 8) & 0x07); - } + uint16_t f2 = victory_mel[tick]; + if (f2 == N_REST) { + NR22_REG = 0x00; + } else { + NR21_REG = 0x80; NR22_REG = 0xF2; + NR23_REG = (uint8_t)(f2 & 0xFF); NR24_REG = 0x80 | ((f2 >> 8) & 0x07); } - uint16_t f3 = vic_bass[tick]; - if (f3 != N_SUST) { - if (f3 == N_REST) { - NR32_REG = 0x00; - } else { - NR32_REG = 0x20; - NR33_REG = (uint8_t)(f3 & 0xFF); NR34_REG = 0x80 | ((f3 >> 8) & 0x07); - } + uint16_t f3 = victory_bass[tick]; + if (f3 == N_REST) { + NR32_REG = 0x00; + } else { + NR32_REG = 0x20; + NR33_REG = (uint8_t)(f3 & 0xFF); NR34_REG = 0x80 | ((f3 >> 8) & 0x07); } - uint8_t d4 = vic_drum[tick]; - if (d4 == 1) { - NR41_REG = 0x00; NR42_REG = 0xF2; - NR43_REG = 0x51; NR44_REG = 0x80; + if (tick % 2 == 0) { + NR41_REG = 0x00; NR42_REG = 0x51; NR43_REG = 0x21; NR44_REG = 0x80; + } else { + NR42_REG = 0x00; } tick++; - frame_counter = 8; // Più veloce e marziale + frame_counter = 6; // Molto veloce e festoso } else if (game_over_mode == 1) { // GAME OVER TRACKER (Tragico, 20 frames per tick) if (tick >= 64) { @@ -588,30 +529,20 @@ void play_game_over_music(void) { } void play_sfx_explosion(void) { - // Esplosione molto forte e lunga sul canale 4 - NR41_REG = 0x00; - NR42_REG = 0xF7; // Volume max (0xF), decay lento (7) - NR43_REG = 0x57; // Rumore molto basso e cupo - NR44_REG = 0x80; // Trigger (senza length counter) - sfx_timer = 45; // Proteggi CH4 -} - -void play_sfx_shotgun(void) { - // Sparo di fucile molto forte, corto e secco sul canale 4 - NR41_REG = 0x00; - NR42_REG = 0xF2; // Volume max, decadimento super rapido - NR43_REG = 0x22; // Rumore bianco medio, secco - NR44_REG = 0x80; // Trigger (senza length counter) - sfx_timer = 20; + // Esplosione forte e lunga sul canale 4 + NR42_REG = 0xF7; + NR43_REG = 0x6E; + NR44_REG = 0xC0; + sfx_timer = 30; // Proteggi CH4 e CH1 } void play_sfx_bomb_drop(void) { // Un suono di innesco "tsst" (miccia) - NR41_REG = 0x00; + NR41_REG = 0x01; // Corto NR42_REG = 0xA2; // Volume alto, decadimento rapido - NR43_REG = 0x12; // Frequenza alta, sibilo - NR44_REG = 0x80; // Trigger (senza length counter) - sfx_timer = 15; + NR43_REG = 0x22; // Frequenza alta, noise + NR44_REG = 0xC0; // Trigger + sfx_timer = 15; // Proteggi per la durata del suono } void play_title_music(void) { @@ -656,3 +587,13 @@ void play_victory_music(void) { NR12_REG = 0; NR22_REG = 0; NR32_REG = 0; NR42_REG = 0; } + wave_ptr[i] = wave_ram[i]; + } + NR30_REG = 0x80; + + game_over_mode = 4; // Victory mode + tick = 0; + frame_counter = 0; + + NR12_REG = 0; NR22_REG = 0; NR32_REG = 0; NR42_REG = 0; +} diff --git a/src/music.h b/src/music.h index beb0e44..f451606 100644 --- a/src/music.h +++ b/src/music.h @@ -9,7 +9,6 @@ void play_sfx_moan(void); void play_sfx_plop(void); void play_sfx_explosion(void); void play_sfx_bomb_drop(void); -void play_sfx_shotgun(void); void play_game_over_music(void); void play_title_music(void); diff --git a/test_audio.c b/test_audio.c index 3d3b9b1..6dbfd39 100644 --- a/test_audio.c +++ b/test_audio.c @@ -2,18 +2,9 @@ #include #include "music.h" -void main(void) { - uint8_t prev_keys = 0; - +void main() { printf(" AUDIO TEST MICE!\n\n"); - printf("UP: Explosion\n"); - printf("DOWN: Shotgun\n"); - printf("LEFT: Bomb Drop\n"); - printf("RIGHT: Moan\n"); - printf("A: Plop\n"); - printf("B: Victory Jingle\n"); - printf("START: Game Over Jingle\n"); - printf("SELECT: Toggle Music\n"); + printf(" Soundtrack in Loop...\n"); init_music(); @@ -22,32 +13,6 @@ void main(void) { wait_vbl_done(); uint8_t keys = joypad(); - - if ((keys & J_UP) && !(prev_keys & J_UP)) { - play_sfx_explosion(); - } - if ((keys & J_DOWN) && !(prev_keys & J_DOWN)) { - play_sfx_shotgun(); - } - if ((keys & J_LEFT) && !(prev_keys & J_LEFT)) { - play_sfx_bomb_drop(); - } - if ((keys & J_RIGHT) && !(prev_keys & J_RIGHT)) { - play_sfx_moan(); - } - if ((keys & J_A) && !(prev_keys & J_A)) { - play_sfx_plop(); - } - if ((keys & J_B) && !(prev_keys & J_B)) { - play_victory_music(); - } - if ((keys & J_START) && !(prev_keys & J_START)) { - play_game_over_music(); - } - if ((keys & J_SELECT) && !(prev_keys & J_SELECT)) { - toggle_music(); - } - - prev_keys = keys; + // Optional test logic for SFX could go here if needed. } } diff --git a/test_mockup.c b/test_mockup.c deleted file mode 100644 index a15b2c2..0000000 --- a/test_mockup.c +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "mockup_gfx.h" - -void main(void) { - DISPLAY_OFF; - - // Palette GBDK standard - BGP_REG = 0b11100100; - - // Non sappiamo quanti tile ci sono, ma sono <= 256. Lo script genera la dimensione esatta. - // Usiamo 256 per coprire tutti i tile - set_bkg_data(0, 256, MockupTileData); - - // Scriviamo direttamente l'intera mappa 20x18 - set_bkg_tiles(0, 0, 20, 18, MockupMapData); - - SHOW_BKG; - DISPLAY_ON; - - while(1) { - wait_vbl_done(); - } -}