Browse Source

docs: complete README rewrite with composite screenshot and architecture deep-dive

master
Matteo Benedetto 2 days ago
parent
commit
f1572efe34
  1. 268
      README.md
  2. 91
      build_autotiles.py
  3. 214
      build_bush_tiles.py
  4. 80
      build_pause.py
  5. 330
      build_pro_tiles.py
  6. 74
      convert_mockup.py
  7. 134
      extract_and_compare.py
  8. 120
      extract_to_tiles_c.py
  9. 120
      fix_mockup.py
  10. 80
      make_bomb_sprites.py
  11. 147
      make_mockup_tiles.py
  12. 81
      make_rat.py
  13. 169
      prepare_bg.py
  14. 188
      prepare_title.py
  15. 143
      prepare_victory.py
  16. 81
      render_tiles.py
  17. BIN
      screenshot.png
  18. 91
      src/bomb.c
  19. 41
      src/cursor.c
  20. 10
      src/main.c
  21. 151
      src/music.c
  22. 1
      src/music.h
  23. 41
      test_audio.c
  24. 23
      test_mockup.c

268
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."*

91
build_autotiles.py

@ -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!")

214
build_bush_tiles.py

@ -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!")

80
build_pause.py

@ -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.")

330
build_pro_tiles.py

@ -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!")

74
convert_mockup.py

@ -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.")

134
extract_and_compare.py

@ -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")

120
extract_to_tiles_c.py

@ -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!")

120
fix_mockup.py

@ -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.")

80
make_bomb_sprites.py

@ -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("};")

147
make_mockup_tiles.py

@ -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")

81
make_rat.py

@ -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 <stdint.h>\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")

169
prepare_bg.py

@ -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 <stdint.h>\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")

188
prepare_title.py

@ -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 <stdint.h>\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")

143
prepare_victory.py

@ -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 <stdint.h>\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")

81
render_tiles.py

@ -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")

BIN
screenshot.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 18 KiB

91
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);

41
src/cursor.c

@ -3,7 +3,6 @@
#include <gb/gb.h>
#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);
}
}

10
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

151
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;
}

1
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);

41
test_audio.c

@ -2,18 +2,9 @@
#include <stdio.h>
#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.
}
}

23
test_mockup.c

@ -1,23 +0,0 @@
#include <gb/gb.h>
#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();
}
}
Loading…
Cancel
Save