From f1572efe3409929997c7d03053637c3b2923b673 Mon Sep 17 00:00:00 2001 From: enne2 Date: Thu, 18 Jun 2026 15:14:08 +0200 Subject: [PATCH] docs: complete README rewrite with composite screenshot and architecture deep-dive --- README.md | 268 +++++++++++++++++++++++++++------ build_autotiles.py | 91 ------------ build_bush_tiles.py | 214 -------------------------- build_pause.py | 80 ---------- build_pro_tiles.py | 330 ----------------------------------------- convert_mockup.py | 74 --------- extract_and_compare.py | 134 ----------------- extract_to_tiles_c.py | 120 --------------- fix_mockup.py | 120 --------------- make_bomb_sprites.py | 80 ---------- make_mockup_tiles.py | 147 ------------------ make_rat.py | 81 ---------- prepare_bg.py | 169 --------------------- prepare_title.py | 188 ----------------------- prepare_victory.py | 143 ------------------ render_tiles.py | 81 ---------- screenshot.png | Bin 4595 -> 18681 bytes src/bomb.c | 91 ++++-------- src/cursor.c | 41 ++--- src/main.c | 10 -- src/music.c | 151 ++++++------------- src/music.h | 1 - test_audio.c | 41 +---- test_mockup.c | 23 --- 24 files changed, 316 insertions(+), 2362 deletions(-) delete mode 100644 build_autotiles.py delete mode 100644 build_bush_tiles.py delete mode 100644 build_pause.py delete mode 100644 build_pro_tiles.py delete mode 100644 convert_mockup.py delete mode 100644 extract_and_compare.py delete mode 100644 extract_to_tiles_c.py delete mode 100644 fix_mockup.py delete mode 100644 make_bomb_sprites.py delete mode 100644 make_mockup_tiles.py delete mode 100644 make_rat.py delete mode 100644 prepare_bg.py delete mode 100644 prepare_title.py delete mode 100644 prepare_victory.py delete mode 100644 render_tiles.py delete mode 100644 test_mockup.c 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 92515a29288a2b2b50a7d54ac478bbed531205bf..ba537415cfc5acc6e7c1e11f2d85ae2df6a45f20 100644 GIT binary patch literal 18681 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 literal 4595 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 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(); - } -}