24 changed files with 316 additions and 2362 deletions
@ -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: |
||||
|
||||
1. **Allocazione Memoria in Potenze di 2 (`MAZE_PITCH = 32`)** |
||||
In C, per leggere un elemento da un array bidimensionale come `maze[y][x]`, il compilatore esegue un'operazione matematica: `y * LARGHEZZA_RIGA + x`. Nelle prime versioni, una larghezza di 20 richiedeva una lenta routine di moltiplicazione software. Per ovviare al problema, la riga logica in RAM è stata allargata a `32` (una potenza di due). In questo modo il compilatore SDCC risolve la moltiplicazione in un singolo, velocissimo *bit-shift* a sinistra (`y << 5`), azzerando del tutto il carico del processore. |
||||
|
||||
2. **Divisioni sostituite da Maschere Bitwise (Bitmasks)** |
||||
La funzione `rand() % num` usa l'operatore Modulo (`%`), che su un'architettura a 8-bit invoca un disastroso ciclo di sottrazioni ripetute per trovare il resto. Poiché la scelta della direzione richiede valori da 0 a 3, l'operatore modulo è stato rimosso in favore di un `& 3` (Bitwise AND). È istantaneo e produce un numero da 0 a 3 in un singolo colpo di clock. |
||||
|
||||
3. **Collisioni "Lazy" (Early-Exit Evaluation)** |
||||
Piuttosto che testare le sovrapposizioni millimetriche (`pixel_x / pixel_y`) su O(N²) iterazioni (105 combinazioni) per frame, la logica confronta in *short-circuit* soltanto le coordinate grossolane in griglia (`rat_x != rat_y`). Se i topi non si trovano nemmeno sulla stessa mattonella, l'algoritmo ignora istantaneamente tutto il resto. Questa singola riga taglia l'80% delle istruzioni necessarie per i check di collisione. |
||||
|
||||
4. **DAS (Delayed Auto Shift) per l'Input** |
||||
Per rendere fluida la navigazione del cursore sulla griglia, è stata implementata una logica a stati temporizzati ereditata dai classici come Tetris. Invece di far scattare il cursore a ogni micro-pressione o farlo "scivolare" in modo incontrollabile, il codice legge l'input continuo (`joypad()`) e utilizza un timer a due fasi: |
||||
- *Initial Delay*: alla prima pressione lo spostamento è istantaneo, dopodiché la logica va in "pausa" per 12 frame (~0.2 secondi). Questo previene i doppi scatti accidentali. |
||||
- *Auto-Repeat Delay*: se il giocatore continua a tenere premuto oltre la soglia iniziale, la pausa tra un movimento e l'altro si accorcia a 6 frame, permettendo uno scorrimento automatico e fulmineo lungo l'intero labirinto. |
||||
|
||||
Queste tecniche mostrano la filosofia del vero **retro-programming**, dove ogni ciclo di CPU conta. |
||||
|
||||
### Testare con emulatore SDL2 |
||||
|
||||
```bash |
||||
pyboy -w SDL2 -s 3 --sound-volume 100 maze.gb |
||||
``` |
||||
|
||||
### Diagnosticare l'audio |
||||
|
||||
```bash |
||||
pyboy -w SDL2 -s 3 --sound-volume 100 test_audio.gb |
||||
``` |
||||
|
||||
Nella ROM audio, ogni tasto attiva un suono diverso: |
||||
|
||||
| Tasto | Suono | |
||||
|-------|-------| |
||||
| ↑ | Esplosione bomba | |
||||
| ↓ | Sparo fucile | |
||||
| ← | Miccia bomba | |
||||
| → | Gemito topo | |
||||
| A | Plop (morte topo) | |
||||
| B | Fanfara di vittoria | |
||||
| START | Tema game over | |
||||
| SELECT | Toggle musica | |
||||
|
||||
--- |
||||
|
||||
## Architettura e Ottimizzazioni Hardware |
||||
|
||||
Il processore del Game Boy (Sharp **SM83**, simile allo Z80) gira a soli **4.19 MHz**, senza FPU, senza moltiplicatore hardware, con soli **8 KB di RAM** e **8 KB di VRAM**. Ogni ciclo di CPU conta. |
||||
|
||||
Di seguito le principali tecniche adottate per garantire i 60 FPS costanti con 10 entità AI indipendenti a schermo. |
||||
|
||||
--- |
||||
|
||||
### 1. Accesso a Matrici 2D senza Moltiplicazione — `MAZE_PITCH = 32` |
||||
|
||||
L'accesso a `maze[y][x]` compila in `base + y * LARGHEZZA + x`. Con una larghezza logica di 19, SDCC emette una **lenta routine di moltiplicazione software** (ciclo di addizioni ripetute, ~20+ cicli di clock). |
||||
|
||||
**Soluzione**: la riga logica in RAM è stata estesa a `32` (potenza di 2). Il compilatore sostituisce automaticamente `y * 32` con `y << 5` (un singolo bit-shift, **2 cicli**), eliminando completamente il costo. |
||||
|
||||
```c |
||||
// maze.h |
||||
#define MAZE_WIDTH 19 // larghezza logica del labirinto |
||||
#define MAZE_PITCH 32 // larghezza allocata in RAM (potenza di 2) |
||||
|
||||
uint8_t maze[MAZE_HEIGHT][MAZE_PITCH]; // accesso: maze[y][x] → base + (y<<5) + x |
||||
``` |
||||
|
||||
--- |
||||
|
||||
### 2. Modulo Rimosso — Bitwise AND al Posto di `%` |
||||
|
||||
La funzione `rand() % N` su architettura 8-bit emette una routine di **divisione software** (sottrazioni ripetute, ~60+ cicli). Poiché il pathfinding sceglie tra al massimo 4 direzioni (0–3), il modulo è stato rimpiazzato con un `& 3`: |
||||
|
||||
```c |
||||
// Prima: r = rand() % count; // lento, divisione software |
||||
// Dopo: r = rand() & 3; // istantaneo, singolo AND bitwise |
||||
``` |
||||
|
||||
--- |
||||
|
||||
### 3. Collision Check "Lazy" con Short-Circuit |
||||
|
||||
La collision detection tra 10 topi richiederebbe 45 coppie da testare a ogni frame. Il controllo è strutturato come segue: |
||||
|
||||
```c |
||||
// Se non sono nemmeno sulla stessa tile, salta tutto il resto |
||||
if (rats[i].rat_x != rats[j].rat_x || rats[i].rat_y != rats[j].rat_y) continue; |
||||
// Solo qui si fa il check pixel-preciso |
||||
``` |
||||
|
||||
Nella stragrande maggioranza dei frame, la guard condition è falsa per tutte le coppie e il codice di collisione non viene mai raggiunto. |
||||
|
||||
--- |
||||
|
||||
### 4. Meta-Sprite 16×8 per i Topi |
||||
|
||||
Il Game Boy può gestire al massimo **40 sprite hardware** (8×8 px ciascuno), con un limite di **10 sprite per scanline**. Ogni topo è una coppia di sprite 8×8 affiancati (meta-sprite 16×8), per un totale di 20 sprite per 10 topi. Il pool di sprite è: |
||||
|
||||
| Sprite HW | Uso | |
||||
|-----------|-----| |
||||
| 0–19 | Corpo dei topi (2 sprite × 10 topi) | |
||||
| 20–23 | Pool esplosione bomba (primo gruppo) | |
||||
| 24 | Flash sparo fucile | |
||||
| 25–28 | Timer HUD (4 cifre) | |
||||
| 29–37 | Pool esplosione bomba (secondo gruppo) | |
||||
| 38 | Bomba (sprite animato, 3 frame + esplosione) | |
||||
| 39 | Cursore del giocatore (bordo lampeggiante) | |
||||
|
||||
--- |
||||
|
||||
### 5. Autotiling a 4-bit dei Muri del Labirinto |
||||
|
||||
I muri del labirinto vengono scelti a runtime in base ai **4 vicini cardinali** (bitmask 4-bit → 16 varianti). Questo permette di avere giunzioni visivamente coerenti (angoli, T, croce) senza allocare dati extra in VRAM, che è un bene rarissimo: ne abbiamo solo 8 KB. |
||||
|
||||
--- |
||||
|
||||
### 6. DAS (Delayed Auto Shift) per l'Input |
||||
|
||||
Il cursore implementa il meccanismo DAS ereditato dai classici dell'epoca (Tetris originale DMG): |
||||
|
||||
- **Initial delay**: 12 frame (~0.2 s) alla prima pressione — previene i doppi scatti accidentali |
||||
- **Auto-repeat**: 6 frame per spostamento successivo — permette traversata rapida del labirinto |
||||
|
||||
```c |
||||
if (cursor_timer == 0) { |
||||
moved = 1; |
||||
cursor_timer = (first_press) ? 12 : 6; |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
### 7. Mini-Tracker Musicale a 4 Canali Nativo |
||||
|
||||
La musica è generata da un sequencer scritto interamente in C, senza librerie esterne. Ogni frame `update_music()` viene chiamata nel VBlank interrupt: |
||||
|
||||
- **CH1 (Square 1)**: arpeggio melodico |
||||
- **CH2 (Square 2)**: melodia principale |
||||
- **CH3 (Wave RAM)**: basso a onda custom (forma d'onda caricata in Wave RAM `0xFF30`) |
||||
- **CH4 (Noise)**: percussioni (kick, snare, hi-hat) |
||||
|
||||
Le note sono codificate come frequenze nei registri `NRx3/NRx4` tramite la formula: `f_reg = 2048 - (131072 / Hz)`. Una tabella di macro `N_C4`, `N_D4`, ecc. precompila i valori corretti. |
||||
|
||||
Gli SFX (esplosione, fucile, miccia, morte topo) interrompono temporaneamente i canali rilevanti tramite un `sfx_timer` di protezione, per poi restituire il controllo al tracker. |
||||
|
||||
--- |
||||
|
||||
### 8. Generazione Asset via Python (Pipeline Offline) |
||||
|
||||
La VRAM del Game Boy può contenere al massimo **256 tile da 8×8 pixel** (in formato 2bpp, 2 bit per pixel). Tutti gli asset grafici (sfondi del titolo, vittoria, game over) sono convertiti **offline** da PNG a header C tramite script Python dedicati: |
||||
|
||||
- `prepare_title.py` → `src/title_bg.c/h` |
||||
- `prepare_bg.py` → `src/rat_bg.c/h` |
||||
- `prepare_victory.py` → `src/victory_bg.c/h` (con overlay testo "VICTORY!") |
||||
|
||||
Questo mantiene il Makefile semplice e i sorgenti C puliti dai blob binari. |
||||
|
||||
--- |
||||
|
||||
## Toolchain e Requisiti |
||||
|
||||
| Componente | Versione | |
||||
|-----------|---------| |
||||
| GBDK-2020 | ≥ 4.3.0 | |
||||
| lcc (frontend SDCC) | incluso in GBDK | |
||||
| Python | ≥ 3.9 (per gli script di asset) | |
||||
| Pillow | `pip install pillow` | |
||||
| PyBoy (opzionale) | `pip install pyboy` | |
||||
|
||||
La ROM finale è compatibile con qualsiasi emulatore Game Boy DMG accurato (BGB, Sameboy, Gambatte) e con hardware originale tramite flash cart. |
||||
|
||||
--- |
||||
|
||||
*"A game by Matteo, because he was bored."* |
||||
|
||||
@ -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!") |
||||
@ -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") |
||||
@ -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") |
||||
@ -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") |
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 18 KiB |
@ -1,23 +0,0 @@
|
||||
#include <gb/gb.h> |
||||
#include "mockup_gfx.h" |
||||
|
||||
void main(void) { |
||||
DISPLAY_OFF; |
||||
|
||||
// Palette GBDK standard
|
||||
BGP_REG = 0b11100100; |
||||
|
||||
// Non sappiamo quanti tile ci sono, ma sono <= 256. Lo script genera la dimensione esatta.
|
||||
// Usiamo 256 per coprire tutti i tile
|
||||
set_bkg_data(0, 256, MockupTileData); |
||||
|
||||
// Scriviamo direttamente l'intera mappa 20x18
|
||||
set_bkg_tiles(0, 0, 20, 18, MockupMapData); |
||||
|
||||
SHOW_BKG; |
||||
DISPLAY_ON; |
||||
|
||||
while(1) { |
||||
wait_vbl_done(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue