docs: complete README rewrite with composite screenshot and architecture deep-dive
This commit is contained in:
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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:
|
||||
### Testare con emulatore SDL2
|
||||
|
||||
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.
|
||||
```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."*
|
||||
|
||||
@@ -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!")
|
||||
@@ -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!")
|
||||
@@ -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.")
|
||||
@@ -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!")
|
||||
@@ -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.")
|
||||
@@ -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")
|
||||
@@ -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
@@ -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.")
|
||||
@@ -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("};")
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 18 KiB |
+32
-59
@@ -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);
|
||||
|
||||
+14
-27
@@ -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
@@ -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
|
||||
|
||||
+46
-105
@@ -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 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_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 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;
|
||||
// VICTORY TRACK
|
||||
if (tick >= 16) {
|
||||
tick = 0; // LOOP
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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 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 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+3
-38
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user