Refactoring: Project structure, sprite stairs & fog masks rendering fix

This commit is contained in:
2026-06-23 19:43:55 +02:00
parent 0ceae6f98b
commit 7d3003203c
89 changed files with 4576 additions and 1767 deletions
+47 -16
View File
@@ -2,25 +2,56 @@ GBDK_HOME = /home/enne2/.local/gbdk
LCC = $(GBDK_HOME)/bin/lcc
PNG2ASSET = $(GBDK_HOME)/bin/png2asset
all: hello_iso.gb test_gameover.gb
# Source and build directories
SRC_DIR = src
ASSETS_DIR = assets
SCRIPTS_DIR = scripts
BUILD_DIR = build
assets: generate_assets.py
python3 generate_assets.py
$(PNG2ASSET) tiles.png -c tiles.c -map -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) player.png -c player.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) gameover.png -c gameover.c -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) stamina.png -c stamina.c -bpp 2 -noflip -keep_palette_order
# C source files
SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/engine.c $(SRC_DIR)/globals.c $(SRC_DIR)/maze.c $(SRC_DIR)/sound.c $(SRC_DIR)/render.c $(SRC_DIR)/player_logic.c $(SRC_DIR)/enemy_logic.c $(SRC_DIR)/tiles.c $(SRC_DIR)/player.c $(SRC_DIR)/enemy.c $(SRC_DIR)/gameover.c $(SRC_DIR)/victory.c $(SRC_DIR)/stamina.c $(SRC_DIR)/title_bg.c $(SRC_DIR)/stairs_sprite.c
$(PNG2ASSET) title_bg.png -c title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
all: $(BUILD_DIR)/hello_iso.gb $(BUILD_DIR)/test_gameover.gb $(BUILD_DIR)/test_sprite_stairs.gb
hello_iso.gb: assets main.c engine.c
$(LCC) -Wa-l -Wl-m -Wl-j -o hello_iso.gb main.c engine.c tiles.c player.c gameover.c stamina.c title_bg.c
# Generate image assets
generate_images: $(SCRIPTS_DIR)/generate_assets.py $(SCRIPTS_DIR)/generate_enemy.py $(SCRIPTS_DIR)/generate_stairs_sprite.py
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_assets.py
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_enemy.py
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_stairs_sprite.py
test_gameover.gb: assets test_gameover_render.c
$(LCC) -Wa-l -Wl-m -Wl-j -o test_gameover.gb test_gameover_render.c player.c gameover.c stamina.c
# Convert image assets to C source files
generate_c_assets: generate_images
$(PNG2ASSET) $(ASSETS_DIR)/tiles.png -c $(SRC_DIR)/tiles.c -map -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) $(ASSETS_DIR)/player.png -c $(SRC_DIR)/player.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) $(ASSETS_DIR)/enemy.png -c $(SRC_DIR)/enemy.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order -sp 0x10
$(PNG2ASSET) $(ASSETS_DIR)/gameover.png -c $(SRC_DIR)/gameover.c -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) $(ASSETS_DIR)/victory.png -c $(SRC_DIR)/victory.c -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) $(ASSETS_DIR)/stamina.png -c $(SRC_DIR)/stamina.c -bpp 2 -noflip -keep_palette_order
$(PNG2ASSET) $(ASSETS_DIR)/title_bg.png -c $(SRC_DIR)/title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
$(PNG2ASSET) $(ASSETS_DIR)/stairs_sprite.png -c $(SRC_DIR)/stairs_sprite.c -sw 32 -sh 16 -bpp 2 -noflip -keep_palette_order -spr8x16
# Main ROM target
$(BUILD_DIR)/hello_iso.gb: generate_c_assets $(SRCS)
mkdir -p $(BUILD_DIR)
$(LCC) $(LCCFLAGS) -o $(BUILD_DIR)/hello_iso.gb $(SRCS)
# Test ROM target
$(BUILD_DIR)/test_gameover.gb: generate_c_assets $(SRC_DIR)/test_gameover_render.c
mkdir -p $(BUILD_DIR)
$(LCC) -Wa-l -Wl-m -Wl-j -o $(BUILD_DIR)/test_gameover.gb $(SRC_DIR)/test_gameover_render.c $(SRC_DIR)/player.c $(SRC_DIR)/enemy.c $(SRC_DIR)/gameover.c $(SRC_DIR)/stamina.c
# Sprite Stairs Test ROM target
$(BUILD_DIR)/test_sprite_stairs.gb: generate_c_assets $(SRCS)
mkdir -p $(BUILD_DIR)
$(LCC) -DUSE_SPRITE_STAIRS $(LCCFLAGS) -o $(BUILD_DIR)/test_sprite_stairs.gb $(SRCS)
clean:
rm -f *.o *.lst *.map *.gb *.ihx *.sym *.cdb *.adb *.asm gameover.c gameover.h gameover.png player.png tiles.png player.c player.h tiles.c tiles.h test_gameover_render.o player.o gameover.o stamina.c stamina.h stamina.png stamina.o title_bg.c title_bg.h title_bg.o
rm -rf $(BUILD_DIR)/*
rm -f $(SRC_DIR)/tiles.c $(SRC_DIR)/tiles.h
rm -f $(SRC_DIR)/player.c $(SRC_DIR)/player.h
rm -f $(SRC_DIR)/enemy.c $(SRC_DIR)/enemy.h
rm -f $(SRC_DIR)/gameover.c $(SRC_DIR)/gameover.h
rm -f $(SRC_DIR)/victory.c $(SRC_DIR)/victory.h
rm -f $(SRC_DIR)/stamina.c $(SRC_DIR)/stamina.h
rm -f $(SRC_DIR)/title_bg.c $(SRC_DIR)/title_bg.h
rm -f $(SRC_DIR)/stairs_sprite.c $(SRC_DIR)/stairs_sprite.h
View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 991 KiB

After

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 B

After

Width:  |  Height:  |  Size: 971 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

BIN
View File
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
# Intelligenza Artificiale (Il Fantasma)
Il gioco utilizza un inseguitore, soprannominato "Il Fantasma", che bracca il giocatore all'interno del labirinto. Poiché il giocatore si muove velocemente, l'AI del fantasma richiede limiti artificiali per risultare un nemico superabile e bilanciato.
## Pathfinding: Perché "Greedy" e non A*?
Sui sistemi moderni o su griglie piccole, l'A-Star (A*) è l'algoritmo standard per il pathfinding dei nemici. Calcola il percorso perfetto evitando vicoli ciechi.
Sul Game Boy (CPU 4 MHz), gestire la memoria (heap, nodi) richiesta da A* per un array 7x7 genererebbe ritardi nei frame (frame drops) inammissibili per un gioco real-time.
La soluzione implementata è un approccio **Greedy (Goloso)**:
Ad ogni passo, il fantasma verifica le 4 celle a lui adiacenti (Nord, Sud, Est, Ovest). Tra quelle calpestabili (non muri), sceglie quella la cui coordinata rende **minima la distanza spaziale al quadrato verso il giocatore** (`dx^2 + dy^2`).
Non usiamo la radice quadrata (necessaria per calcolare la distanza esatta euclidea) perché troppo costosa per la CPU; dal momento che stiamo solo comparando quale distanza sia minore, i quadrati bastano e avanzano matematicamente (`A < B <=> A^2 < B^2`).
Un algoritmo Greedy tende a incastrarsi in ostacoli a forma di U, ma essendo questo un labirinto dinamico pieno di loop a corridoio singolo, questo "difetto" si trasforma in una dinamica di gioco: i giocatori astuti possono sfruttare il level design per confondere l'AI e aggirarla.
## Attivazione e Cooldown
Il fantasma non parte immediatamente alla carica. Usa la distanza di Chebyshev per "vedere":
```c
if (dist <= 2) { // Inizia a muoversi }
```
Ovvero, inizia l'inseguimento solo quando entra nel riquadro visivo del giocatore (5x5, distanza <= 2).
Inoltre, il fantasma impiega 16 frame per muoversi da un blocco all'altro (come il player), ma è limitato dalla variabile `enemy_cooldown = 60`. Dopo ogni singolo passo, rimane congelato per 1 secondo intero prima di poterne fare un altro. Questo garantisce che il fantasma sia più lento del giocatore e ti costringe, qualora commettessi un errore, a usare rapidamente la meccanica di Salto Evasivo e seminarlo.
## Hitbox Pixel-Perfect
Nonostante la mappa sia calcolata su una pura griglia logica bidimensionale (1 passo = 1 casella X,Y), la condizione di "Sconfitta" non si basa sul fatto che il giocatore e il fantasma occupino la medesima casella logica contemporaneamente. Si controlla invece che le coordinate fisiche (i veri e propri **pixel** renderizzati sullo schermo del Game Boy) si sovrappongano con un margine di errore basso (12 pixel in orizzontale e 6 in verticale).
Questo rende la morte "giusta" agli occhi dell'utente e permette scambi per il rotto della cuffia!
+25
View File
@@ -0,0 +1,25 @@
# Architettura di Base
## Organizzazione del Progetto
Il progetto è strutturato in modo da separare logicamente gli asset visivi dal codice sorgente:
- `assets/`: Contiene le immagini PNG (sprite, tileset) create dagli script.
- `scripts/`: Script Python per la generazione procedurale di grafica (es. metasprite del fantasma o del game over).
- `src/`: Tutto il codice sorgente C e gli header generati.
- `build/`: Risultato della compilazione (i file `.o` e il ROM `.gb` finale).
## Modularizzazione del Codice C
Inizialmente, l'intero gioco risiedeva in un unico file `engine.c` di oltre 1000 righe. Per facilitare la manutenzione, è stato rifattorizzato in moduli a singola responsabilità:
- `maze.c`: Gestisce puramente i dati della mappa e la loro generazione.
- `sound.c`: Isola tutte le interazioni con l'hardware audio (APU).
- `render.c`: Isola l'hardware visivo (PPU), la memoria video (VRAM) e lo scrolling (SCX/SCY).
- `player_logic.c` ed `enemy_logic.c`: Contengono le regole fisiche e l'AI.
- `engine.c`: Agisce da "Direttore d'Orchestra", richiamando i vari `update` in sequenza.
## Gestione dello Stato Globale (`globals.h`)
Un problema ricorrente nei giochi C in più moduli sono le "dipendenze circolari" (es. il render ha bisogno delle coordinate del player, ma il player ha bisogno della mappa per le collisioni).
Per risolvere questo, lo stato mutabile del gioco è centralizzato in `globals.c` ed esposto tramite `globals.h`.
Tutti i moduli includono `globals.h` e leggono/scrivono sulle variabili `extern` (come `stamina`, `game_over`, `player_lx`), evitando grovigli di include e rendendo il passaggio dei dati estremamente leggero e globale, un approccio molto comune ed efficiente nello sviluppo retro-console.
+34
View File
@@ -0,0 +1,34 @@
# Sintesi Audio e Musica
Tutto l'audio del gioco è prodotto internamente manipolando a basso livello i 4 canali audio (APU) registrati direttamente in memoria hardware del Game Boy (da `NR10_REG` a `NR52_REG`).
L'architettura separa completamente la logica audio da `engine.c` nel file isolato `sound.c`.
## Hardware Audio Channels (APU)
Il Game Boy dispone di 4 canali dedicati:
1. **Canale 1 (Pulse 1)**: Onda quadra con sweep (glissando automatico del pitch).
2. **Canale 2 (Pulse 2)**: Onda quadra senza sweep. Ideale per la base musicale.
3. **Canale 3 (Wave)**: Onda personalizzabile definita in RAM (qui non utilizzata).
4. **Canale 4 (Noise)**: Generatore di rumore pseudo-casuale bianco, usato per batterie ed effetti.
## Il Sequencer via Interrupt VBL
L'intera colonna sonora viene eseguita senza bloccare mai l'esecuzione del codice di rendering o della CPU.
La funzione `play_music_tick` viene agganciata al **Vertical Blanking (VBL) Interrupt** (`add_VBL()`), che scatta magicamente tra il disegno di un frame video e il successivo sul display a circa 60 Hz.
Ogni chiamata incrementa un timer logico; quando il timer supera la durata della nota impostata, il sequencer aggiorna l'indice dell'array passandolo ai registri e suonando fisicamente il suono.
## Illusioni Audio (Arpeggi e Polifonia Fittizia)
Il chip GBDK può suonare solo un singolo tono per canale alla volta.
Per la "Fanfara di Vittoria" e i momenti di forte tensione, per imitare accordi (che richiederebbero 3 canali), usiamo gli **Arpeggi Veloci**.
Una melodia arpeggiata è un elenco di tre note (Es: Do-Mi-Sol) suonate in un loop talmente veloce che l'orecchio umano le fonde percependo un accordo polifonico. Nel codice, le note `N_C4`, `N_E4`, `N_G4` si susseguono ogni pochi frame (timer = 8 frame/tick).
## Il Rumore come Percussione
Durante la triste sequenza in tonalità minore del "Game Over", il Canale 4 del Rumore viene attivato sincopatamente:
```c
if (step % 8 == 0) {
NR41_REG = 0x01;
NR42_REG = 0xB2; // Decadimento rapidissimo per dare un colpo percussivo
NR43_REG = (step < 64) ? 0x68 : 0x42; // Varia la frequenza: Thud basso vs Piatto (Crash) acuto
NR44_REG = 0x80;
}
```
Questo simula il battito di un tamburo e i piatti, donando drammaticità alla scena.
+29
View File
@@ -0,0 +1,29 @@
# Fisica e Controlli del Giocatore
Il movimento nel gioco è rigidamente vincolato alla griglia (Grid-Based), come nei vecchi RPG (Zelda, Pokémon), per semplificare le collisioni e la leggibilità degli ostacoli.
## State Machine (Blocco Input)
In `player_logic.c`, l'interpolazione fluida del movimento (il passaggio da una casella all'altra) richiede 16 frame.
Durante questo lasso di tempo, la variabile `is_moving` è `true`. Qualsiasi input direzionale proveniente dai tasti (D-Pad) o dal tasto A viene totalmente ignorato finché il movimento sub-tile non si azzera (`move_progress == 16`). Questo impedisce movimenti diagonali non voluti o "scivolamenti" fuori griglia.
## Delayed Auto-Shift (DAS)
Leggere banalmente il D-Pad a 60 FPS renderebbe il personaggio incontrollabile (si muoverebbe di 4 caselle con una pressione leggermente prolungata). Leggere solo la "singola pressione" (keys_pressed) obbligherebbe a martellare il tasto per avanzare.
Abbiamo implementato un DAS, una tecnica tipica del Tetris:
- Al primo tocco, il giocatore si muove.
- Se si continua a tenere premuto, l'input viene ignorato per `DAS_DELAY` frames.
- Superato il delay, l'input si ripete in automatico ogni `DAS_REPEAT` frames.
Questo rende la navigazione dei corridoi estremamente fluida e confortevole.
## Il Salto e il Costo in Stamina
Tenendo premuto il tasto `A` assieme a una direzione direzionale, il giocatore scavalca il blocco adiacente atterrando due tile più in là.
Condizioni per il salto:
1. La casella di destinazione (`X+2`, `Y+2`) deve essere libera.
2. La casella intermedia saltata (`X+1`, `Y+1`) DEVE essere un muro (non si può saltare a vuoto sui corridoi).
3. Il giocatore deve avere almeno 60 punti Stamina.
La stamina si ricarica di 1 punto ogni secondo. Eseguire un salto costa 60 punti, disabilitando ulteriori balzi per un minuto intero. Questo lo rende una manovra evasiva salva-vita estrema e non uno strumento da spam abusivo.
Visivamente, l'altezza del salto non altera la logica (sempre 2D Grid), ma solo il rendering in `update_player_sprite()`, in cui sottraiamo al posizionamento verticale la formula parabolica `x * (16 - x) / 4`.
+23
View File
@@ -0,0 +1,23 @@
# Generazione Procedurale del Livello
## Algoritmo Depth-First Search (DFS)
Per garantire che ogni sessione di gioco sia unica, il labirinto viene generato proceduralmente ad ogni avvio (usando `DIV_REG` hardware del Game Boy come seed pseudo-casuale).
L'algoritmo utilizzato in `maze.c` è un **Depth-First Search (DFS)** con Backtracking, modellato per creare un "Perfect Maze" (labirinto perfetto in cui ogni cella è raggiungibile tramite un unico percorso possibile e senza cicli).
La mappa è concettualmente divisa in:
- Celle dispari (es. `1,1`, `1,3`): "Stanze"
- Celle pari: "Muri Divisori"
L'algoritmo parte da `1,1`, sceglie un vicino dispari casuale non visitato, abbatte il muro divisorio pari nel mezzo e avanza, salvando le posizioni in uno stack per poter tornare indietro (backtracking) quando finisce in un vicolo cieco.
## Rottura del Perfect Maze (Loop Generation)
Un "Perfect Maze" è frustrante in un gioco di inseguimento, perché intrappola il giocatore nei vicoli ciechi senza via di scampo.
Pertanto, è stata aggiunta una *Fase 2* alla generazione: l'algoritmo scansiona i muri rimanenti e, se un muro divide due stanze adiacenti, lo abbatte con una probabilità del 15%.
Questo genera anelli (loop) all'interno del labirinto, permettendo al giocatore tattiche evasive per aggirare il fantasma.
## Posizionamento del Traguardo (Vittoria)
La casella traguardo (il "Portale") deve essere logicamente il punto più lontano dalla partenza (situata in alto a sinistra a `1,1`).
Per trovarla, l'algoritmo calcola la **Distanza di Manhattan** (`|X1 - X2| + |Y1 - Y2|`) di ogni cella percorribile rispetto a `1,1`. La cella con il valore più alto viene designata con ID 2, trasformandola graficamente nel portale di fine livello.
+41
View File
@@ -0,0 +1,41 @@
# Motore Grafico e Rendering
## Proiezione Isometrica
Il Game Boy possiede un hardware progettato per griglie 2D piatte (scrolling orizzontale/verticale). Per ottenere un effetto isometrico 3D, trasformiamo matematicamente le coordinate logiche della griglia `(X, Y)` in pixel schermo `(iso_x, iso_y)`.
La formula utilizzata in `render.c` è:
```c
int8_t iso_x = (lx - ly) * 2 + 12;
int8_t iso_y = (lx + ly) * 1 + 2;
```
Questa trasformazione ruota la mappa di 45 gradi e la appiattisce sull'asse Y (rapporto 2:1, tipico dell'isometrico pixel-art).
## Ottimizzazione della Mappa
Aggiornare l'intera background map hardware del Game Boy (32x32 tiles, 1024 bytes) ad ogni "passo" del giocatore causerebbe gravissimi cali di framerate (lag) e sfarfallii visivi.
La soluzione implementata prevede di aggiornare *solo* le 16 righe visibili sullo schermo (160x144 pixel = 20x18 tiles) durante la funzione `draw_map`:
```c
set_bkg_tiles(0, 2, 32, 16, &map_buffer[2 * 32]);
```
## Fog of War e Distanza di Chebyshev
Per aumentare la tensione e oscurare il labirinto, il gioco disegna solo i pavimenti vicini al giocatore. Invece di calcolare un cerchio reale (distanza euclidea) che richiederebbe una CPU avida di risorse matematiche (radici quadrate o lookup tables), usiamo la **Distanza di Chebyshev**:
```c
int8_t dist = max(abs(dx), abs(dy));
if (dist > 2) continue; // Nascondi
```
Questo crea un quadrato di visibilità (5x5 celle) centrato sul giocatore, perfetto per le dinamiche a griglia.
## Auto-Tiling e Illuminazione
Il modulo di rendering controlla i vicini logici (Nord, Sud, Est, Ovest) di ogni casella per applicare la grafica giusta (es. muri arrotondati e non tagliati). Questo avviene tramite una maschera di bit (`mask`).
Inoltre, le celle a distanza Chebyshev pari a 2 (i bordi della visibilità) subiscono un cambio di offset grafico (`v = 32 + mask` o `v = 48 + mask`), prelevando dal tileset varianti più scure ("Tile Dark"). Questo simula l'affievolirsi della luce prima dell'oscurità totale.
## Gestione Overlapping Isometrico (Multi-Pass Rendering)
Nei giochi isometrici basati su tile, la proiezione genera sovrapposizioni (overlapping) tra i macro-blocchi. Dato che il background del Game Boy non supporta vere trasparenze hardware (un tile sovrascrive interamente quello sottostante), i tile disegnati per ultimi "tagliano" quelli precedenti con i loro angoli piatti.
Per preservare la complessa grafica 3D del traguardo (le scale) ed evitare l'esaurimento della ristrettissima VRAM (limite di 256 tile) tipico degli scenari pre-renderizzati combinatori, l'engine utilizza una strategia ibrida:
1. **Bitmasking:** I pavimenti piatti ricolorano dinamicamente i propri angoli trasparenti in base al colore dei vicini, creando l'illusione di un ritaglio perfetto.
2. **Painter's Algorithm (Pass Multiplo):** I pavimenti vengono renderizzati nel Pass 1, mentre gli oggetti complessi (come la casella di Vittoria) vengono disegnati rigorosamente in un Pass 2 successivo. Disegnandola per ultima, la scala sovrascrive i pavimenti frontali ma, grazie al Bitmasking dei propri angoli inferiori, si fonde con essi in modo invisibile. Ciò preserva interamente il disegno 3D senza richiedere sprite hardware addizionali (i quali causerebbero "flickering" per il rigido limite hardware di 10 sprite per scanline orizzontale).
+25
View File
@@ -0,0 +1,25 @@
# Game Boy Iso-Engine: Documentazione Tecnica
Benvenuto nella documentazione tecnica del progetto. Questo motore per Game Boy a 8-bit è stato progettato per dimostrare come tecniche avanzate (prospettiva isometrica, fog of war, generazione procedurale e intelligenza artificiale) possano essere implementate sulle ristrette risorse hardware del Game Boy (CPU Sharp LR35902 a 4.19 MHz, 8KB WRAM).
## Indice dei Capitoli
La documentazione è suddivisa in file separati, ognuno dedicato a un aspetto specifico dell'engine.
1. **[Architettura di Base](architecture.md)**
Struttura del codice, organizzazione in moduli (src, assets, scripts) e gestione dello stato globale per evitare dipendenze circolari in C.
2. **[Motore Grafico e Rendering](graphics.md)**
La matematica dietro la proiezione isometrica, l'ottimizzazione dell'auto-tiling, il sistema "Fog of War" basato sulla distanza di Chebyshev e la gestione dei metasprite.
3. **[Generazione Procedurale del Livello](generation.md)**
Come l'algoritmo Depth-First Search (DFS) crea un labirinto perfetto, come vengono generati i loop per il gameplay e come la distanza di Manhattan posiziona il traguardo.
4. **[Fisica e Controlli del Giocatore](gameplay.md)**
Il sistema di input con Delayed Auto-Shift (DAS), la State Machine rigida per l'allineamento alla griglia, e la complessa meccanica del salto con traiettoria parabolica e costo in Stamina.
5. **[Intelligenza Artificiale (Il Fantasma)](ai.md)**
Perché il nemico usa un algoritmo Greedy invece di A*, il sistema di cooldown dei movimenti per bilanciare il gameplay, e la hitbox "Pixel-Perfect" che scatena il Game Over.
6. **[Sintesi Audio e Musica](audio.md)**
Come l'engine manipola direttamente i registri hardware APU (Audio Processing Unit) per creare arpeggi finti, percussioni bianche (noise channel) e melodie atmosferiche.
-971
View File
@@ -1,971 +0,0 @@
#include <gb/gb.h>
#include <gbdk/font.h>
#include <stdint.h>
#include <string.h>
#include <rand.h>
#include "engine.h"
// Generated by png2asset
#include "tiles.h"
#include "player.h"
#include "gameover.h"
#include "stamina.h"
#include "title_bg.h"
// Music variables
static volatile uint8_t music_timer = 0;
static volatile uint8_t music_step = 0;
volatile uint8_t game_over = 0;
volatile uint8_t game_over_timer = 0;
// Game Over creepy melody variables
static volatile uint8_t gameover_music_timer = 0;
static volatile uint8_t gameover_music_step = 0;
// Notes frequencies
#define R 0
#define N_E2 458
#define N_F2 547
#define N_G2 742
#define N_A2 857
#define N_AS2 907
#define N_B2 961
#define N_C3 1046
#define N_CS3 1103
#define N_D3 1155
#define N_E3 1253
#define N_F3 1298
#define N_FS3 1340
#define N_G3 1380
#define N_GS3 1417
#define N_A3 1453
#define N_AS3 1486
#define N_B3 1517
#define N_C4 1547
#define N_CS4 1575
#define N_D4 1602
#define N_E4 1651
#define N_F4 1673
#define N_FS4 1694
#define N_G4 1714
#define N_GS4 1732
#define N_A4 1751
#define N_AS4 1767
#define N_B4 1783
#define N_C5 1798
#define N_CS5 1812
#define N_D5 1825
#define N_E5 1849
#define N_F5 1860
#define N_FS5 1871
#define N_G5 1881
#define N_GS5 1890
#define N_A5 1899
const uint16_t ch1_seq[128] = {
// 1 Dm
N_D3, N_A3, N_D4, N_A3, N_D3, N_A3, N_D4, N_A3,
// 2 A/C#
N_CS3, N_A3, N_CS4, N_A3, N_CS3, N_A3, N_CS4, N_A3,
// 3 F/C
N_C3, N_A3, N_C4, N_A3, N_C3, N_A3, N_C4, N_A3,
// 4 G/B
N_B2, N_G3, N_B3, N_G3, N_B2, N_G3, N_B3, N_G3,
// 5 Bb
N_AS2, N_F3, N_AS3, N_F3, N_AS2, N_F3, N_AS3, N_F3,
// 6 F/A
N_A2, N_F3, N_A3, N_F3, N_A2, N_F3, N_A3, N_F3,
// 7 Gm
N_G2, N_D3, N_G3, N_D3, N_G2, N_D3, N_G3, N_D3,
// 8 A7
N_A2, N_E3, N_A3, N_CS4, N_A2, N_E3, N_A3, N_CS4,
// 9 Dm (intensifies: higher arpeggio)
N_D3, N_D4, N_F4, N_A4, N_D3, N_D4, N_F4, N_A4,
// 10 A/C#
N_CS3, N_CS4, N_E4, N_A4, N_CS3, N_CS4, N_E4, N_A4,
// 11 F/C
N_C3, N_C4, N_F4, N_A4, N_C3, N_C4, N_F4, N_A4,
// 12 G/B
N_B2, N_B3, N_D4, N_G4, N_B2, N_B3, N_D4, N_G4,
// 13 Bb
N_AS2, N_AS3, N_D4, N_F4, N_AS2, N_AS3, N_D4, N_F4,
// 14 F/A
N_A2, N_A3, N_C4, N_F4, N_A2, N_A3, N_C4, N_F4,
// 15 Gm
N_G2, N_G3, N_AS3, N_D4, N_G2, N_G3, N_AS3, N_D4,
// 16 A7 -> Dm resolution
N_A2, N_A3, N_CS4, N_E4, N_A2, R, N_D3, R
};
const uint16_t ch2_seq[128] = {
// 1 Dm
N_F4, R, R, R, N_E4, R, R, R,
// 2 A/C#
N_E4, R, R, R, N_D4, R, R, R,
// 3 F/C
N_C4, R, R, R, N_D4, R, N_E4, R,
// 4 G/B
N_D4, R, R, R, N_F4, R, R, R,
// 5 Bb
N_D4, R, R, R, N_C4, R, R, R,
// 6 F/A
N_C4, R, R, R, N_AS3, R, R, R,
// 7 Gm
N_AS3, R, N_A3, N_AS3, N_C4, R, N_AS3, R,
// 8 A7
N_A3, R, R, R, R, R, R, R,
// 9 Dm
N_A4, R, R, R, N_G4, R, N_F4, N_E4,
// 10 A/C#
N_G4, R, R, R, N_F4, R, N_E4, N_D4,
// 11 F/C
N_F4, R, R, R, N_E4, R, N_C4, R,
// 12 G/B
N_D4, R, R, R, N_G4, R, R, R,
// 13 Bb
N_F4, R, R, R, N_E4, R, N_D4, N_CS4,
// 14 F/A
N_E4, R, R, R, N_D4, R, N_C4, N_B3,
// 15 Gm
N_D4, R, R, R, N_C4, R, N_AS3, R,
// 16 A7 -> Dm resolution
N_A3, R, R, R, R, R, N_D4, R
};
// Eerie pulse note frequencies (96-note sequence for normal gameplay)
const uint16_t eerie_reg_vals[] = {
// Pattern 1 (A minor)
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
// Pattern 2 (D minor)
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
// Pattern 3 (E7 tension)
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497
};
// Title Music Variables
static volatile uint8_t title_music_timer = 0;
static volatile uint8_t title_music_step = 0;
const uint16_t title_melody[32] = {
N_A2, N_E3, N_A3, N_E3, N_C4, N_A3, N_E3, N_A2, // Am
N_G2, N_D3, N_G3, N_D3, N_B3, N_G3, N_D3, N_G2, // G
N_F2, N_C3, N_F3, N_C3, N_A3, N_F3, N_C3, N_F2, // F
N_E2, N_B2, N_E3, N_B2, N_GS3, N_E3, N_B2, N_E2 // E
};
static void play_note(uint16_t reg_val) {
NR21_REG = 0x80; // 50% duty cycle
NR22_REG = 0x73; // volume 7, fade envelope
NR23_REG = reg_val & 0xFF;
NR24_REG = (reg_val >> 8) | 0x80; // trigger note
}
static void play_gameover_step(uint8_t step) {
if (step < 128) {
uint16_t n1 = ch1_seq[step];
if (n1 != R) {
NR10_REG = 0x00; // No sweep
NR11_REG = 0x80; // 50% duty
NR12_REG = 0x73; // volume 7, fast fade for arpeggio pluck
NR13_REG = n1 & 0xFF;
NR14_REG = (n1 >> 8) | 0x80;
}
uint16_t n2 = ch2_seq[step];
if (n2 != R) {
NR21_REG = 0x80; // 50% duty
NR22_REG = 0xA7; // volume 10, long fade for melody sustain
NR23_REG = n2 & 0xFF;
NR24_REG = (n2 >> 8) | 0x80;
}
// Add dramatic percussion on the first beat of each chord (every 8 steps)
if (step % 8 == 0) {
NR41_REG = 0x01;
NR42_REG = 0xB2; // Volume 11, quick fade
// step < 64 gets a lower tympani, step >= 64 gets a crash
if (step < 64) {
NR43_REG = 0x68; // Low thud
} else {
NR43_REG = 0x42; // Crash
}
NR44_REG = 0x80;
}
}
}
static void play_music_tick(void) {
if (app_state == 0) {
title_music_timer++;
if (title_music_timer >= 30) { // 30 frames per note
title_music_timer = 0;
uint16_t n = title_melody[title_music_step];
NR10_REG = 0;
NR11_REG = 0x80;
NR12_REG = 0x63; // soft fade
NR13_REG = n & 0xFF;
NR14_REG = (n >> 8) | 0x80;
// Add a slow bass note every 4 steps
if (title_music_step % 4 == 0) {
NR21_REG = 0x80;
NR22_REG = 0x77;
uint16_t bn = n / 2; // Octave lower
NR23_REG = bn & 0xFF;
NR24_REG = (bn >> 8) | 0x80;
}
title_music_step++;
if (title_music_step >= 32) title_music_step = 0;
}
return;
}
if (game_over) {
if (game_over_timer == 0) {
gameover_music_timer++;
if (gameover_music_timer >= 10) { // 10 frames per note (~6 notes/sec)
gameover_music_timer = 0;
if (gameover_music_step < 128) {
play_gameover_step(gameover_music_step);
gameover_music_step++;
}
}
}
return;
}
music_timer++;
if (music_timer >= 20) {
music_timer = 0;
play_note(eerie_reg_vals[music_step]);
music_step++;
if (music_step >= 96) {
music_step = 0;
}
}
}
#define MAP_SIZE 7
uint8_t maze[MAP_SIZE][MAP_SIZE];
static uint8_t map_buffer[32 * 32];
uint8_t player_lx = 1;
uint8_t player_ly = 1;
uint8_t player_dir = 0; // 0=DR, 1=DL, 2=UL, 3=UR
uint8_t scroll_x = 0;
uint8_t scroll_y = 0;
// Movement transition variables
uint8_t is_moving = 0;
uint8_t is_jumping = 0;
uint8_t stamina = 100;
uint8_t stamina_recharge_timer = 0;
uint8_t move_progress = 0;
int8_t start_lx, start_ly;
int8_t target_lx, target_ly;
int16_t start_px, start_py;
int16_t target_px, target_py;
// DAS variables
uint8_t das_timer = 0;
uint8_t das_active = 0;
#define DAS_DELAY 12
#define DAS_REPEAT 6
// Enemy variables
uint8_t enemy_lx = 0;
uint8_t enemy_ly = 0;
uint8_t enemy_is_moving = 0;
uint8_t enemy_move_progress = 0;
int8_t enemy_start_lx, enemy_start_ly;
int8_t enemy_target_lx, enemy_target_ly;
int16_t enemy_start_px, enemy_start_py;
int16_t enemy_target_px, enemy_target_py;
uint8_t enemy_cooldown = 0;
const metasprite_t enemy_metasprite[] = {
METASPR_ITEM(-8, -8, 0, S_PALETTE),
METASPR_ITEM(0, 8, 2, S_PALETTE),
METASPR_TERM
};
static void generate_maze(void) {
// Clear maze (all 0/walls)
memset(maze, 0, sizeof(maze));
// Stack for backtracking (7x7 grid of odd cells has 49 cells max)
uint8_t stack_x[49];
uint8_t stack_y[49];
uint8_t stack_ptr = 0;
// Start at (1, 1)
uint8_t cx = 1;
uint8_t cy = 1;
maze[cy][cx] = 1;
while (1) {
// Find unvisited neighbors at distance 2
// Up: (cx, cy-2), Down: (cx, cy+2), Left: (cx-2, cy), Right: (cx+2, cy)
uint8_t nx[4];
uint8_t ny[4];
uint8_t count = 0;
// Up
if (cy >= 3 && maze[cy - 2][cx] == 0) {
nx[count] = cx; ny[count] = cy - 2; count++;
}
// Down
if (cy <= MAP_SIZE - 4 && maze[cy + 2][cx] == 0) {
nx[count] = cx; ny[count] = cy + 2; count++;
}
// Left
if (cx >= 3 && maze[cy][cx - 2] == 0) {
nx[count] = cx - 2; ny[count] = cy; count++;
}
// Right
if (cx <= MAP_SIZE - 4 && maze[cy][cx + 2] == 0) {
nx[count] = cx + 2; ny[count] = cy; count++;
}
if (count > 0) {
// Choose a random neighbor
uint8_t dir = rand() % count;
// Push current cell to stack
stack_x[stack_ptr] = cx;
stack_y[stack_ptr] = cy;
stack_ptr++;
// Remove wall between current cell and chosen neighbor
maze[(cy + ny[dir]) / 2][(cx + nx[dir]) / 2] = 1;
// Move to neighbor
cx = nx[dir];
cy = ny[dir];
maze[cy][cx] = 1;
} else if (stack_ptr > 0) {
// Pop from stack
stack_ptr--;
cx = stack_x[stack_ptr];
cy = stack_y[stack_ptr];
} else {
break;
}
}
// Reopen some walls to create alternative paths/loops
for (uint8_t y = 1; y < MAP_SIZE - 1; y++) {
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
if (maze[y][x] == 0) {
// If it connects two paths horizontally or vertically, open it with a 15% probability
if ((maze[y][x - 1] == 1 && maze[y][x + 1] == 1) ||
(maze[y - 1][x] == 1 && maze[y + 1][x] == 1)) {
if ((rand() % 100) < 15) {
maze[y][x] = 1;
}
}
}
}
}
}
static void update_stamina_display(void) {
if (game_over) {
// Hide the 5 stamina bar sprites when game is over
for (uint8_t i = 0; i < 5; i++) {
move_sprite(18 + i, 0, 0);
}
return;
}
// Position 5 sprites at top-right of the screen (fixed, completely ignores scroll)
move_sprite(18, (uint8_t)(112 + 8), 16);
move_sprite(19, (uint8_t)(120 + 8), 16);
move_sprite(20, (uint8_t)(128 + 8), 16);
move_sprite(21, (uint8_t)(136 + 8), 16);
move_sprite(22, (uint8_t)(144 + 8), 16);
uint16_t temp = (uint16_t)stamina * 40;
uint8_t num_pixels = temp / 100;
uint8_t base_tile = player_TILE_COUNT + gameover_TILE_COUNT; // 44
for (uint8_t i = 0; i < 5; i++) {
uint8_t tile_idx;
if (i == 0) {
tile_idx = (num_pixels >= 8) ? 11 : 9; // Left Cap full (11) or empty (9)
} else if (i == 4) {
tile_idx = (num_pixels >= 40) ? 12 : 10; // Right Cap full (12) or empty (10)
} else {
int8_t seg_px = (int8_t)num_pixels - (i * 8);
if (seg_px < 0) seg_px = 0;
if (seg_px > 8) seg_px = 8;
tile_idx = seg_px; // middle segments (0..8 px)
}
set_sprite_tile(18 + i, base_tile + tile_idx * 2);
}
}
static void draw_map(uint8_t center_x, uint8_t center_y) {
// Fill entire map with tile index 0 (completely black empty tile)
memset(map_buffer, 0, sizeof(map_buffer));
int8_t start_x = center_x - 2;
if (start_x < 0) start_x = 0;
int8_t end_x = center_x + 2;
if (end_x >= MAP_SIZE) end_x = MAP_SIZE - 1;
int8_t start_y = center_y - 2;
if (start_y < 0) start_y = 0;
int8_t end_y = center_y + 2;
if (end_y >= MAP_SIZE) end_y = MAP_SIZE - 1;
// Draw logical map path tiles in the visible 5x5 window
for (int8_t ly = start_y; ly <= end_y; ly++) {
for (int8_t lx = start_x; lx <= end_x; lx++) {
if (maze[ly][lx] == 1) {
// Calculate Chebyshev distance to center
int8_t dx = lx - center_x;
int8_t dy = ly - center_y;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int8_t dist = (dx > dy) ? dx : dy;
// Hide tiles at distance > 2 (Fog of War)
if (dist > 2) continue;
// Centering math for 15x15 map on 32x32 background map
int8_t iso_x = (lx - ly) * 2 + 12;
int8_t iso_y = (lx + ly) * 1 + 2;
// Alternate colors (checkerboard)
uint8_t is_alt = ((lx + ly) % 2 == 0);
// Calculate neighbor mask
uint8_t has_tl = (lx > 0 && maze[ly][lx - 1] == 1);
uint8_t has_tr = (ly > 0 && maze[ly - 1][lx] == 1);
uint8_t has_bl = (ly < MAP_SIZE - 1 && maze[ly + 1][lx] == 1);
uint8_t has_br = (lx < MAP_SIZE - 1 && maze[ly][lx + 1] == 1);
uint8_t mask = (has_tl ? 1 : 0) | (has_tr ? 2 : 0) | (has_bl ? 4 : 0) | (has_br ? 8 : 0);
// Determine variation style based on lighting distance
uint8_t v;
if (dist == 2) {
// Darker variants (Tile 1 Dark starting at 32, Tile 2 Dark starting at 48)
v = is_alt ? (48 + mask) : (32 + mask);
} else {
// Normal variants
v = is_alt ? (16 + mask) : mask;
}
for (uint8_t y = 0; y < 2; y++) {
for (uint8_t x = 0; x < 4; x++) {
uint8_t target_x = (iso_x + x) & 31;
uint8_t target_y = (iso_y + y) & 31;
uint8_t src_tile = tiles_map[4 + v * 8 + y * 4 + x];
map_buffer[target_y * 32 + target_x] = src_tile;
}
}
}
}
}
update_stamina_display();
// Optimize: only write rows 2 to 17 (16 rows total) which contain the active isometric map, avoiding full 1024-byte copy lags
set_bkg_tiles(0, 2, 32, 16, &map_buffer[2 * 32]);
}
static void update_camera(void) {
// Center the camera on the player's logical tile coordinates
int16_t px = (player_lx - player_ly) * 16 + 96;
int16_t py = (player_lx + player_ly) * 8 + 16;
scroll_x = px - 64;
scroll_y = py - 72;
move_bkg(scroll_x, scroll_y);
}
static void update_player_sprite(void) {
uint8_t frame_offset = is_moving ? ((move_progress >> 2) & 1) : 0;
uint8_t y_offset = 0;
if (is_jumping) {
// Parabolic arc for the jump height: peak is 16 pixels at frame 8
y_offset = (move_progress * (16 - move_progress)) >> 2;
}
move_metasprite(player_metasprites[player_dir * 2 + frame_offset], 0, 0, 88, 88 - y_offset);
}
void title_init(void) {
// Hide all sprites
for (uint8_t i = 0; i < 40; i++) move_sprite(i, 0, 0);
// Load title background tiles
set_bkg_data(0, title_bg_TILE_COUNT, title_bg_tiles);
// Draw title map
set_bkg_tiles(0, 0, title_bg_WIDTH / 8, title_bg_HEIGHT / 8, title_bg_map);
// Set standard palette
BGP_REG = 0xE4;
// Center screen
move_bkg(0, 0);
// Initialize sound registers
NR52_REG = 0x80; // Turn on sound
NR50_REG = 0x77; // Max master volume
NR51_REG = 0xFF; // Enable all sound channels
// Start title music
title_music_timer = 0;
title_music_step = 0;
remove_VBL(play_music_tick);
add_VBL(play_music_tick);
}
void title_update(uint8_t keys, uint8_t prev_keys) {
// Only handling music and waiting for start, which is handled in main.c and play_music_tick
}
void engine_init(void) {
SPRITES_8x16;
// Initialize sound registers
NR52_REG = 0x80; // Turn on sound
NR50_REG = 0x77; // Max master volume
NR51_REG = 0xFF; // Enable all sound channels
// Load native GBDK IBM font (loads starting at VRAM index 0x80)
font_init();
font_t ibm_font = font_load(font_ibm);
font_set(ibm_font);
// Reset music step counters
music_timer = 0;
music_step = 0;
gameover_music_timer = 0;
gameover_music_step = 0;
remove_VBL(play_music_tick);
add_VBL(play_music_tick);
// Seed random number generator with hardware divider register
initrand(DIV_REG);
// Generate maze path cells
generate_maze();
// Explicitly initialize DMG palette registers
OBP0_REG = 0xE4; // 11 10 01 00 (Black, Dark Gray, Light Gray, White)
OBP1_REG = 0x1B; // 00 01 10 11 (Inverted palette for the ghost enemy)
BGP_REG = 0xE4;
// Initialize GBC palettes (safe on DMG, does nothing)
set_bkg_palette(0, 8, tiles_palettes);
set_sprite_palette(0, 8, player_palettes);
// Load tiles
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles);
set_sprite_data(0, player_TILE_COUNT, player_tiles);
set_sprite_data(player_TILE_COUNT, gameover_TILE_COUNT, gameover_tiles);
set_sprite_data(player_TILE_COUNT + gameover_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles);
player_lx = 1;
player_ly = 1;
// Safety check: if (1,1) is somehow a wall, scan the maze and start at the first path tile
if (maze[player_ly][player_lx] == 0) {
uint8_t found = 0;
for (uint8_t y = 1; y < MAP_SIZE - 1; y++) {
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
if (maze[y][x] == 1) {
player_lx = x;
player_ly = y;
found = 1;
break;
}
}
if (found) break;
}
}
// Spawn enemy at a random path tile sufficiently far from the player (Chebyshev distance >= 3)
while (1) {
uint8_t rx = rand() % MAP_SIZE;
uint8_t ry = rand() % MAP_SIZE;
if (maze[ry][rx] == 1) {
int8_t dx = (int8_t)rx - (int8_t)player_lx;
int8_t dy = (int8_t)ry - (int8_t)player_ly;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int8_t dist = (dx > dy) ? dx : dy;
if (dist >= 3) {
enemy_lx = rx;
enemy_ly = ry;
break;
}
}
}
enemy_is_moving = 0;
enemy_cooldown = 60;
game_over = 0;
stamina = 100;
stamina_recharge_timer = 0;
is_jumping = 0;
draw_map(player_lx, player_ly);
update_camera();
update_player_sprite();
}
void engine_update(uint8_t keys, uint8_t prev_keys) {
if (game_over) {
if (game_over_timer > 0) {
game_over_timer--;
if (game_over_timer == 0) {
// Clear map_buffer to 0 (completely black/empty)
memset(map_buffer, 0, sizeof(map_buffer));
set_bkg_tiles(0, 0, 32, 32, map_buffer);
// Initialize gameover music step variables
gameover_music_timer = 45;
gameover_music_step = 0;
// Play a sad defeat melody / note on Channel 2
NR21_REG = 0x80;
NR22_REG = 0xF3; // volume 15, fade
NR23_REG = 0x2C; // low frequency
NR24_REG = 0x84; // trigger note
}
} else {
// Render the "GAME OVER" metasprite centered horizontally (88, due to sprite X-offset) and below player (120)
move_metasprite(gameover_metasprites[0], player_TILE_COUNT, 8, 88, 120);
// Keep the player sprite visible at center screen (88, 88)
update_player_sprite();
// Render enemy sprite at its final screen coordinate relative to camera scroll
int16_t enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
int16_t enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24;
int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16;
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
if (edx < 0) edx = -edx;
if (edy < 0) edy = -edy;
uint8_t ep_dist = (edx > edy) ? edx : edy;
if (ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
move_metasprite(enemy_metasprite, 0, 4, enemy_screen_x, enemy_screen_y);
} else {
move_metasprite(enemy_metasprite, 0, 4, 0, 0);
}
// Check for restart via J_START
if ((keys & J_START) && !(prev_keys & J_START)) {
// Hide game over metasprite by moving offscreen
move_metasprite(gameover_metasprites[0], player_TILE_COUNT, 8, 0, 0);
engine_init();
}
}
return;
}
// Stamina recharge: 1 point per second (every 60 frames)
stamina_recharge_timer++;
if (stamina_recharge_timer >= 60) {
stamina_recharge_timer = 0;
if (stamina < 100) {
stamina++;
update_stamina_display();
}
}
if (is_moving) {
move_progress++;
// Interpolate camera position
int16_t px = start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4);
int16_t py = start_py + (((target_py - start_py) * (int16_t)move_progress) >> 4);
scroll_x = px - 64;
scroll_y = py - 72;
move_bkg(scroll_x, scroll_y);
// Update walk animation frame (alternate frame every 4 ticks)
update_player_sprite();
// Update visible tiles (fog of war center) mid-way through movement
if (move_progress == 8) {
draw_map(target_lx, target_ly);
}
if (move_progress == 16) {
is_moving = 0;
is_jumping = 0;
player_lx = target_lx;
player_ly = target_ly;
// Final snap to target position to avoid any rounding errors
int16_t final_px = (player_lx - player_ly) * 16 + 96;
int16_t final_py = (player_lx + player_ly) * 8 + 16;
scroll_x = final_px - 64;
scroll_y = final_py - 72;
move_bkg(scroll_x, scroll_y);
// Draw map at final coordinates
draw_map(player_lx, player_ly);
// Set player to idle stand frame
update_player_sprite();
}
}
// Update enemy movement progress
if (enemy_is_moving) {
enemy_move_progress++;
if (enemy_move_progress == 16) {
enemy_is_moving = 0;
enemy_lx = enemy_target_lx;
enemy_ly = enemy_target_ly;
enemy_cooldown = 60; // Delay between steps to make the enemy move slower than the player
}
}
// Update enemy cooldown
if (enemy_cooldown > 0) {
enemy_cooldown--;
}
// Enemy AI pathfinding toward the player
if (!enemy_is_moving && enemy_cooldown == 0) {
// Calculate Chebyshev distance to player
int8_t dx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t dy = (int8_t)player_ly - (int8_t)enemy_ly;
int8_t abs_dx = (dx < 0) ? -dx : dx;
int8_t abs_dy = (dy < 0) ? -dy : dy;
int8_t dist = (abs_dx > abs_dy) ? abs_dx : abs_dy;
if (dist <= 2) {
int8_t best_nx = enemy_lx;
int8_t best_ny = enemy_ly;
// Initialize with current squared distance so we only move if we get strictly closer
int16_t min_dist_sq = (int16_t)dx * dx + (int16_t)dy * dy;
// Check all 4 directions and choose the one that minimizes the squared distance to the player
// Down-Right (lx + 1, ly)
if (enemy_lx + 1 < MAP_SIZE && maze[enemy_ly][enemy_lx + 1] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)(enemy_lx + 1);
int8_t ndy = (int8_t)player_ly - (int8_t)enemy_ly;
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx + 1; best_ny = enemy_ly; }
}
// Down-Left (lx, ly + 1)
if (enemy_ly + 1 < MAP_SIZE && maze[enemy_ly + 1][enemy_lx] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t ndy = (int8_t)player_ly - (int8_t)(enemy_ly + 1);
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx; best_ny = enemy_ly + 1; }
}
// Up-Left (lx - 1, ly)
if (enemy_lx > 0 && maze[enemy_ly][enemy_lx - 1] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)(enemy_lx - 1);
int8_t ndy = (int8_t)player_ly - (int8_t)enemy_ly;
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx - 1; best_ny = enemy_ly; }
}
// Up-Right (lx, ly - 1)
if (enemy_ly > 0 && maze[enemy_ly - 1][enemy_lx] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t ndy = (int8_t)player_ly - (int8_t)(enemy_ly - 1);
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx; best_ny = enemy_ly - 1; }
}
if (best_nx != (int8_t)enemy_lx || best_ny != (int8_t)enemy_ly) {
enemy_is_moving = 1;
enemy_move_progress = 0;
enemy_start_lx = enemy_lx;
enemy_start_ly = enemy_ly;
enemy_target_lx = best_nx;
enemy_target_ly = best_ny;
enemy_start_px = (enemy_start_lx - enemy_start_ly) * 16 + 96;
enemy_start_py = (enemy_start_lx + enemy_start_ly) * 8 + 16;
enemy_target_px = (enemy_target_lx - enemy_target_ly) * 16 + 96;
enemy_target_py = (enemy_target_lx + enemy_target_ly) * 8 + 16;
}
}
}
// Render enemy sprite relative to the scroll viewport with centering offset
int16_t enemy_px, enemy_py;
if (enemy_is_moving) {
enemy_px = enemy_start_px + (((enemy_target_px - enemy_start_px) * (int16_t)enemy_move_progress) >> 4);
enemy_py = enemy_start_py + (((enemy_target_py - enemy_start_py) * (int16_t)enemy_move_progress) >> 4);
} else {
enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
}
// Add player centering offset (+24, +16) to align with player sprite at (88, 88), wrapping coordinates to 256
int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24;
int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16;
// Determine player-enemy distance for visibility masking
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
if (edx < 0) edx = -edx;
if (edy < 0) edy = -edy;
uint8_t ep_dist = (edx > edy) ? edx : edy;
// Render only if within range (<= 2) to let player see the ghost when visible in fog of war
if (ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
move_metasprite(enemy_metasprite, 0, 4, enemy_screen_x, enemy_screen_y); // Sprite index offset 4
} else {
move_metasprite(enemy_metasprite, 0, 4, 0, 0); // Hide offscreen
}
// Get current interpolated player coordinates
int16_t p_px = is_moving ? (start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4)) : ((player_lx - player_ly) * 16 + 96);
int16_t p_py = is_moving ? (start_py + (((target_py - start_py) * (int16_t)move_progress) >> 4)) : ((player_lx + player_ly) * 8 + 16);
// Collision detection: check actual pixel distance (sprites overlap)
int16_t dx_collision = p_px - enemy_px;
int16_t dy_collision = p_py - enemy_py;
if (dx_collision < 0) dx_collision = -dx_collision;
if (dy_collision < 0) dy_collision = -dy_collision;
if (dx_collision < 12 && dy_collision < 6) {
game_over = 1;
game_over_timer = 45; // 45 frames delay
update_stamina_display(); // Hide stamina immediately
// Play defeat sweep SFX on Channel 1 (slide pitch down)
NR10_REG = 0x1E; // sweep register: shift down
NR11_REG = 0x10; // 25% duty cycle
NR12_REG = 0xF3; // initial volume 15, envelope decrease
NR13_REG = 0x00; // low frequency bits
NR14_REG = 0xC6; // trigger note, frequency high bits
}
uint8_t keys_pressed = keys & ~prev_keys;
if (keys_pressed) {
das_timer = DAS_DELAY;
das_active = 1;
} else if (keys && das_active) {
if (das_timer > 0) {
das_timer--;
}
if (das_timer == 0 && !is_moving) {
das_timer = DAS_REPEAT;
keys_pressed = keys; // trigger move
}
} else {
das_active = 0;
}
if (is_moving) {
return; // Ignore other inputs while moving
}
int8_t move_lx = 0;
int8_t move_ly = 0;
if (keys_pressed & J_RIGHT) {
move_lx = 1; player_dir = 0; // DR
} else if (keys_pressed & J_LEFT) {
move_lx = -1; player_dir = 2; // UL
} else if (keys_pressed & J_DOWN) {
move_ly = 1; player_dir = 1; // DL
} else if (keys_pressed & J_UP) {
move_ly = -1; player_dir = 3; // UR
}
if (move_lx != 0 || move_ly != 0) {
// If J_A is held down, attempt to jump over a wall
if (keys & J_A) {
int8_t land_lx = player_lx + move_lx * 2;
int8_t land_ly = player_ly + move_ly * 2;
// Bounds check for intermediate and landing tiles
if (land_lx >= 0 && land_lx < MAP_SIZE && land_ly >= 0 && land_ly < MAP_SIZE) {
// intermediate tile must be a wall (0), landing tile must be a path (1), and we need >= 60 stamina
if (maze[player_ly + move_ly][player_lx + move_lx] == 0 && maze[land_ly][land_lx] == 1 && stamina >= 60) {
is_moving = 1;
is_jumping = 1;
move_progress = 0;
stamina -= 60;
start_lx = player_lx;
start_ly = player_ly;
target_lx = land_lx;
target_ly = land_ly;
start_px = (start_lx - start_ly) * 16 + 96;
start_py = (start_lx + start_ly) * 8 + 16;
target_px = (target_lx - target_ly) * 16 + 96;
target_py = (target_lx + target_ly) * 8 + 16;
// Play jump SFX (sweep up on Channel 1)
NR10_REG = 0x15; // sweep up
NR11_REG = 0x80; // 50% duty cycle
NR12_REG = 0xF3; // volume 15, fade
NR13_REG = 0x00; // low frequency bits
NR14_REG = 0xC3; // trigger note, frequency high bits
update_stamina_display();
update_player_sprite();
return;
}
}
// If jump checks fail, just face direction
update_player_sprite();
} else {
// Normal walk
int8_t new_lx = player_lx + move_lx;
int8_t new_ly = player_ly + move_ly;
// Bounds check & wall/empty area collision check
if (new_lx >= 0 && new_lx < MAP_SIZE && new_ly >= 0 && new_ly < MAP_SIZE) {
if (maze[new_ly][new_lx] == 1) {
is_moving = 1;
move_progress = 0;
start_lx = player_lx;
start_ly = player_ly;
target_lx = new_lx;
target_ly = new_ly;
start_px = (start_lx - start_ly) * 16 + 96;
start_py = (start_lx + start_ly) * 8 + 16;
target_px = (target_lx - target_ly) * 16 + 96;
target_py = (target_lx + target_ly) * 8 + 16;
update_player_sprite();
} else {
update_player_sprite();
}
} else {
update_player_sprite();
}
}
}
}
-444
View File
@@ -1,444 +0,0 @@
import os
from PIL import Image, ImageDraw
# Standard Game Boy palette
# 0: White, 1: Light Gray, 2: Dark Gray, 3: Black
PALETTE = [
255, 255, 255, # 0
170, 170, 170, # 1
85, 85, 85, # 2
0, 0, 0 # 3
] + [0] * (256 * 3 - 12)
def draw_autotile(draw, y_offset, is_alt, mask, is_dark=False):
# Determine the neighbor fill color
# Tile 1 (Normal, is_alt=False) neighbors should match Tile 2 (Alt) body color: 1 (Light Gray)
# Tile 2 (Alt, is_alt=True) neighbors should match Tile 1 (Normal) body color: 0 (White)
if not is_dark:
neighbor_color = 0 if is_alt else 1
tl_color = neighbor_color if (mask & 1) else 3
tr_color = neighbor_color if (mask & 2) else 3
dl_color = neighbor_color if (mask & 4) else 3
dr_color = neighbor_color if (mask & 8) else 3
# Draw corners
draw.polygon([(0, y_offset), (15, y_offset), (0, y_offset + 7)], fill=tl_color)
draw.polygon([(16, y_offset), (31, y_offset), (31, y_offset + 7)], fill=tr_color)
draw.polygon([(0, y_offset + 8), (0, y_offset + 15), (15, y_offset + 15)], fill=dl_color)
draw.polygon([(31, y_offset + 8), (31, y_offset + 15), (16, y_offset + 15)], fill=dr_color)
# Draw the main diamond shape on top
points = [(15, y_offset), (31, y_offset + 7), (16, y_offset + 15), (0, y_offset + 8)]
inner = [(15, y_offset + 4), (27, y_offset + 7), (16, y_offset + 11), (4, y_offset + 8)]
if not is_alt:
# Tile 1: Normal floor (body is White, inner is Light Gray)
draw.polygon(points, fill=0, outline=2)
draw.polygon(inner, fill=1, outline=2)
else:
# Tile 2: Alt floor (body is Light Gray, inner is White)
draw.polygon(points, fill=1, outline=2)
draw.polygon(inner, fill=0, outline=2)
else:
# Darker version: Shift colors down
# White (0) -> Light Gray (1)
# Light Gray (1) -> Dark Gray (2)
# Dark Gray (2) -> Black (3)
# Black (3) -> Black (3)
neighbor_color = 1 if is_alt else 2
tl_color = neighbor_color if (mask & 1) else 3
tr_color = neighbor_color if (mask & 2) else 3
dl_color = neighbor_color if (mask & 4) else 3
dr_color = neighbor_color if (mask & 8) else 3
# Draw corners
draw.polygon([(0, y_offset), (15, y_offset), (0, y_offset + 7)], fill=tl_color)
draw.polygon([(16, y_offset), (31, y_offset), (31, y_offset + 7)], fill=tr_color)
draw.polygon([(0, y_offset + 8), (0, y_offset + 15), (15, y_offset + 15)], fill=dl_color)
draw.polygon([(31, y_offset + 8), (31, y_offset + 15), (16, y_offset + 15)], fill=dr_color)
# Draw the main diamond shape on top
points = [(15, y_offset), (31, y_offset + 7), (16, y_offset + 15), (0, y_offset + 8)]
inner = [(15, y_offset + 4), (27, y_offset + 7), (16, y_offset + 11), (4, y_offset + 8)]
if not is_alt:
# Tile 1 Dark: body is Light Gray (1), inner is Dark Gray (2), outline is Black (3)
draw.polygon(points, fill=1, outline=3)
draw.polygon(inner, fill=2, outline=3)
else:
# Tile 2 Dark: body is Dark Gray (2), inner is Light Gray (1), outline is Black (3)
draw.polygon(points, fill=2, outline=3)
draw.polygon(inner, fill=1, outline=3)
def generate_tiles():
# 64 variants total:
# 0..15: Tile 1 (Normal Floor) with masks 0..15
# 16..31: Tile 2 (Alt Floor) with masks 0..15
# 32..47: Tile 1 Dark (Normal Floor Dark) with masks 0..15
# 48..63: Tile 2 Dark (Alt Floor Dark) with masks 0..15
# Height is 8 pixels (black spacer) + 64 variants * 16 pixels = 1032 pixels
img = Image.new("P", (32, 1032), 3)
img.putpalette(PALETTE)
draw = ImageDraw.Draw(img)
# Draw Tile 1 variants (start at y=8)
for mask in range(16):
draw_autotile(draw, 8 + mask * 16, False, mask, False)
# Draw Tile 2 variants (start at y=264)
for mask in range(16):
draw_autotile(draw, 264 + mask * 16, True, mask, False)
# Draw Tile 1 Dark variants (start at y=520)
for mask in range(16):
draw_autotile(draw, 520 + mask * 16, False, mask, True)
# Draw Tile 2 Dark variants (start at y=776)
for mask in range(16):
draw_autotile(draw, 776 + mask * 16, True, mask, True)
img.save("tiles.png")
def generate_stamina():
# 13 variants of 8x16 tiles = 104x16 pixels
img = Image.new("P", (104, 16), 0) # Fill with 0 (transparent)
img.putpalette(PALETTE)
def draw_sprite_bar_tile(x_off, filled_px, cap=None):
for y in range(8):
for x in range(8):
color = 3 # black inside
if y == 0 or y == 7:
color = 3 # black border
elif y == 1 or y == 6:
color = 2 # dark gray inner border
else:
if x < filled_px:
color = 1 # light gray (filled)
else:
color = 3 # black (empty)
if cap == 'L' and x == 0:
color = 2 # left border
elif cap == 'R' and x == 7:
color = 2 # right border
img.putpixel((x_off + x, y), color)
# Also fill bottom 8 rows with transparent color 0
for y in range(8, 16):
for x in range(104):
img.putpixel((x, y), 0)
for i in range(9):
draw_sprite_bar_tile(i * 8, i)
draw_sprite_bar_tile(9 * 8, 0, 'L')
draw_sprite_bar_tile(10 * 8, 0, 'R')
draw_sprite_bar_tile(11 * 8, 8, 'L')
draw_sprite_bar_tile(12 * 8, 8, 'R')
img.save("stamina.png")
# Grids for player frames
# 0 = White, 1 = Light Gray, 2 = Dark Gray, 3 = Black
# Down-Right Stand (Frame 0)
GRID_DR_STAND = [
"0000000000000000",
"0000003333000000",
"0000031111300000",
"0000311111130000",
"0000311313130000", # Eyes facing right/down
"0000031111300000",
"0000003333000000",
"0000032222300000",
"0000312222130000",
"0000312222130000",
"0000032222300000",
"0000003333000000",
"0000033003300000",
"0000033003300000",
"0000333003330000",
"0000000000000000"
]
# Down-Right Walk (Frame 1)
GRID_DR_WALK = [
"0000000000000000",
"0000003333000000",
"0000031111300000",
"0000311111130000",
"0000311313130000",
"0000031111300000",
"0000003333000000",
"0000032222300000",
"0000312222130000",
"0000312222130000",
"0000032222300000",
"0000003333000000",
"0000033000030000", # Left leg forward, right leg back
"0000330000033000",
"0003330000033300",
"0000000000000000"
]
# Up-Right Stand (Frame 4 in final list)
GRID_UR_STAND = [
"0000000000000000",
"0000003333000000",
"0000031111300000",
"0000311111130000",
"0000311111130000", # Back of head (no eyes)
"0000031111300000",
"0000003333000000",
"0000032222300000",
"0000312222130000",
"0000312222130000",
"0000032222300000",
"0000003333000000",
"0000033003300000",
"0000033003300000",
"0000333003330000",
"0000000000000000"
]
# Up-Right Walk (Frame 5 in final list)
GRID_UR_WALK = [
"0000000000000000",
"0000003333000000",
"0000031111300000",
"0000311111130000",
"0000311111130000",
"0000031111300000",
"0000003333000000",
"0000032222300000",
"0000312222130000",
"0000312222130000",
"0000032222300000",
"0000003333000000",
"0000033000030000",
"0000330000033000",
"0003330000033300",
"0000000000000000"
]
def draw_frame(draw, grid, x_off):
for y in range(16):
for x in range(16):
val = int(grid[y][x])
draw.point((x_off + x, y), fill=val)
def generate_player():
# 8 frames in total (128x16 pixels)
player_img = Image.new("P", (128, 16), 0)
player_img.putpalette(PALETTE)
dr_stand = Image.new("P", (16, 16), 0)
dr_stand.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(dr_stand), GRID_DR_STAND, 0)
dr_walk = Image.new("P", (16, 16), 0)
dr_walk.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(dr_walk), GRID_DR_WALK, 0)
ur_stand = Image.new("P", (16, 16), 0)
ur_stand.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(ur_stand), GRID_UR_STAND, 0)
ur_walk = Image.new("P", (16, 16), 0)
ur_walk.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(ur_walk), GRID_UR_WALK, 0)
dl_stand = dr_stand.transpose(Image.FLIP_LEFT_RIGHT)
dl_walk = dr_walk.transpose(Image.FLIP_LEFT_RIGHT)
ul_stand = ur_stand.transpose(Image.FLIP_LEFT_RIGHT)
ul_walk = ur_walk.transpose(Image.FLIP_LEFT_RIGHT)
player_img.paste(dr_stand, (0 * 16, 0))
player_img.paste(dr_walk, (1 * 16, 0))
player_img.paste(dl_stand, (2 * 16, 0))
player_img.paste(dl_walk, (3 * 16, 0))
player_img.paste(ul_stand, (4 * 16, 0))
player_img.paste(ul_walk, (5 * 16, 0))
player_img.paste(ur_stand, (6 * 16, 0))
player_img.paste(ur_walk, (7 * 16, 0))
player_img.save("player.png")
def generate_gameover():
# 80x16 pixels
img = Image.new("P", (80, 16), 3)
img.putpalette(PALETTE)
# We can design each letter on an 8x16 grid
# Let's specify the pixel grids (16 rows of 8 chars)
# 0: White (text body), 2: Dark Gray (text outline/shadow), 3: Black (background)
grids = {
'G': [
"33322223",
"33200002",
"32002222",
"32023333",
"32023222",
"32023202",
"32002202",
"33200002",
"33322223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'A': [
"33322333",
"33200233",
"32022023",
"32022023",
"32000023",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'M': [
"32233223",
"32022023",
"32000023",
"32022023",
"32022023",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'E': [
"32222223",
"32000002",
"32022222",
"32000023",
"32022222",
"32023333",
"32000002",
"32222223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
' ': [
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'O': [
"33222233",
"32000023",
"32022023",
"32022023",
"32022023",
"32022023",
"32000023",
"33222233",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'V': [
"32233223",
"32022023",
"32022023",
"32022023",
"32022023",
"32022023",
"33200233",
"33322333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'R': [
"32222233",
"32000023",
"32022023",
"32000233",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
]
}
text = "GAME OVER"
for i, char in enumerate(text):
grid = grids[char]
for y in range(16):
for x in range(8):
val = int(grid[y][x])
target_x = i * 8 + x + 4
target_y = y + 3
if target_x < 80 and target_y < 16:
img.putpixel((target_x, target_y), val)
img.save("gameover.png")
if __name__ == "__main__":
generate_tiles()
generate_player()
generate_gameover()
generate_stamina()
print("PNG assets generated (tiles, player, gameover, and stamina).")
-102
View File
@@ -1,102 +0,0 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: player.png -c player.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(player)
const palette_color_t player_palettes[32] = {
RGB8(255,255,255), RGB8(170,170,170), RGB8( 85, 85, 85), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t player_tiles[448] = {
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x09,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x06,0x06,0x06,0x06,0x0e,0x0e,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x50,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x60,0x60,0x60,0x60,0x70,0x70,0x00,0x00,
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x09,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x06,0x06,0x0c,0x0c,0x1c,0x1c,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x50,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x10,0x10,0x18,0x18,0x1c,0x1c,0x00,0x00,
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x0a,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x06,0x06,0x06,0x06,0x0e,0x0e,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x90,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x60,0x60,0x60,0x60,0x70,0x70,0x00,0x00,
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x0a,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x08,0x08,0x18,0x18,0x38,0x38,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x90,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x60,0x60,0x30,0x30,0x38,0x38,0x00,0x00,
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x08,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x06,0x06,0x06,0x06,0x0e,0x0e,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x10,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x60,0x60,0x60,0x60,0x70,0x70,0x00,0x00,
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x08,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x08,0x08,0x18,0x18,0x38,0x38,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x10,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x60,0x60,0x30,0x30,0x38,0x38,0x00,0x00,
0x00,0x00,0x03,0x03,0x07,0x04,0x0f,0x08,0x0f,0x08,0x07,0x04,0x03,0x03,0x04,0x07,
0x0c,0x0b,0x0c,0x0b,0x04,0x07,0x03,0x03,0x06,0x06,0x0c,0x0c,0x1c,0x1c,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xe0,0x20,0xf0,0x10,0xf0,0x10,0xe0,0x20,0xc0,0xc0,0x20,0xe0,
0x30,0xd0,0x30,0xd0,0x20,0xe0,0xc0,0xc0,0x10,0x10,0x18,0x18,0x1c,0x1c,0x00,0x00
};
const metasprite_t player_metasprite0[] = {
METASPR_ITEM(-8, -8, 0, S_PAL(0)),
METASPR_ITEM(0, 8, 2, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite1[] = {
METASPR_ITEM(-8, -8, 4, S_PAL(0)),
METASPR_ITEM(0, 8, 6, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite2[] = {
METASPR_ITEM(-8, -8, 8, S_PAL(0)),
METASPR_ITEM(0, 8, 10, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite3[] = {
METASPR_ITEM(-8, -8, 12, S_PAL(0)),
METASPR_ITEM(0, 8, 14, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite4[] = {
METASPR_ITEM(-8, -8, 16, S_PAL(0)),
METASPR_ITEM(0, 8, 18, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite5[] = {
METASPR_ITEM(-8, -8, 20, S_PAL(0)),
METASPR_ITEM(0, 8, 22, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite6[] = {
METASPR_ITEM(-8, -8, 16, S_PAL(0)),
METASPR_ITEM(0, 8, 18, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite7[] = {
METASPR_ITEM(-8, -8, 24, S_PAL(0)),
METASPR_ITEM(0, 8, 26, S_PAL(0)),
METASPR_TERM
};
const metasprite_t* const player_metasprites[8] = {
player_metasprite0, player_metasprite1, player_metasprite2, player_metasprite3, player_metasprite4, player_metasprite5, player_metasprite6, player_metasprite7
};
+40
View File
@@ -0,0 +1,40 @@
import numpy as np
from PIL import Image, ImageDraw
PALETTE = [
224, 248, 207, # 0
134, 192, 108, # 1
48, 104, 80, # 2
7, 24, 33 # 3
] + [0] * (256 * 3 - 12)
# Based on peasant_preview3, but looking down-right
GRID_DR_STAND = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032321111232300",
"0032312112132300",
"0032313113132300",
"0032311111132300",
"0032313333132300",
"0003331111333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000323003230000",
"0003333003333000",
"0003333003333000"
]
def draw_frame(draw, grid, x_off):
for y in range(16):
for x in range(16):
val = int(grid[y][x])
draw.point((x_off + x, y), fill=val)
img = Image.new("P", (16, 16), 0)
img.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(img), GRID_DR_STAND, 0)
img.save("test_peasant.png")
+583
View File
@@ -0,0 +1,583 @@
import os
from PIL import Image, ImageDraw
# Standard Game Boy palette
# 0: White, 1: Light Gray, 2: Dark Gray, 3: Black
PALETTE = [
224, 248, 207, # 0
134, 192, 108, # 1
48, 104, 80, # 2
7, 24, 33 # 3
] + [0] * (256 * 3 - 12)
def draw_corner(draw, corner_type, state, is_alt):
if state == 0:
fill_color = 3
neigh_inner = None
elif state == 1:
fill_color = 0 if is_alt else 1
neigh_inner = 1 if is_alt else 0
neigh_outline = 2
else:
fill_color = 1 if is_alt else 2
neigh_inner = 2 if is_alt else 1
neigh_outline = 3
if corner_type == 'tl':
draw.polygon([(0, 0), (15, 0), (0, 7)], fill=fill_color)
if state != 0:
draw.polygon([(-1, -4), (11, -1), (0, 3), (-12, 0)], fill=neigh_inner, outline=neigh_outline)
elif corner_type == 'tr':
draw.polygon([(16, 0), (31, 0), (31, 7)], fill=fill_color)
if state != 0:
draw.polygon([(31, -4), (43, -1), (32, 3), (20, 0)], fill=neigh_inner, outline=neigh_outline)
elif corner_type == 'bl':
draw.polygon([(0, 8), (0, 15), (15, 15)], fill=fill_color)
if state != 0:
draw.polygon([(-1, 12), (11, 15), (0, 19), (-12, 16)], fill=neigh_inner, outline=neigh_outline)
elif corner_type == 'br':
draw.polygon([(31, 8), (31, 15), (16, 15)], fill=fill_color)
if state != 0:
draw.polygon([(31, 12), (43, 15), (32, 19), (20, 16)], fill=neigh_inner, outline=neigh_outline)
def draw_autotile(img, y_offset, is_alt, mask, is_dark=False):
temp_img = Image.new("P", (32, 16), 3)
temp_img.putpalette(PALETTE)
draw = ImageDraw.Draw(temp_img)
tl_state = mask % 3
tr_state = mask // 3
body_color = 2 if is_dark else (1 if is_alt else 0)
outer = [(15, 0), (31, 7), (16, 15), (0, 8)]
draw.polygon(outer, fill=body_color)
draw_corner(draw, 'tl', tl_state, is_alt)
draw_corner(draw, 'tr', tr_state, is_alt)
draw_corner(draw, 'bl', 0, is_alt)
draw_corner(draw, 'br', 0, is_alt)
inner_color = 3 if is_dark else (0 if is_alt else 1)
if is_dark:
inner_color = 1 if is_alt else 2
outline_color = 3 if is_dark else 2
inner = [(15, 4), (27, 7), (16, 11), (4, 8)]
draw.polygon(inner, fill=inner_color, outline=outline_color)
img.paste(temp_img, (0, y_offset))
def generate_tiles():
img = Image.new("P", (32, 5768), 3)
img.putpalette(PALETTE)
for mask in range(9): draw_autotile(img, 8 + mask * 16, False, mask, False)
for mask in range(9): draw_autotile(img, 152 + mask * 16, True, mask, False)
for mask in range(9): draw_autotile(img, 296 + mask * 16, False, mask, True)
for mask in range(9): draw_autotile(img, 440 + mask * 16, True, mask, True)
stairs_pattern = [
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTT3311111133TTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT330033TTTTTTTTTTTTT",
"TTTTTTTTTTT3300000033TTTTTTTTTTT",
"TTTTTTTTTTTTT330033TTTTTTTTTTTTT",
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTT3311111133TTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT330033TTTTTTTTTTTTT",
"TTTTTTTTTTT3300000033TTTTTTTTTTT",
"TTTTTTTTTTTTT330033TTTTTTTTTTTTT",
]
dark_stairs_pattern = [
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT332233TTTTTTTTTTTTT",
"TTTTTTTTTTT3322222233TTTTTTTTTTT",
"TTTTTTTTTTTTT332233TTTTTTTTTTTTT",
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTT3311111133TTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT332233TTTTTTTTTTTTT",
"TTTTTTTTTTT3322222233TTTTTTTTTTT",
"TTTTTTTTTTTTT332233TTTTTTTTTTTTT",
"TTTTTTTTTTTTTTT33TTTTTTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
"TTTTTTTTTTT3311111133TTTTTTTTTTT",
"TTTTTTTTTTTTT331133TTTTTTTTTTTTT",
]
for v_set, base_offset, pattern in [
(0, 584, stairs_pattern),
(1, 1880, stairs_pattern),
(2, 3176, dark_stairs_pattern),
(3, 4472, dark_stairs_pattern)
]:
is_alt_vic = (v_set == 1 or v_set == 3)
is_dark_vic = (v_set == 2 or v_set == 3)
for mask in range(81):
y_offset = base_offset + mask * 16
tl_state = mask % 3
tr_state = (mask // 3) % 3
bl_state = (mask // 9) % 3
br_state = (mask // 27) % 3
temp_img = Image.new("P", (32, 16), 3)
temp_img.putpalette(PALETTE)
draw = ImageDraw.Draw(temp_img)
body_color = 2 if is_dark_vic else (1 if is_alt_vic else 0)
outer = [(15, 0), (31, 7), (16, 15), (0, 8)]
draw.polygon(outer, fill=body_color)
draw_corner(draw, 'tl', tl_state, is_alt_vic)
draw_corner(draw, 'tr', tr_state, is_alt_vic)
draw_corner(draw, 'bl', bl_state, is_alt_vic)
draw_corner(draw, 'br', br_state, is_alt_vic)
for py in range(16):
for px in range(32):
char = pattern[py][px]
if char != 'T':
temp_img.putpixel((px, py), int(char))
img.paste(temp_img, (0, y_offset))
img.save("tiles.png")
def generate_stamina():
# 13 variants of 8x16 tiles = 104x16 pixels
img = Image.new("P", (104, 16), 0) # Fill with 0 (transparent)
img.putpalette(PALETTE)
def draw_sprite_bar_tile(x_off, filled_px, cap=None):
for y in range(8):
for x in range(8):
color = 3 # black inside
if y == 0 or y == 7:
color = 3 # black border
elif y == 1 or y == 6:
color = 2 # dark gray inner border
else:
if x < filled_px:
color = 1 # light gray (filled)
else:
color = 3 # black (empty)
if cap == 'L' and x == 0:
color = 2 # left border
elif cap == 'R' and x == 7:
color = 2 # right border
img.putpixel((x_off + x, y), color)
# Also fill bottom 8 rows with transparent color 0
for y in range(8, 16):
for x in range(104):
img.putpixel((x, y), 0)
for i in range(9):
draw_sprite_bar_tile(i * 8, i)
draw_sprite_bar_tile(9 * 8, 0, 'L')
draw_sprite_bar_tile(10 * 8, 0, 'R')
draw_sprite_bar_tile(11 * 8, 8, 'L')
draw_sprite_bar_tile(12 * 8, 8, 'R')
img.save("stamina.png")
# Grids for player frames
# 0 = White, 1 = Light Gray, 2 = Dark Gray, 3 = Black
# Down-Right Stand (Frame 0)
GRID_DR_STAND = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032321111232300",
"0032311211232300",
"0032311311332300",
"0032311111132300",
"0032311333332300",
"0003331111333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000323003230000",
"0003333003333000",
"0003333003333000"
]
# Down-Right Walk (Frame 1)
GRID_DR_WALK = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032321111232300",
"0032311211232300",
"0032311311332300",
"0032311111132300",
"0032311333332300",
"0003331111333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000320002300000",
"0003330033300000",
"0003330033300000"
]
# Up-Right Stand (Frame 4 in final list)
GRID_UR_STAND = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0003333333333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000323003230000",
"0003333003333000",
"0003333003333000"
]
# Up-Right Walk (Frame 5 in final list)
GRID_UR_WALK = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0003333333333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000320002300000",
"0003330033300000",
"0003330033300000"
]
def draw_frame(draw, grid, x_off):
for y in range(16):
for x in range(16):
val = int(grid[y][x])
draw.point((x_off + x, y), fill=val)
def generate_player():
# 8 frames in total (128x16 pixels)
player_img = Image.new("P", (128, 16), 0)
player_img.putpalette(PALETTE)
dr_stand = Image.new("P", (16, 16), 0)
dr_stand.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(dr_stand), GRID_DR_STAND, 0)
dr_walk = Image.new("P", (16, 16), 0)
dr_walk.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(dr_walk), GRID_DR_WALK, 0)
ur_stand = Image.new("P", (16, 16), 0)
ur_stand.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(ur_stand), GRID_UR_STAND, 0)
ur_walk = Image.new("P", (16, 16), 0)
ur_walk.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(ur_walk), GRID_UR_WALK, 0)
dl_stand = dr_stand.transpose(Image.FLIP_LEFT_RIGHT)
dl_walk = dr_walk.transpose(Image.FLIP_LEFT_RIGHT)
ul_stand = ur_stand.transpose(Image.FLIP_LEFT_RIGHT)
ul_walk = ur_walk.transpose(Image.FLIP_LEFT_RIGHT)
player_img.paste(dr_stand, (0 * 16, 0))
player_img.paste(dr_walk, (1 * 16, 0))
player_img.paste(dl_stand, (2 * 16, 0))
player_img.paste(dl_walk, (3 * 16, 0))
player_img.paste(ul_stand, (4 * 16, 0))
player_img.paste(ul_walk, (5 * 16, 0))
player_img.paste(ur_stand, (6 * 16, 0))
player_img.paste(ur_walk, (7 * 16, 0))
player_img.save("player.png")
def generate_gameover():
# 80x16 pixels
img = Image.new("P", (80, 16), 3)
img.putpalette(PALETTE)
# We can design each letter on an 8x16 grid
# Let's specify the pixel grids (16 rows of 8 chars)
# 0: White (text body), 2: Dark Gray (text outline/shadow), 3: Black (background)
grids = {
'G': [
"33322223",
"33200002",
"32002222",
"32023333",
"32023222",
"32023202",
"32002202",
"33200002",
"33322223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'A': [
"33322333",
"33200233",
"32022023",
"32022023",
"32000023",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'M': [
"32233223",
"32022023",
"32000023",
"32022023",
"32022023",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'E': [
"32222223",
"32000002",
"32022222",
"32000023",
"32022222",
"32023333",
"32000002",
"32222223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
' ': [
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'O': [
"33222233",
"32000023",
"32022023",
"32022023",
"32022023",
"32022023",
"32000023",
"33222233",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'V': [
"32233223",
"32022023",
"32022023",
"32022023",
"32022023",
"32022023",
"33200233",
"33322333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'R': [
"32222233",
"32000023",
"32022023",
"32000233",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
]
}
text = "GAME OVER"
for i, char in enumerate(text):
grid = grids[char]
for y in range(16):
for x in range(8):
val = int(grid[y][x])
target_x = i * 8 + x + 4
target_y = y + 3
if target_x < 80 and target_y < 16:
img.putpixel((target_x, target_y), val)
img.save("gameover.png")
def generate_victory():
img = Image.new("P", (56, 16), 3)
img.putpalette(PALETTE)
grids = {
'V': [
"32223222",
"32023202",
"32023202",
"32023202",
"33202023",
"33200023",
"33320233",
"33332333"
],
'I': [
"33222233",
"33200233",
"33320233",
"33320233",
"33320233",
"33320233",
"33200233",
"33222233"
],
'C': [
"33222233",
"32000023",
"32022233",
"32023333",
"32023333",
"32022233",
"32000023",
"33222233"
],
'T': [
"32222223",
"32000002",
"33320233",
"33320233",
"33320233",
"33320233",
"33320233",
"33322233"
],
'O': [
"33222233",
"32000023",
"32022023",
"32022023",
"32022023",
"32022023",
"32000023",
"33222233"
],
'R': [
"32222233",
"32000023",
"32022023",
"32000023",
"32022023",
"32023023",
"32023023",
"32233223"
],
'Y': [
"32233223",
"32023202",
"33202023",
"33200023",
"33320233",
"33320233",
"33320233",
"33322233"
]
}
text = "VICTORY"
for i, char in enumerate(text):
grid = grids[char]
for y in range(8):
for x in range(8):
val = int(grid[y][x])
target_x = i * 8 + x
target_y = y + 4
if target_x < 56 and target_y < 16:
img.putpixel((target_x, target_y), val)
img.save("victory.png")
if __name__ == "__main__":
generate_tiles()
generate_player()
generate_gameover()
generate_stamina()
generate_victory()
print("PNG assets generated (tiles, player, gameover, stamina, victory).")
+118
View File
@@ -0,0 +1,118 @@
import numpy as np
from PIL import Image, ImageDraw
# Game Boy Hardware Colors (Green Palette)
PALETTE = [
224, 248, 207, # 0
134, 192, 108, # 1
48, 104, 80, # 2
7, 24, 33 # 3
] + [0] * (256 * 3 - 12)
# Scarier diagonal ghost
GHOST_DR_STAND = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003113113130000",
"0003112221130000",
"0031113331130000",
"0032111211133000",
"0003211111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000321330000000",
"0000033000000000",
"0000000000000000"
]
GHOST_DR_WALK = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003113113130000",
"0003112221130000",
"0003113331130000",
"0032111211133000",
"0032211111111300",
"0003221111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000003213000000",
"0000000330000000",
"0000000000000000"
]
GHOST_UR_STAND = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003111111130000",
"0003111111130000",
"0003111111130000",
"0003211111133000",
"0000321111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000321330000000",
"0000033000000000",
"0000000000000000"
]
GHOST_UR_WALK = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003111111130000",
"0003111111130000",
"0003111111130000",
"0003211111133000",
"0000321111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000003213000000",
"0000000330000000",
"0000000000000000"
]
def draw_frame(draw, grid, x_off):
for y in range(16):
for x in range(16):
val = int(grid[y][x])
draw.point((x_off + x, y), fill=val)
ghost_img = Image.new("P", (128, 16), 0)
ghost_img.putpalette(PALETTE)
dr_s = Image.new("P", (16, 16), 0); dr_s.putpalette(PALETTE); draw_frame(ImageDraw.Draw(dr_s), GHOST_DR_STAND, 0)
dr_w = Image.new("P", (16, 16), 0); dr_w.putpalette(PALETTE); draw_frame(ImageDraw.Draw(dr_w), GHOST_DR_WALK, 0)
ur_s = Image.new("P", (16, 16), 0); ur_s.putpalette(PALETTE); draw_frame(ImageDraw.Draw(ur_s), GHOST_UR_STAND, 0)
ur_w = Image.new("P", (16, 16), 0); ur_w.putpalette(PALETTE); draw_frame(ImageDraw.Draw(ur_w), GHOST_UR_WALK, 0)
dl_s = dr_s.transpose(Image.FLIP_LEFT_RIGHT)
dl_w = dr_w.transpose(Image.FLIP_LEFT_RIGHT)
ul_s = ur_s.transpose(Image.FLIP_LEFT_RIGHT)
ul_w = ur_w.transpose(Image.FLIP_LEFT_RIGHT)
ghost_img.paste(dr_s, (0 * 16, 0))
ghost_img.paste(dr_w, (1 * 16, 0))
ghost_img.paste(dl_s, (2 * 16, 0))
ghost_img.paste(dl_w, (3 * 16, 0))
ghost_img.paste(ul_s, (4 * 16, 0))
ghost_img.paste(ul_w, (5 * 16, 0))
ghost_img.paste(ur_s, (6 * 16, 0))
ghost_img.paste(ur_w, (7 * 16, 0))
ghost_img.save('enemy.png')
print("Saved enemy.png")
+128
View File
@@ -0,0 +1,128 @@
import numpy as np
from PIL import Image, ImageDraw
# Game Boy Hardware Colors (Green Palette)
C0 = (224, 248, 207, 255)
C1 = (134, 192, 108, 255)
C2 = (48, 104, 80, 255)
C3 = (7, 24, 33, 255)
TR = (224, 248, 207, 0) # Transparent
# Scarier diagonal ghost
# Down-Right Stand
GHOST_DR_STAND = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003113113130000",
"0003112221130000",
"0031113331130000",
"0032111211133000",
"0003211111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000321330000000",
"0000033000000000",
"0000000000000000"
]
GHOST_DR_WALK = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003113113130000",
"0003112221130000",
"0003113331130000",
"0032111211133000",
"0032211111111300",
"0003221111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000003213000000",
"0000000330000000",
"0000000000000000"
]
GHOST_UR_STAND = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003111111130000",
"0003111111130000",
"0003111111130000",
"0003211111133000",
"0000321111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000321330000000",
"0000033000000000",
"0000000000000000"
]
GHOST_UR_WALK = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003111111130000",
"0003111111130000",
"0003111111130000",
"0003211111133000",
"0000321111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000003213000000",
"0000000330000000",
"0000000000000000"
]
def draw_frame(draw, grid, x_off):
for y in range(16):
for x in range(16):
val = int(grid[y][x])
draw.point((x_off + x, y), fill=val)
ghost_img = Image.new("P", (128, 16), 0)
PALETTE = [C0[0], C0[1], C0[2], C1[0], C1[1], C1[2], C2[0], C2[1], C2[2], C3[0], C3[1], C3[2]] + [0]*756
ghost_img.putpalette(PALETTE)
dr_s = Image.new("P", (16, 16), 0); draw_frame(ImageDraw.Draw(dr_s), GHOST_DR_STAND, 0)
dr_w = Image.new("P", (16, 16), 0); draw_frame(ImageDraw.Draw(dr_w), GHOST_DR_WALK, 0)
ur_s = Image.new("P", (16, 16), 0); draw_frame(ImageDraw.Draw(ur_s), GHOST_UR_STAND, 0)
ur_w = Image.new("P", (16, 16), 0); draw_frame(ImageDraw.Draw(ur_w), GHOST_UR_WALK, 0)
dl_s = dr_s.transpose(Image.FLIP_LEFT_RIGHT)
dl_w = dr_w.transpose(Image.FLIP_LEFT_RIGHT)
ul_s = ur_s.transpose(Image.FLIP_LEFT_RIGHT)
ul_w = ur_w.transpose(Image.FLIP_LEFT_RIGHT)
ghost_img.paste(dr_s, (0 * 16, 0))
ghost_img.paste(dr_w, (1 * 16, 0))
ghost_img.paste(dl_s, (2 * 16, 0))
ghost_img.paste(dl_w, (3 * 16, 0))
ghost_img.paste(ul_s, (4 * 16, 0))
ghost_img.paste(ul_w, (5 * 16, 0))
ghost_img.paste(ur_s, (6 * 16, 0))
ghost_img.paste(ur_w, (7 * 16, 0))
# Resize by 4x for easier previewing
ghost_preview = Image.new('RGBA', ghost_img.size)
preview_palette = [TR, C1, C2, C3]
for y in range(ghost_img.size[1]):
for x in range(ghost_img.size[0]):
p = ghost_img.getpixel((x, y))
ghost_preview.putpixel((x, y), preview_palette[p])
ghost_preview = ghost_preview.resize((128*4, 16*4), Image.Resampling.NEAREST)
ghost_preview.save('ghost_enemy.png')
print("Saved scary diagonal ghost_enemy.png")
+44
View File
@@ -0,0 +1,44 @@
import numpy as np
from PIL import Image
palette = [
(224, 248, 207, 0), # 0: Transparent (alpha 0, but Game Boy color 0)
(134, 192, 108, 255), # 1: Light
(48, 104, 80, 255), # 2: Dark
(7, 24, 33, 255) # 3: Black
]
sprite = [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], # 00
[0,0,0,3,1,3,0,0,0,0,0,0,0,0,0,0], # 01 flame tip
[0,0,3,1,2,1,3,0,0,3,3,3,3,3,0,0], # 02 flame / head top
[0,0,3,1,2,1,3,0,3,2,2,2,2,2,3,0], # 03 flame / hood
[0,0,0,3,1,3,0,0,3,1,1,1,1,1,3,0], # 04 flame base / face top
[0,0,0,3,2,3,0,0,3,1,3,1,3,1,3,0], # 05 stick / scared eyes (wide)
[0,0,0,3,2,3,0,0,3,1,1,3,1,1,3,0], # 06 stick / scared open mouth
[0,0,0,3,2,3,0,0,0,3,1,1,1,3,0,0], # 07 stick / chin
[0,0,0,3,2,3,0,0,3,2,2,2,2,2,3,0], # 08 stick / shoulders
[0,0,3,1,1,1,3,3,2,2,2,2,2,3,0,0], # 09 hand holding stick / body
[0,0,0,3,3,3,0,3,2,2,2,2,2,3,1,3], # 10 hand bottom / body / right hand
[0,0,0,0,0,0,0,0,3,2,2,2,2,3,3,3], # 11 body lower
[0,0,0,0,0,0,0,0,3,2,3,0,3,2,3,0], # 12 legs
[0,0,0,0,0,0,0,0,3,2,3,0,3,2,3,0], # 13 legs
[0,0,0,0,0,0,0,3,3,2,3,0,3,2,3,3], # 14 shoes
[0,0,0,0,0,0,0,3,3,3,0,0,0,3,3,3], # 15 shoes bottom
]
img = Image.new('RGBA', (16, 16))
pixels = img.load()
for y in range(16):
for x in range(16):
pixels[x, y] = palette[sprite[y][x]]
# Save 16x16 with transparency (for the actual asset generation)
img.save('peasant_16x16.png')
# Upscale for preview
preview = img.resize((160, 160), Image.Resampling.NEAREST)
bg = Image.new('RGBA', (160, 160), (224, 248, 207, 255))
bg.paste(preview, (0,0), preview)
bg.save('peasant_preview.png')
print("Generated peasant_16x16.png and peasant_preview.png")
+65
View File
@@ -0,0 +1,65 @@
import numpy as np
from PIL import Image
palette = [
(224, 248, 207, 0), # 0: Transparent
(134, 192, 108, 255), # 1: Light (Skin)
(48, 104, 80, 255), # 2: Dark (Clothes/Hood)
(7, 24, 33, 255) # 3: Black (Outlines/Eyes)
]
sprite = [
[0,0,0,0, 3,3,3,3,3,3,3,3, 0,0,0,0], # 00 hat top
[0,0,0,3, 2,2,2,2,2,2,2,2, 3,0,0,0], # 01 hat
[0,0,3,2, 2,3,3,3,3,3,3,2, 2,3,0,0], # 02 hat brim
[0,0,3,2, 3,2,1,1,1,1,2,3, 2,3,0,0], # 03 eyebrows outer
[0,0,3,2, 3,1,2,1,1,2,1,3, 2,3,0,0], # 04 eyebrows inner
[0,0,3,2, 3,1,3,1,1,3,1,3, 2,3,0,0], # 05 eyes
[0,0,3,2, 3,1,1,3,3,1,1,3, 2,3,0,0], # 06 mouth
[0,0,3,2, 3,1,1,3,3,1,1,3, 2,3,0,0], # 07 mouth
[0,0,0,3, 3,3,1,1,1,1,3,3, 3,0,0,0], # 08 chin
[0,0,3,2, 2,3,3,3,3,3,3,2, 2,3,0,0], # 09 shoulders
[0,0,3,2, 2,3,1,1,1,1,3,2, 2,3,0,0], # 10 hands clasped
[0,0,3,2, 2,3,3,3,3,3,3,2, 2,3,0,0], # 11 belt/lower arms
[0,0,3,2, 2,2,2,2,2,2,2,2, 2,3,0,0], # 12 tunic
[0,0,0,3, 2,2,2,3,3,2,2,2, 3,0,0,0], # 13 tunic split
[0,0,0,0, 3,2,3,0,0,3,2,3, 0,0,0,0], # 14 legs
[0,0,0,3, 3,3,3,0,0,3,3,3, 3,0,0,0], # 15 shoes
]
# We need to replace the first frame of player.png which is 128x16
# We will create a new 128x16 image, paste this frame into the first slot
# For now, we just copy this frame 8 times so the animation doesn't break,
# although we only designed the first frame.
img_sheet = Image.new('P', (128, 16))
# Create palette flat array
flat_palette = []
for p in palette:
flat_palette.extend([p[0], p[1], p[2]])
# Pad palette to 256 colors
flat_palette.extend([0] * (768 - len(flat_palette)))
img_sheet.putpalette(flat_palette)
pixels = img_sheet.load()
for frame in range(8):
for y in range(16):
for x in range(16):
pixels[x + frame*16, y] = sprite[y][x]
# Save the game asset
img_sheet.save('player.png', transparency=0)
# Save the preview
preview = Image.new('RGBA', (16, 16))
p_pixels = preview.load()
for y in range(16):
for x in range(16):
p_pixels[x, y] = palette[sprite[y][x]]
preview = preview.resize((160, 160), Image.Resampling.NEAREST)
bg = Image.new('RGBA', (160, 160), (224, 248, 207, 255))
bg.paste(preview, (0,0), preview)
bg.save('peasant_preview2.png')
print("Generated player.png and peasant_preview2.png")
+55
View File
@@ -0,0 +1,55 @@
import numpy as np
from PIL import Image
palette = [
(224, 248, 207, 0), # 0: Transparent
(134, 192, 108, 255), # 1: Light (Skin)
(48, 104, 80, 255), # 2: Dark (Clothes/Hood)
(7, 24, 33, 255) # 3: Black (Outlines/Eyes)
]
sprite = [
[0,0,0,0, 3,3,3,3,3,3,3,3, 0,0,0,0], # 00 hat top
[0,0,0,3, 2,2,2,2,2,2,2,2, 3,0,0,0], # 01 hat
[0,0,3,2, 2,3,3,3,3,3,3,2, 2,3,0,0], # 02 hat brim
[0,0,3,2, 3,2,1,1,1,1,2,3, 2,3,0,0], # 03 eyebrows outer
[0,0,3,2, 3,1,2,1,1,2,1,3, 2,3,0,0], # 04 eyebrows inner
[0,0,3,2, 3,1,3,1,1,3,1,3, 2,3,0,0], # 05 eyes
[0,0,3,2, 3,1,1,1,1,1,1,3, 2,3,0,0], # 06 cheeks
[0,0,3,2, 3,1,3,3,3,3,1,3, 2,3,0,0], # 07 mouth (tense)
[0,0,0,3, 3,3,1,1,1,1,3,3, 3,0,0,0], # 08 chin
[0,0,3,2, 3,3,3,3,3,3,3,3, 2,3,0,0], # 09 shoulders/neck
[0,0,3,2, 3,2,2,2,2,2,2,3, 2,3,0,0], # 10 arms/body
[0,0,3,1, 3,2,2,2,2,2,2,3, 1,3,0,0], # 11 hands/body
[0,0,3,3, 3,3,2,2,2,2,3,3, 3,3,0,0], # 12 hands bottom / tunic
[0,0,0,0, 3,2,3,0,0,3,2,3, 0,0,0,0], # 13 legs
[0,0,0,3, 3,3,3,0,0,3,3,3, 3,0,0,0], # 14 shoes
[0,0,0,3, 3,3,3,0,0,3,3,3, 3,0,0,0], # 15 shoes
]
img_sheet = Image.new('P', (128, 16))
flat_palette = []
for p in palette:
flat_palette.extend([p[0], p[1], p[2]])
flat_palette.extend([0] * (768 - len(flat_palette)))
img_sheet.putpalette(flat_palette)
pixels = img_sheet.load()
for frame in range(8):
for y in range(16):
for x in range(16):
pixels[x + frame*16, y] = sprite[y][x]
img_sheet.save('player.png', transparency=0)
preview = Image.new('RGBA', (16, 16))
p_pixels = preview.load()
for y in range(16):
for x in range(16):
p_pixels[x, y] = palette[sprite[y][x]]
preview = preview.resize((160, 160), Image.Resampling.NEAREST)
bg = Image.new('RGBA', (160, 160), (224, 248, 207, 255))
bg.paste(preview, (0,0), preview)
bg.save('peasant_preview3.png')
print("Generated peasant_preview3.png")
+46
View File
@@ -0,0 +1,46 @@
import os
from PIL import Image, ImageDraw
# Standard Game Boy palette
# 0: White, 1: Light Gray, 2: Dark Gray, 3: Black
PALETTE = [
224, 248, 207, # 0
134, 192, 108, # 1
48, 104, 80, # 2
7, 24, 33 # 3
] + [0] * (256 * 3 - 12)
stairs_pattern = [
"00000000000000033000000000000000",
"00000000000003311330000000000000",
"00000000000331111113300000000000",
"00000000000003311330000000000000",
"00000000000000033000000000000000",
"00000000000003300330000000000000",
"00000000000330000003300000000000",
"00000000000003300330000000000000",
"00000000000000033000000000000000",
"00000000000003311330000000000000",
"00000000000331111113300000000000",
"00000000000003311330000000000000",
"00000000000000033000000000000000",
"00000000000003300330000000000000",
"00000000000330000003300000000000",
"00000000000003300330000000000000",
]
def generate_stairs_sprite():
img = Image.new("P", (32, 16), 0)
img.putpalette(PALETTE)
for py in range(16):
for px in range(32):
char = stairs_pattern[py][px]
if char != '0':
img.putpixel((px, py), int(char))
img.save("stairs_sprite.png")
print("stairs_sprite.png generated.")
if __name__ == "__main__":
generate_stairs_sprite()
+141
View File
@@ -0,0 +1,141 @@
import sys
from PIL import Image, ImageDraw
PALETTE = [
224, 248, 207, # 0
134, 192, 108, # 1
48, 104, 80, # 2
7, 24, 33 # 3
] + [0] * (256 * 3 - 12)
def generate_victory():
# 64x16 pixels
img = Image.new("P", (64, 16), 3)
img.putpalette(PALETTE)
grids = {
'V': [
"32233223",
"32022023",
"32022023",
"32022023",
"32022023",
"32022023",
"33200233",
"33322333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'I': [
"33322333",
"33322333",
"33300333",
"33300333",
"33300333",
"33300333",
"33300333",
"33322333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'T': [
"32222223",
"32000002",
"33300333",
"33300333",
"33300333",
"33300333",
"33300333",
"33322333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'O': [
"33222233",
"32000023",
"32022023",
"32022023",
"32022023",
"32022023",
"32000023",
"33222233",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'R': [
"32222233",
"32000023",
"32022023",
"32000233",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
],
'A': [
"33322333",
"33200233",
"32022023",
"32022023",
"32000023",
"32022023",
"32022023",
"32022023",
"32233223",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333",
"33333333"
]
}
text = "VITTORIA"
for i, char in enumerate(text):
grid = grids[char]
for y in range(16):
for x in range(8):
val = int(grid[y][x])
target_x = i * 8 + x
target_y = y
if target_x < 64 and target_y < 16:
img.putpixel((target_x, target_y), val)
img.save("victory.png")
if __name__ == "__main__":
generate_victory()
+78
View File
@@ -0,0 +1,78 @@
GRID_DR_STAND = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032321111232300",
"0032311211232300",
"0032311311332300",
"0032311111132300",
"0032311333332300",
"0003331111333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000323003230000",
"0003333003333000",
"0003333003333000"
]
GRID_DR_WALK = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032321111232300",
"0032311211232300",
"0032311311332300",
"0032311111132300",
"0032311333332300",
"0003331111333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000320002300000",
"0003330033300000",
"0003330033300000"
]
GRID_UR_STAND = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0003333333333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000323003230000",
"0003333003333000",
"0003333003333000"
]
GRID_UR_WALK = [
"0000333333330000",
"0003222222223000",
"0032233333322300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0032222222222300",
"0003333333333000",
"0032333333332300",
"0032322222232300",
"0031322222231300",
"0033332222333300",
"0000320002300000",
"0003330033300000",
"0003330033300000"
]
import sys
print("Done")
+126
View File
@@ -0,0 +1,126 @@
import os
with open('generate_assets.py', 'r') as f:
content = f.read()
GHOST_GRIDS = """
# Ghost Grids
GHOST_DR_STAND = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003113113130000",
"0003112221130000",
"0031113331130000",
"0032111211133000",
"0003211111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000321330000000",
"0000033000000000",
"0000000000000000"
]
GHOST_DR_WALK = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003113113130000",
"0003112221130000",
"0003113331130000",
"0032111211133000",
"0032211111111300",
"0003221111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000003213000000",
"0000000330000000",
"0000000000000000"
]
GHOST_UR_STAND = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003111111130000",
"0003111111130000",
"0003111111130000",
"0003211111133000",
"0000321111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000321330000000",
"0000033000000000",
"0000000000000000"
]
GHOST_UR_WALK = [
"0000033333000000",
"0000322222300000",
"0003211111230000",
"0003111111130000",
"0003111111130000",
"0003111111130000",
"0003211111133000",
"0000321111111300",
"0000322111113000",
"0000322111330000",
"0000032213000000",
"0000032213000000",
"0000032113000000",
"0000003213000000",
"0000000330000000",
"0000000000000000"
]
def generate_enemy():
img = Image.new("P", (128, 16), 0)
img.putpalette(PALETTE)
dr_s = Image.new("P", (16, 16), 0)
dr_s.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(dr_s), GHOST_DR_STAND, 0)
dr_w = Image.new("P", (16, 16), 0)
dr_w.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(dr_w), GHOST_DR_WALK, 0)
ur_s = Image.new("P", (16, 16), 0)
ur_s.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(ur_s), GHOST_UR_STAND, 0)
ur_w = Image.new("P", (16, 16), 0)
ur_w.putpalette(PALETTE)
draw_frame(ImageDraw.Draw(ur_w), GHOST_UR_WALK, 0)
dl_s = dr_s.transpose(Image.FLIP_LEFT_RIGHT)
dl_w = dr_w.transpose(Image.FLIP_LEFT_RIGHT)
ul_s = ur_s.transpose(Image.FLIP_LEFT_RIGHT)
ul_w = ur_w.transpose(Image.FLIP_LEFT_RIGHT)
img.paste(dr_s, (0 * 16, 0))
img.paste(dr_w, (1 * 16, 0))
img.paste(dl_s, (2 * 16, 0))
img.paste(dl_w, (3 * 16, 0))
img.paste(ul_s, (4 * 16, 0))
img.paste(ul_w, (5 * 16, 0))
img.paste(ur_s, (6 * 16, 0))
img.paste(ur_w, (7 * 16, 0))
img.save("enemy.png")
"""
content = content.replace("def generate_player():", GHOST_GRIDS + "\ndef generate_player():")
content = content.replace("generate_player()", "generate_player()\n generate_enemy()")
content = content.replace("player, gameover", "player, enemy, gameover")
with open('generate_assets.py', 'w') as f:
f.write(content)
+106
View File
@@ -0,0 +1,106 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /enemy.png -c /enemy.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order -sp 0x10
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(enemy)
const palette_color_t enemy_palettes[32] = {
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t enemy_tiles[512] = {
0x07,0x07,0x08,0x0f,0x17,0x18,0x1f,0x12,0x1c,0x13,0x3f,0x23,0x2e,0x31,0x17,0x18,
0x09,0x0e,0x09,0x0e,0x04,0x07,0x04,0x07,0x05,0x06,0x0b,0x0d,0x06,0x06,0x00,0x00,
0xc0,0xc0,0x20,0xe0,0xd0,0x30,0xf0,0x50,0x70,0x90,0xf0,0x90,0xf8,0x18,0xfc,0x04,
0xf8,0x08,0xf0,0x30,0xc0,0x40,0xc0,0x40,0xc0,0x40,0x80,0x80,0x00,0x00,0x00,0x00,
0x07,0x07,0x08,0x0f,0x17,0x18,0x1f,0x12,0x1c,0x13,0x1f,0x13,0x2e,0x31,0x27,0x38,
0x13,0x1c,0x09,0x0e,0x04,0x07,0x04,0x07,0x05,0x06,0x02,0x03,0x01,0x01,0x00,0x00,
0xc0,0xc0,0x20,0xe0,0xd0,0x30,0xf0,0x50,0x70,0x90,0xf0,0x90,0xf8,0x18,0xfc,0x04,
0xf8,0x08,0xf0,0x30,0xc0,0x40,0xc0,0x40,0xc0,0x40,0xc0,0x40,0x80,0x80,0x00,0x00,
0x03,0x03,0x04,0x07,0x0b,0x0c,0x0f,0x0a,0x0e,0x09,0x0f,0x09,0x1f,0x18,0x3f,0x20,
0x1f,0x10,0x0f,0x0c,0x03,0x02,0x03,0x02,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,
0xe0,0xe0,0x10,0xf0,0xe8,0x18,0xf8,0x48,0x38,0xc8,0xfc,0xc4,0x74,0x8c,0xe8,0x18,
0x90,0x70,0x90,0x70,0x20,0xe0,0x20,0xe0,0xa0,0x60,0xd0,0xb0,0x60,0x60,0x00,0x00,
0x03,0x03,0x04,0x07,0x0b,0x0c,0x0f,0x0a,0x0e,0x09,0x0f,0x09,0x1f,0x18,0x3f,0x20,
0x1f,0x10,0x0f,0x0c,0x03,0x02,0x03,0x02,0x03,0x02,0x03,0x02,0x01,0x01,0x00,0x00,
0xe0,0xe0,0x10,0xf0,0xe8,0x18,0xf8,0x48,0x38,0xc8,0xf8,0xc8,0x74,0x8c,0xe4,0x1c,
0xc8,0x38,0x90,0x70,0x20,0xe0,0x20,0xe0,0xa0,0x60,0x40,0xc0,0x80,0x80,0x00,0x00,
0x03,0x03,0x04,0x07,0x0b,0x0c,0x0f,0x08,0x0f,0x08,0x0f,0x08,0x1f,0x18,0x3f,0x20,
0x1f,0x10,0x0f,0x0c,0x03,0x02,0x03,0x02,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,
0xe0,0xe0,0x10,0xf0,0xe8,0x18,0xf8,0x08,0xf8,0x08,0xf8,0x08,0xe8,0x18,0xd0,0x30,
0x90,0x70,0x90,0x70,0x20,0xe0,0x20,0xe0,0xa0,0x60,0xd0,0xb0,0x60,0x60,0x00,0x00,
0x03,0x03,0x04,0x07,0x0b,0x0c,0x0f,0x08,0x0f,0x08,0x0f,0x08,0x1f,0x18,0x3f,0x20,
0x1f,0x10,0x0f,0x0c,0x03,0x02,0x03,0x02,0x03,0x02,0x03,0x02,0x01,0x01,0x00,0x00,
0xe0,0xe0,0x10,0xf0,0xe8,0x18,0xf8,0x08,0xf8,0x08,0xf8,0x08,0xe8,0x18,0xd0,0x30,
0x90,0x70,0x90,0x70,0x20,0xe0,0x20,0xe0,0xa0,0x60,0x40,0xc0,0x80,0x80,0x00,0x00,
0x07,0x07,0x08,0x0f,0x17,0x18,0x1f,0x10,0x1f,0x10,0x1f,0x10,0x17,0x18,0x0b,0x0c,
0x09,0x0e,0x09,0x0e,0x04,0x07,0x04,0x07,0x05,0x06,0x0b,0x0d,0x06,0x06,0x00,0x00,
0xc0,0xc0,0x20,0xe0,0xd0,0x30,0xf0,0x10,0xf0,0x10,0xf0,0x10,0xf8,0x18,0xfc,0x04,
0xf8,0x08,0xf0,0x30,0xc0,0x40,0xc0,0x40,0xc0,0x40,0x80,0x80,0x00,0x00,0x00,0x00,
0x07,0x07,0x08,0x0f,0x17,0x18,0x1f,0x10,0x1f,0x10,0x1f,0x10,0x17,0x18,0x0b,0x0c,
0x09,0x0e,0x09,0x0e,0x04,0x07,0x04,0x07,0x05,0x06,0x02,0x03,0x01,0x01,0x00,0x00,
0xc0,0xc0,0x20,0xe0,0xd0,0x30,0xf0,0x10,0xf0,0x10,0xf0,0x10,0xf8,0x18,0xfc,0x04,
0xf8,0x08,0xf0,0x30,0xc0,0x40,0xc0,0x40,0xc0,0x40,0xc0,0x40,0x80,0x80,0x00,0x00
};
const metasprite_t enemy_metasprite0[] = {
METASPR_ITEM(-8, -8, 0, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 2, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite1[] = {
METASPR_ITEM(-8, -8, 4, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 6, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite2[] = {
METASPR_ITEM(-8, -8, 8, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 10, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite3[] = {
METASPR_ITEM(-8, -8, 12, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 14, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite4[] = {
METASPR_ITEM(-8, -8, 16, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 18, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite5[] = {
METASPR_ITEM(-8, -8, 20, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 22, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite6[] = {
METASPR_ITEM(-8, -8, 24, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 26, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t enemy_metasprite7[] = {
METASPR_ITEM(-8, -8, 28, S_PAL(0) | S_PALETTE),
METASPR_ITEM(0, 8, 30, S_PAL(0) | S_PALETTE),
METASPR_TERM
};
const metasprite_t* const enemy_metasprites[8] = {
enemy_metasprite0, enemy_metasprite1, enemy_metasprite2, enemy_metasprite3, enemy_metasprite4, enemy_metasprite5, enemy_metasprite6, enemy_metasprite7
};
+31
View File
@@ -0,0 +1,31 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /enemy.png -c /enemy.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order -sp 0x10
#ifndef METASPRITE_enemy_H
#define METASPRITE_enemy_H
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
#define enemy_TILE_ORIGIN 0
#define enemy_TILE_W 8
#define enemy_TILE_H 16
#define enemy_WIDTH 16
#define enemy_HEIGHT 16
#define enemy_TILE_COUNT 32
#define enemy_PALETTE_COUNT 8
#define enemy_COLORS_PER_PALETTE 4
#define enemy_TOTAL_COLORS 32
#define enemy_PIVOT_X 8
#define enemy_PIVOT_Y 8
#define enemy_PIVOT_W 16
#define enemy_PIVOT_H 16
extern const metasprite_t* const enemy_metasprites[8];
BANKREF_EXTERN(enemy)
extern const palette_color_t enemy_palettes[32];
extern const uint8_t enemy_tiles[512];
#endif
+170
View File
@@ -0,0 +1,170 @@
#include "enemy_logic.h"
#include "globals.h"
#include "render.h" // For update_stamina_display
#include <gb/gb.h>
// Tile graphics required for metasprites
#include "player.h"
#include "enemy.h"
/**
* Gestisce l'intelligenza artificiale (AI) e il rendering del nemico (il fantasma).
*
* Sviluppo & Scelte Architetturali:
* 1. Cooldown System: Il fantasma fa "passi" esatti sulla griglia proprio come il giocatore.
* Tuttavia, se si muovesse a ogni frame, sarebbe impossibile scappare.
* Abbiamo implementato un timer di `enemy_cooldown` (60 frame = 1 secondo) di pausa
* tra un passo e l'altro. Il giocatore cammina/salta più velocemente, creando una
* tensione in cui devi pianificare i tuoi balzi per seminarlo.
* 2. Pathfinding (Ricerca del Percorso): Invece di usare A* (A-Star) che consumerebbe
* troppa RAM e CPU per un Game Boy a 8-bit, il fantasma usa una logica "Greedy".
* Calcola la distanza al quadrato verso il giocatore per ciascuna delle 4 direzioni
* valide (muri permettendo). Sceglie la direzione che *minimizza* questa distanza.
* Non essendo A*, può incastrarsi in vicoli ciechi a forma di "U", che è esattamente
* una debolezza voluta per permettere al giocatore di seminarlo usando il level design.
* 3. Collisione Pixel-Perfect: Anche se logica e pathfinding sono su griglia (Grid-Based),
* la Morte scatta solo se i *pixel* fisici a schermo dei due sprite si sovrappongono.
*/
void update_enemy_logic(void) {
// 1. Aggiorna l'interpolazione del movimento visivo del fantasma
if (enemy_is_moving) {
enemy_move_progress++;
if (enemy_move_progress == 16) {
// Movimento completato
enemy_is_moving = 0;
enemy_lx = enemy_target_lx;
enemy_ly = enemy_target_ly;
// Imposta una pausa di 1 secondo prima del prossimo passo (Bilanciamento difficoltà)
enemy_cooldown = 60;
}
}
// 2. Decrementa il timer di riposo se non si sta muovendo
if (enemy_cooldown > 0) {
enemy_cooldown--;
}
// 3. AI PATHFINDING: Calcola il prossimo passo se è pronto a muoversi
if (!enemy_is_moving && enemy_cooldown == 0) {
// Calcola Distanza di Chebyshev logica dal giocatore per sapere se siamo "vicini"
int8_t dx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t dy = (int8_t)player_ly - (int8_t)enemy_ly;
int8_t abs_dx = (dx < 0) ? -dx : dx;
int8_t abs_dy = (dy < 0) ? -dy : dy;
int8_t dist = (abs_dx > abs_dy) ? abs_dx : abs_dy;
// L'AI "si sveglia" e inizia a inseguirti solo se è entro 2 celle di distanza
// (cioè è entrato nel tuo cono visivo di nebbia)
if (dist <= 2) {
int8_t best_nx = enemy_lx;
int8_t best_ny = enemy_ly;
// Inizializza con la distanza Euclidea al QUADRATO (x^2 + y^2) corrente.
// Non usiamo la radice quadrata (sqrt) perché è un calcolo pesantissimo su Game Boy.
// Il quadrato della distanza conserva le proporzioni perfette per il confronto (min_dist).
int16_t min_dist_sq = (int16_t)dx * dx + (int16_t)dy * dy;
// Controllo 1: Direzione Down-Right (+1 X)
if (enemy_lx + 1 < MAP_SIZE && maze[enemy_ly][enemy_lx + 1] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)(enemy_lx + 1);
int8_t ndy = (int8_t)player_ly - (int8_t)enemy_ly;
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx + 1; best_ny = enemy_ly; }
}
// Controllo 2: Direzione Down-Left (+1 Y)
if (enemy_ly + 1 < MAP_SIZE && maze[enemy_ly + 1][enemy_lx] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t ndy = (int8_t)player_ly - (int8_t)(enemy_ly + 1);
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx; best_ny = enemy_ly + 1; }
}
// Controllo 3: Direzione Up-Left (-1 X)
if (enemy_lx > 0 && maze[enemy_ly][enemy_lx - 1] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)(enemy_lx - 1);
int8_t ndy = (int8_t)player_ly - (int8_t)enemy_ly;
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx - 1; best_ny = enemy_ly; }
}
// Controllo 4: Direzione Up-Right (-1 Y)
if (enemy_ly > 0 && maze[enemy_ly - 1][enemy_lx] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t ndy = (int8_t)player_ly - (int8_t)(enemy_ly - 1);
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx; best_ny = enemy_ly - 1; }
}
// Se abbiamo trovato un percorso migliore (Strictly closer)
if (best_nx != (int8_t)enemy_lx || best_ny != (int8_t)enemy_ly) {
enemy_is_moving = 1;
enemy_move_progress = 0;
enemy_start_lx = enemy_lx;
enemy_start_ly = enemy_ly;
enemy_target_lx = best_nx;
enemy_target_ly = best_ny;
// Preparazione coordinate pixel hardware per l'interpolazione fluida
enemy_start_px = (enemy_start_lx - enemy_start_ly) * 16 + 96;
enemy_start_py = (enemy_start_lx + enemy_start_ly) * 8 + 16;
enemy_target_px = (enemy_target_lx - enemy_target_ly) * 16 + 96;
enemy_target_py = (enemy_target_lx + enemy_target_ly) * 8 + 16;
}
}
}
// 4. RENDERING DEL NEMICO (Telecamera relativa)
// Recuperiamo i pixel assoluti
int16_t enemy_px, enemy_py;
if (enemy_is_moving) {
enemy_px = enemy_start_px + (((enemy_target_px - enemy_start_px) * (int16_t)enemy_move_progress) >> 4);
enemy_py = enemy_start_py + (((enemy_target_py - enemy_start_py) * (int16_t)enemy_move_progress) >> 4);
} else {
enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
}
// Il motore renderizza lo sfondo scrollando, ma gli SPRITE devono essere disegnati a mano.
// Togliamo il valore di scroll della telecamera (scroll_x, scroll_y) dalla posizione assoluta.
// +24 e +16 sono offset per l'allineamento hardware OAM del Game Boy per le griglie 16x16.
int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24;
int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16;
// Mostriamo lo sprite solo se è all'interno della griglia logica visibile (distanza <= 2)
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
if (edx < 0) edx = -edx;
if (edy < 0) edy = -edy;
uint8_t ep_dist = (edx > edy) ? edx : edy;
// Il limite x=168 e y=152 assicura di nascondere gli sprite appena fuori dai bordi fisici
if (ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
// Disegna il metasprite del Fantasma usando gli offset di memoria generati in enemy.h
move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, enemy_screen_x, enemy_screen_y);
} else {
// Nasconde lo sprite spostandolo a coordinate (0,0) offscreen
move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, 0, 0);
}
// 5. COLLISIONE FATALE (Player Hitbox check)
// Per calcolare esattamente il game over, usiamo la posizione *Pixel* di entrambi.
int16_t p_px = is_moving ? (start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4)) : ((player_lx - player_ly) * 16 + 96);
int16_t p_py = is_moving ? (start_py + (((target_py - start_py) * (int16_t)move_progress) >> 4)) : ((player_lx + player_ly) * 8 + 16);
int16_t dx_collision = p_px - enemy_px;
int16_t dy_collision = p_py - enemy_py;
if (dx_collision < 0) dx_collision = -dx_collision;
if (dy_collision < 0) dy_collision = -dy_collision;
// Se i pixel centrali sono vicini (X < 12 e Y < 6), scatena il GAME OVER
if (dx_collision < 12 && dy_collision < 6) {
game_over = 1; // 1 = Defeat
game_over_timer = 45; // Fermo immagine drammatico per 45 frame (~0.7s) prima del menu nero
update_stamina_display(); // Aggiornando con game_over=1 l'HUD scompare istantaneamente
// Esegue il Sound Effect della cattura: Suono profondo a caduta (Slide Down) sul Canale 1
NR10_REG = 0x1E; // sweep register: shift down
NR11_REG = 0x10; // 25% duty cycle
NR12_REG = 0xF3; // volume alto, calo rapido (envelope decrease)
NR13_REG = 0x00; // frequenza bassa
NR14_REG = 0xC6; // trigger
}
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef ENEMY_LOGIC_H
#define ENEMY_LOGIC_H
/**
* ==========================================
* ENEMY ARTIFICIAL INTELLIGENCE
* ==========================================
* Handles the logic for the ghost enemy that chases the player through the maze.
*/
// Call every frame during gameplay
void update_enemy_logic(void);
#endif
+243
View File
@@ -0,0 +1,243 @@
#include <gb/gb.h>
#include <gbdk/font.h>
#include <stdint.h>
#include <string.h>
#include <rand.h>
#include "engine.h"
#include "globals.h"
#include "maze.h"
#include "sound.h"
#include "render.h"
#include "player_logic.h"
#include "enemy_logic.h"
// Generated by png2asset
#include "tiles.h"
#include "player.h"
#include "enemy.h"
#include "gameover.h"
#include "victory.h"
#include "stamina.h"
#include "title_bg.h"
#ifdef USE_SPRITE_STAIRS
#include "stairs_sprite.h"
#endif
/**
* Inizializza la schermata del titolo
*/
void title_init(void) {
// Nasconde tutti gli sprite (sia personaggio che HUD)
for (uint8_t i = 0; i < 40; i++) move_sprite(i, 0, 0);
// Carica l'immagine di sfondo del titolo
set_bkg_data(0, title_bg_TILE_COUNT, title_bg_tiles);
set_bkg_tiles(0, 0, title_bg_WIDTH / 8, title_bg_HEIGHT / 8, title_bg_map);
// Configura la palette standard B/N
BGP_REG = 0xE4;
// Centra lo scroll della telecamera in alto a sinistra (0,0)
move_bkg(0, 0);
// Inizializza l'Audio Hardware del Game Boy
NR52_REG = 0x80; // Accende il chip audio
NR50_REG = 0x77; // Volume master al massimo
NR51_REG = 0xFF; // Abilita l'output stereo su tutti i canali
// Avvia la musica del titolo riavviando il sequencer
sound_reset_music_state();
remove_VBL(play_music_tick);
add_VBL(play_music_tick);
}
/**
* Loop di aggiornamento della schermata del titolo (non fa nulla, la pressione di START è gestita in main.c)
*/
void title_update(uint8_t keys, uint8_t prev_keys) {
// Vuoto. La musica continua in background via Interrupt (VBL)
}
/**
* Inizializza l'engine per la fase di Gameplay.
* Qui viene orchestrata la "nascita" di un nuovo livello.
*/
void engine_init(void) {
SPRITES_8x16; // Imposta la modalità Sprite Alti (16 pixel invece di 8)
// Hard reset Audio
NR52_REG = 0x80;
NR50_REG = 0x77;
NR51_REG = 0xFF;
// Carica il font testuale di sistema (IBM)
font_init();
font_t ibm_font = font_load(font_ibm);
font_set(ibm_font);
// Avvia il sequencer per la musica In-Game "Eerie pulse"
sound_reset_music_state();
remove_VBL(play_music_tick);
add_VBL(play_music_tick);
// Usa il registro divisore hardware (DIV_REG) per seedare l'RNG. Garantisce labirinti diversi ogni volta.
initrand(DIV_REG);
// DELEGA LA GENERAZIONE: Chiede al modulo maze.c di creare l'array della mappa
generate_maze();
// Imposta le palette (DMG compatibile)
OBP0_REG = 0xE4; // Palette Normale (Nero, Grigio Scuro, Grigio Chiaro, Bianco)
OBP1_REG = 0x1B; // Palette Invertita (per il fantasma: bianco, grigio scuro, nero)
BGP_REG = 0xE4; // Palette Background
// Carica in memoria VRAM la grafica di Tiles e Sprite
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles);
set_sprite_data(0, player_TILE_COUNT, player_tiles);
set_sprite_data(player_TILE_COUNT, enemy_TILE_COUNT, enemy_tiles);
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT, gameover_TILE_COUNT, gameover_tiles);
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT, victory_TILE_COUNT, victory_tiles);
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles);
#ifdef USE_SPRITE_STAIRS
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT + stamina_TILE_COUNT * 2, stairs_sprite_TILE_COUNT, stairs_sprite_tiles);
#endif
// SPAWN DEL GIOCATORE
player_lx = 1;
player_ly = 1;
// Fallback di sicurezza: Cerca la prima casella libera se 1,1 è occupato (teoricamente impossibile col DFS)
if (maze[player_ly][player_lx] == 0) {
uint8_t found = 0;
for (uint8_t y = 1; y < MAP_SIZE - 1; y++) {
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
if (maze[y][x] == 1) {
player_lx = x;
player_ly = y;
found = 1;
break;
}
}
if (found) break;
}
}
#ifdef USE_SPRITE_STAIRS
// HARDCODE STAIRS FOR TESTING
stairs_lx = player_lx + 1;
stairs_ly = player_ly;
maze[stairs_ly][stairs_lx] = 2;
#endif
// SPAWN DEL NEMICO
// Cerca randomicamente un punto lontano almeno 3 celle dal giocatore
while (1) {
uint8_t rx = rand() % MAP_SIZE;
uint8_t ry = rand() % MAP_SIZE;
if (maze[ry][rx] == 1) {
int8_t dx = (int8_t)rx - (int8_t)player_lx;
int8_t dy = (int8_t)ry - (int8_t)player_ly;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int8_t dist = (dx > dy) ? dx : dy;
if (dist >= 3) {
enemy_lx = rx;
enemy_ly = ry;
break;
}
}
}
// Reset Variabili di Gameplay
enemy_is_moving = 0;
enemy_cooldown = 60;
game_over = 0;
stamina = 100;
stamina_recharge_timer = 0;
is_jumping = 0;
is_moving = 0;
// Richiama render.c per forzare un disegno iniziale della scena
draw_map(player_lx, player_ly);
update_camera();
update_player_sprite();
}
/**
* Loop Principale di Aggiornamento del Gioco (chiamato ad ogni Frame ~60 FPS).
* Questo metodo funge da "Direttore d'Orchestra", delegando i compiti ai moduli specializzati.
*/
void engine_update(uint8_t keys, uint8_t prev_keys) {
// --- GESTIONE STATO DI FINE GIOCO (Sconfitta o Vittoria) ---
if (game_over) {
if (game_over_timer > 0) {
game_over_timer--;
// Quando scade il timer drammatico...
if (game_over_timer == 0) {
// ...Svuota lo schermo
memset(map_buffer, 0, sizeof(map_buffer));
set_bkg_tiles(0, 0, 32, 32, map_buffer);
// Imposta i timer audio per far iniziare la musica finale dal modulo sound.c
// La musica verrà suonata dall'interrupt play_music_tick in background.
if (game_over == 1) {
// Start gameover sequence state
// gameover_music_timer/step are handled in play_music_tick inside sound.c automatically
// We just trigger a starting dramatic beat.
NR21_REG = 0x80;
NR22_REG = 0xF3;
NR23_REG = 0x2C;
NR24_REG = 0x84;
}
}
} else {
// Dopo il timer, disegna i metasprite di VITTORIA o GAME OVER fissi in mezzo allo schermo
if (game_over == 1) {
move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 88, 120);
} else if (game_over == 2) {
move_metasprite(victory_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT, 8, 88, 120);
}
// Il giocatore rimane visibile al centro, il nemico scompare se vinto o appare se sconfitto.
update_player_sprite();
int16_t enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
int16_t enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24;
int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16;
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
if (edx < 0) edx = -edx;
if (edy < 0) edy = -edy;
uint8_t ep_dist = (edx > edy) ? edx : edy;
if (game_over == 1 && ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, enemy_screen_x, enemy_screen_y);
} else {
move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, 0, 0);
}
// Attende la pressione di START per riavviare
if ((keys & J_START) && !(prev_keys & J_START)) {
// Nasconde la grafica testuale e ricarica tutto l'engine
move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 0, 0);
move_metasprite(victory_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT, 8, 0, 0);
engine_init();
}
}
return;
}
// --- GESTIONE DEL LOOP DI GIOCO STANDARD ---
// 1. DELEGA L'AI DEL NEMICO (se il fantasma si deve muovere, lo calcola qui)
update_enemy_logic();
// 2. DELEGA LA LOGICA DEL GIOCATORE (movimento, tasti, animazioni)
update_player_movement(keys, prev_keys);
// Note: Il rendering effettivo della mappa e della telecamera avviene già all'interno di
// update_player_movement durante l'interpolazione del passo (per fluidità sub-tile).
}
View File
+2 -2
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: gameover.png -c gameover.c -bpp 2 -noflip -keep_palette_order
// Conversion args: /gameover.png -c /gameover.c -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
@@ -8,7 +8,7 @@
BANKREF(gameover)
const palette_color_t gameover_palettes[32] = {
RGB8(255,255,255), RGB8(170,170,170), RGB8( 85, 85, 85), RGB8( 0, 0, 0),
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
+1 -1
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: gameover.png -c gameover.c -bpp 2 -noflip -keep_palette_order
// Conversion args: /gameover.png -c /gameover.c -bpp 2 -noflip -keep_palette_order
#ifndef METASPRITE_gameover_H
#define METASPRITE_gameover_H
+38
View File
@@ -0,0 +1,38 @@
#include "globals.h"
// Define the actual memory storage for all global variables
volatile uint8_t game_over = 0;
volatile uint8_t game_over_timer = 0;
uint8_t maze[MAP_SIZE][MAP_SIZE];
uint8_t map_buffer[32 * 32];
uint8_t player_lx = 1;
uint8_t player_ly = 1;
uint8_t player_dir = 0;
uint8_t scroll_x = 0;
int8_t scroll_y = 0;
uint8_t stairs_lx = 0;
uint8_t stairs_ly = 0;
uint8_t is_moving = 0;
uint8_t is_jumping = 0;
uint8_t move_progress = 0;
int8_t start_lx, start_ly;
int8_t target_lx, target_ly;
int16_t start_px, start_py;
int16_t target_px, target_py;
uint8_t stamina = 100;
uint8_t stamina_recharge_timer = 0;
uint8_t enemy_lx = 0;
uint8_t enemy_ly = 0;
uint8_t enemy_is_moving = 0;
uint8_t enemy_move_progress = 0;
int8_t enemy_start_lx, enemy_start_ly;
int8_t enemy_target_lx, enemy_target_ly;
int16_t enemy_start_px, enemy_start_py;
int16_t enemy_target_px, enemy_target_py;
uint8_t enemy_cooldown = 0;
+72
View File
@@ -0,0 +1,72 @@
#ifndef GLOBALS_H
#define GLOBALS_H
#include <stdint.h>
/**
* ==========================================
* GLOBAL GAME STATE
* ==========================================
* This file contains all the global variables that are shared across different
* logical modules (e.g., rendering, player logic, enemy logic, audio).
* We place them here to avoid circular dependencies between modules.
*/
// --- General Application State ---
// app_state = 0 means Title Screen, 1 means Gameplay
extern uint8_t app_state;
// game_over = 0 means Playing, 1 means Defeat (Ghost caught you), 2 means Victory (Reached portal)
extern volatile uint8_t game_over;
// Timer used to delay actions after game over (e.g. before showing text or playing music)
extern volatile uint8_t game_over_timer;
// --- Map Data ---
#define MAP_SIZE 7
// The generated maze: 0 = Wall, 1 = Floor, 2 = Victory Tile
extern uint8_t maze[MAP_SIZE][MAP_SIZE];
// --- Camera & Rendering ---
// Map buffer used to draw the isometric tiles into the Game Boy Background map
extern uint8_t map_buffer[32 * 32];
// Camera scroll coordinates to keep the player centered
extern uint8_t scroll_x;
extern int8_t scroll_y;
extern uint8_t stairs_lx;
extern uint8_t stairs_ly;
// --- Mappa e Entità ---
// The generated maze: 0 = Wall, 1 = Floor, 2 = Victory Tile
extern uint8_t maze[MAP_SIZE][MAP_SIZE];
extern uint8_t player_lx;
extern uint8_t player_ly;
extern uint8_t player_dir; // 0=DR, 1=DL, 2=UL, 3=UR
// --- Movement & Physics State ---
// Shared variables for interpolated movement (walking/jumping)
extern uint8_t is_moving;
extern uint8_t is_jumping;
extern uint8_t move_progress; // 0 to 16, tracks the sub-tile animation progress
extern int8_t start_lx, start_ly;
extern int8_t target_lx, target_ly;
extern int16_t start_px, start_py;
extern int16_t target_px, target_py;
// --- Stamina System ---
// Stamina powers the jump mechanic. 100 = full. Recharges over time.
extern uint8_t stamina;
extern uint8_t stamina_recharge_timer;
// --- Enemy State ---
extern uint8_t enemy_lx;
extern uint8_t enemy_ly;
extern uint8_t enemy_is_moving;
extern uint8_t enemy_move_progress;
extern int8_t enemy_start_lx, enemy_start_ly;
extern int8_t enemy_target_lx, enemy_target_ly;
extern int16_t enemy_start_px, enemy_start_py;
extern int16_t enemy_target_px, enemy_target_py;
extern uint8_t enemy_cooldown;
#endif
View File
+132
View File
@@ -0,0 +1,132 @@
#include "maze.h"
#include "globals.h"
#include <string.h>
#include <rand.h>
/**
* Generates a randomized maze using a Depth-First Search (DFS) algorithm with backtracking.
*
* Sviluppo & Scelte Architetturali:
* 1. Perché DFS? Questo algoritmo è perfetto per labirinti garantendo che ogni cella sia
* raggiungibile partendo dall'inizio (nessuna isola irraggiungibile).
* 2. Il labirinto è una griglia MAP_SIZE x MAP_SIZE in cui le celle dispari (es. 1,1 o 3,3)
* sono le stanze, mentre le celle pari sono i muri divisori. Il DFS scava i muri saltando
* di 2 in 2 e abbattendo il muro in mezzo.
* 3. Abbiamo aggiunto una fase post-DFS che rimuove casualmente qualche muro rimasto (con
* il 15% di probabilità). Questo spezza la struttura "perfetta" del labirinto creando
* dei percorsi ciclici (loop). I loop sono vitali in un gioco con un nemico che ti insegue,
* permettendo al giocatore di aggirare il nemico scappando in cerchio.
* 4. La casella della VITTORIA viene piazzata alla fine. Usiamo la "Distanza di Manhattan"
* (somma della distanza X e Y) dalla partenza (1,1) per assicurarci che il traguardo
* sia la casella più lontana in assoluto.
*/
void generate_maze(void) {
// 1. Inizializziamo l'intera mappa a 0 (che rappresenta un Muro / Spazio Vuoto)
memset(maze, 0, sizeof(maze));
// Stack array per il backtracking. La griglia 7x7 ha al massimo 49 celle dispari (stanze).
uint8_t stack_x[49];
uint8_t stack_y[49];
uint8_t stack_ptr = 0;
// Partiamo sempre dalle coordinate (1, 1) in alto a sinistra
uint8_t cx = 1;
uint8_t cy = 1;
maze[cy][cx] = 1; // 1 = Pavimento calpestabile
while (1) {
// Cerchiamo i vicini non visitati a distanza 2 (saltando il muro divisorio)
uint8_t nx[4];
uint8_t ny[4];
uint8_t count = 0;
// Su
if (cy >= 3 && maze[cy - 2][cx] == 0) {
nx[count] = cx; ny[count] = cy - 2; count++;
}
// Giù
if (cy <= MAP_SIZE - 4 && maze[cy + 2][cx] == 0) {
nx[count] = cx; ny[count] = cy + 2; count++;
}
// Sinistra
if (cx >= 3 && maze[cy][cx - 2] == 0) {
nx[count] = cx - 2; ny[count] = cy; count++;
}
// Destra
if (cx <= MAP_SIZE - 4 && maze[cy][cx + 2] == 0) {
nx[count] = cx + 2; ny[count] = cy; count++;
}
// Se ci sono vicini validi
if (count > 0) {
// Scegli una direzione a caso tra quelle disponibili
uint8_t dir = rand() % count;
// Salviamo la cella corrente nello stack per poterci tornare
stack_x[stack_ptr] = cx;
stack_y[stack_ptr] = cy;
stack_ptr++;
// Abbattiamo il muro nel mezzo (calcolato facendo la media aritmetica delle coordinate)
maze[(cy + ny[dir]) / 2][(cx + nx[dir]) / 2] = 1;
// Spostiamo la posizione corrente sul vicino scelto
cx = nx[dir];
cy = ny[dir];
maze[cy][cx] = 1;
}
// Se non ci sono vicini, torna indietro prendendo l'ultima cella salvata dallo stack
else if (stack_ptr > 0) {
stack_ptr--;
cx = stack_x[stack_ptr];
cy = stack_y[stack_ptr];
}
// Se lo stack è vuoto, abbiamo visitato tutte le celle: il labirinto è completo!
else {
break;
}
}
// FASE 2: Riapriamo casualmente dei muri per creare percorsi alternativi e loop (15% chance).
// Questo è cruciale per la giocabilità: permette al giocatore di aggirare l'inseguitore!
for (uint8_t y = 1; y < MAP_SIZE - 1; y++) {
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
if (maze[y][x] == 0) {
// Se la casella è un muro che connette orizzontalmente o verticalmente due corridoi
if ((maze[y][x - 1] == 1 && maze[y][x + 1] == 1) ||
(maze[y - 1][x] == 1 && maze[y + 1][x] == 1)) {
// Genera numero casuale tra 0 e 99. Se < 15, trasforma in pavimento.
if ((rand() % 100) < 15) {
maze[y][x] = 1;
}
}
}
}
}
// FASE 3: Calcoliamo il traguardo (Casella di Vittoria).
// Invece della coordinata più lontana, scegliamo un punto casuale
// lungo il bordo inferiore del labirinto (y = MAP_SIZE - 2).
// Questo permette alla scala di apparire ovunque sul bordo sud.
uint8_t valid_x[MAP_SIZE];
uint8_t num_valid = 0;
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
if (maze[MAP_SIZE - 2][x] == 1) {
valid_x[num_valid] = x;
num_valid++;
}
}
if (num_valid > 0) {
uint8_t target_x = valid_x[rand() % num_valid];
maze[MAP_SIZE - 2][target_x] = 2;
stairs_lx = target_x;
stairs_ly = MAP_SIZE - 2;
} else {
// Fallback estremo se la riga in fondo fosse inaccessibile
maze[MAP_SIZE - 2][MAP_SIZE - 2] = 2;
stairs_lx = MAP_SIZE - 2;
stairs_ly = MAP_SIZE - 2;
}
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef MAZE_H
#define MAZE_H
/**
* ==========================================
* MAZE GENERATION
* ==========================================
* Handles the logic for creating the procedural level.
*/
void generate_maze(void);
#endif
+102
View File
@@ -0,0 +1,102 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /player.png -c /player.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(player)
const palette_color_t player_palettes[32] = {
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t player_tiles[448] = {
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x2b,0x3c,0x2e,0x39,0x2f,0x39,0x2f,0x38,0x2f,0x39,
0x1f,0x1c,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x0a,0x0e,0x1e,0x1e,0x1e,0x1e,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0xd4,0x3c,0xd4,0x3c,0xf4,0x3c,0xf4,0x1c,0xf4,0xfc,
0xf8,0x38,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x50,0x70,0x78,0x78,0x78,0x78,
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x2b,0x3c,0x2e,0x39,0x2f,0x39,0x2f,0x38,0x2f,0x39,
0x1f,0x1c,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x08,0x0c,0x1c,0x1c,0x1c,0x1c,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0xd4,0x3c,0xd4,0x3c,0xf4,0x3c,0xf4,0x1c,0xf4,0xfc,
0xf8,0x38,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x20,0x60,0xe0,0xe0,0xe0,0xe0,
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x2b,0x3c,0x2b,0x3c,0x2f,0x3c,0x2f,0x38,0x2f,0x3f,
0x1f,0x1c,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x0a,0x0e,0x1e,0x1e,0x1e,0x1e,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0xd4,0x3c,0x74,0x9c,0xf4,0x9c,0xf4,0x1c,0xf4,0x9c,
0xf8,0x38,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x50,0x70,0x78,0x78,0x78,0x78,
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x2b,0x3c,0x2b,0x3c,0x2f,0x3c,0x2f,0x38,0x2f,0x3f,
0x1f,0x1c,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x04,0x06,0x07,0x07,0x07,0x07,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0xd4,0x3c,0x74,0x9c,0xf4,0x9c,0xf4,0x1c,0xf4,0x9c,
0xf8,0x38,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x10,0x30,0x38,0x38,0x38,0x38,
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,
0x1f,0x1f,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x0a,0x0e,0x1e,0x1e,0x1e,0x1e,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,
0xf8,0xf8,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x50,0x70,0x78,0x78,0x78,0x78,
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,
0x1f,0x1f,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x04,0x06,0x07,0x07,0x07,0x07,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,
0xf8,0xf8,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x10,0x30,0x38,0x38,0x38,0x38,
0x0f,0x0f,0x10,0x1f,0x27,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,0x20,0x3f,
0x1f,0x1f,0x2f,0x3f,0x28,0x3f,0x38,0x2f,0x3c,0x3f,0x08,0x0c,0x1c,0x1c,0x1c,0x1c,
0xf0,0xf0,0x08,0xf8,0xe4,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,0x04,0xfc,
0xf8,0xf8,0xf4,0xfc,0x14,0xfc,0x1c,0xf4,0x3c,0xfc,0x20,0x60,0xe0,0xe0,0xe0,0xe0
};
const metasprite_t player_metasprite0[] = {
METASPR_ITEM(-8, -8, 0, S_PAL(0)),
METASPR_ITEM(0, 8, 2, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite1[] = {
METASPR_ITEM(-8, -8, 4, S_PAL(0)),
METASPR_ITEM(0, 8, 6, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite2[] = {
METASPR_ITEM(-8, -8, 8, S_PAL(0)),
METASPR_ITEM(0, 8, 10, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite3[] = {
METASPR_ITEM(-8, -8, 12, S_PAL(0)),
METASPR_ITEM(0, 8, 14, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite4[] = {
METASPR_ITEM(-8, -8, 16, S_PAL(0)),
METASPR_ITEM(0, 8, 18, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite5[] = {
METASPR_ITEM(-8, -8, 20, S_PAL(0)),
METASPR_ITEM(0, 8, 22, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite6[] = {
METASPR_ITEM(-8, -8, 16, S_PAL(0)),
METASPR_ITEM(0, 8, 18, S_PAL(0)),
METASPR_TERM
};
const metasprite_t player_metasprite7[] = {
METASPR_ITEM(-8, -8, 24, S_PAL(0)),
METASPR_ITEM(0, 8, 26, S_PAL(0)),
METASPR_TERM
};
const metasprite_t* const player_metasprites[8] = {
player_metasprite0, player_metasprite1, player_metasprite2, player_metasprite3, player_metasprite4, player_metasprite5, player_metasprite6, player_metasprite7
};
+1 -1
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: player.png -c player.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order
// Conversion args: /player.png -c /player.c -sw 16 -sh 16 -bpp 2 -noflip -keep_palette_order
#ifndef METASPRITE_player_H
#define METASPRITE_player_H
+196
View File
@@ -0,0 +1,196 @@
#include "player_logic.h"
#include "globals.h"
#include "render.h"
#include <gb/gb.h>
#include <string.h>
// Delayed Auto-Shift variables
static uint8_t das_timer = 0;
static uint8_t das_active = 0;
#define DAS_DELAY 12
#define DAS_REPEAT 6
/**
* Gestisce la logica di movimento del giocatore, i salti e le collisioni.
*
* Sviluppo & Scelte Architetturali:
* 1. State Machine Rigida: Se `is_moving` è vero, ignoriamo qualsiasi altro input
* finché l'animazione di transizione tra i tile (16 frame) non è completata.
* Questo ci assicura un movimento perfettamente a griglia "grid-based", in stile Pokémon o Zelda.
* 2. Delayed Auto-Shift (DAS): Come in Tetris, se tieni premuto un tasto direzionale,
* c'è un ritardo iniziale (DAS_DELAY) seguito da ripetizioni veloci (DAS_REPEAT).
* Questo rende i controlli molto più fluidi e reattivi rispetto a una semplice lettura continua.
* 3. Meccanica del Salto: Premendo A+Direzione, il giocatore può saltare *esattamente* 2 celle
* più in . Il codice verifica se la cella intermedia (+1) è un ostacolo (0) e se
* la cella di atterraggio (+2) è calpestabile. Consuma 60 di stamina per bilanciare il vantaggio tattico.
*/
void update_player_movement(uint8_t keys, uint8_t prev_keys) {
// --- Gestione della Stamina ---
// Ricarica la stamina di 1 punto al secondo (60 frames su Game Boy)
stamina_recharge_timer++;
if (stamina_recharge_timer >= 60) {
stamina_recharge_timer = 0;
if (stamina < 100) {
stamina++;
update_stamina_display();
}
}
// --- State Machine: Transizione Animazione ---
// Se siamo nel mezzo di uno spostamento tra tile, calcoliamo i pixel interpolati
if (is_moving) {
move_progress++;
// Interpolazione lineare (LERP) da start a target
int16_t px = start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4); // >> 4 equivale a / 16
int16_t py = start_py + (((target_py - start_py) * (int16_t)move_progress) >> 4);
// Sincronizza la telecamera con i nuovi pixel sub-tile calcolati
scroll_x = px - 64;
scroll_y = py - 72;
move_bkg(scroll_x, scroll_y);
update_player_sprite();
// A metà strada (frame 8/16), forziamo un ridisegno della mappa per aggiornare la nebbia (Fog of War)
// in modo che il caricamento appaia fluido al giocatore
if (move_progress == 8) {
draw_map(target_lx, target_ly);
}
// Fine del movimento
if (move_progress == 16) {
is_moving = 0;
is_jumping = 0;
player_lx = target_lx;
player_ly = target_ly;
// Snap matematico alla destinazione per prevenire derive dovute ad arrotondamenti
int16_t final_px = (player_lx - player_ly) * 16 + 96;
int16_t final_py = (player_lx + player_ly) * 8 + 16;
scroll_x = final_px - 64;
scroll_y = final_py - 72;
move_bkg(scroll_x, scroll_y);
draw_map(player_lx, player_ly);
update_player_sprite();
// Controllo Casella Vittoria (ID 2)
if (maze[player_ly][player_lx] == 2) {
game_over = 2; // Stato 2 = Vittoria
game_over_timer = 30; // Piccolo ritardo prima che la scena cambi
update_stamina_display();
// Effetto dissolvenza: ripulisce tutto lo schermo per mostrare la scritta VITTORIA su sfondo nero
memset(map_buffer, 0, sizeof(map_buffer));
set_bkg_tiles(0, 0, 32, 32, map_buffer);
}
}
return; // Interrompe qui se stiamo camminando
}
// --- Lettura Controlli (Polling) con DAS ---
uint8_t keys_pressed = keys & ~prev_keys; // Tasti premuti ESATTAMENTE in questo frame
if (keys_pressed) {
das_timer = DAS_DELAY;
das_active = 1;
} else if (keys && das_active) {
// Se si tiene premuto un tasto...
if (das_timer > 0) {
das_timer--;
}
// Se il timer arriva a 0, emuliamo una pressione ripetuta
if (das_timer == 0 && !is_moving) {
das_timer = DAS_REPEAT;
keys_pressed = keys;
}
} else {
das_active = 0;
}
// Traduzione Input Direzionale in Movimento Logico Grid-Based
int8_t move_lx = 0;
int8_t move_ly = 0;
if (keys_pressed & J_RIGHT) {
move_lx = 1; player_dir = 0; // Direzione Down-Right
} else if (keys_pressed & J_LEFT) {
move_lx = -1; player_dir = 2; // Direzione Up-Left
} else if (keys_pressed & J_DOWN) {
move_ly = 1; player_dir = 1; // Direzione Down-Left
} else if (keys_pressed & J_UP) {
move_ly = -1; player_dir = 3; // Direzione Up-Right
}
// Se stiamo cercando di muoverci
if (move_lx != 0 || move_ly != 0) {
// 1. Controllo Salto: Tasto A premuto insieme alla direzione
if (keys & J_A) {
// Il salto ci scaglia a distanza 2 lungo l'asse
int8_t land_lx = player_lx + move_lx * 2;
int8_t land_ly = player_ly + move_ly * 2;
// Verifica che il salto cada all'interno del labirinto
if (land_lx >= 0 && land_lx < MAP_SIZE && land_ly >= 0 && land_ly < MAP_SIZE) {
// Condizioni del salto:
// a) La cella intermedia DEVE essere un muro (maze == 0)
// b) La cella di arrivo DEVE essere un pavimento o vittoria (maze == 1 o 2)
// c) Devi avere abbastanza stamina (>= 60)
if (maze[player_ly + move_ly][player_lx + move_lx] == 0 && (maze[land_ly][land_lx] == 1 || maze[land_ly][land_lx] == 2) && stamina >= 60) {
is_moving = 1;
is_jumping = 1; // Questo attiva l'arco parabolico in render.c
move_progress = 0;
stamina -= 60;
start_lx = player_lx;
start_ly = player_ly;
target_lx = land_lx;
target_ly = land_ly;
start_px = (start_lx - start_ly) * 16 + 96;
start_py = (start_lx + start_ly) * 8 + 16;
target_px = (target_lx - target_ly) * 16 + 96;
target_py = (target_lx + target_ly) * 8 + 16;
// Suono Salto (Pitch Sweep Up sul Canale 1)
NR10_REG = 0x15;
NR11_REG = 0x80;
NR12_REG = 0xF3;
NR13_REG = 0x00;
NR14_REG = 0xC3;
update_stamina_display();
update_player_sprite();
return;
}
}
// Se le condizioni di salto falliscono, aggiorna semplicemente il frame per far girare il personaggio
update_player_sprite();
}
// 2. Movimento standard (Camminata)
else {
int8_t new_lx = player_lx + move_lx;
int8_t new_ly = player_ly + move_ly;
if (new_lx >= 0 && new_lx < MAP_SIZE && new_ly >= 0 && new_ly < MAP_SIZE) {
// Se la destinazione è pavimento normale o casella di vittoria
if (maze[new_ly][new_lx] == 1 || maze[new_ly][new_lx] == 2) {
is_moving = 1;
move_progress = 0;
start_lx = player_lx;
start_ly = player_ly;
target_lx = new_lx;
target_ly = new_ly;
start_px = (start_lx - start_ly) * 16 + 96;
start_py = (start_lx + start_ly) * 8 + 16;
target_px = (target_lx - target_ly) * 16 + 96;
target_py = (target_lx + target_ly) * 8 + 16;
}
}
// Qualunque cosa accada, aggiorniamo il frame dello sprite per riflettere la direzione
update_player_sprite();
}
}
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef PLAYER_LOGIC_H
#define PLAYER_LOGIC_H
#include <stdint.h>
/**
* ==========================================
* PLAYER CONTROLLER LOGIC
* ==========================================
* Handles user input, delayed auto-shift (DAS) for movement repeat,
* and the logic for walking, jumping, and collision.
*/
// Call every frame during gameplay
void update_player_movement(uint8_t keys, uint8_t prev_keys);
#endif
+273
View File
@@ -0,0 +1,273 @@
#include "render.h"
#include "globals.h"
#include <gb/gb.h>
#include <string.h>
#include "tiles.h"
#include "player.h"
#include "enemy.h"
#include "gameover.h"
#include "victory.h"
#include "stamina.h"
#include "maze.h"
#ifdef USE_SPRITE_STAIRS
#include "stairs_sprite.h"
#endif
uint8_t get_tile_state(int8_t cx, int8_t cy, int8_t lx, int8_t ly) {
if (lx < 0 || lx >= MAP_SIZE || ly < 0 || ly >= MAP_SIZE) return 0;
if (maze[ly][lx] == 0) return 0;
int8_t dx = lx - cx;
int8_t dy = ly - cy;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int8_t dist = (dx > dy) ? dx : dy;
if (dist > 2) return 0;
if (dist == 2) return 2;
return 1;
}
/**
* Aggiorna l'interfaccia utente (UI) della barra della stamina.
*
* Sviluppo & Scelte Architetturali:
* 1. La UI su Game Boy può essere fatta in Background (HUD window) o tramite Sprite.
* Abbiamo scelto gli Sprite (ID da 18 a 22) perché la Window è più difficile da
* gestire insieme allo scrolling isometrico fluido.
* 2. Gli sprite sono posizionati a coordinate fisiche fisse sullo schermo
* (es. 112, 16 per l'angolo in alto a destra). Il move_bkg della telecamera NON li muove!
* 3. La barra è lunga 5 segmenti (40 pixel totali). L'algoritmo converte il valore `stamina` (0-100)
* in pixel (0-40) e seleziona il tile giusto dal set per mostrare il bordo arrotondato e
* il livello di riempimento.
*/
void update_stamina_display(void) {
if (game_over) {
// Nasconde la barra spostando gli sprite fuori schermo quando finisce la partita
for (uint8_t i = 0; i < 5; i++) {
move_sprite(18 + i, 0, 0);
}
return;
}
// Posizionamento in alto a destra (+8, +16 sono gli offset hardware del Game Boy per gli sprite)
move_sprite(18, (uint8_t)(112 + 8), 16);
move_sprite(19, (uint8_t)(120 + 8), 16);
move_sprite(20, (uint8_t)(128 + 8), 16);
move_sprite(21, (uint8_t)(136 + 8), 16);
move_sprite(22, (uint8_t)(144 + 8), 16);
// Convertiamo 0-100% in 0-40 pixels
uint16_t temp = (uint16_t)stamina * 40;
uint8_t num_pixels = temp / 100;
// Offset di base nella VRAM dove sono caricati i tile della stamina
uint8_t base_tile = player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT; // 44
for (uint8_t i = 0; i < 5; i++) {
uint8_t tile_idx;
if (i == 0) {
tile_idx = (num_pixels >= 8) ? 11 : 9; // Tappo sinistro pieno (11) o vuoto (9)
} else if (i == 4) {
tile_idx = (num_pixels >= 40) ? 12 : 10; // Tappo destro pieno (12) o vuoto (10)
} else {
int8_t seg_px = (int8_t)num_pixels - (i * 8);
if (seg_px < 0) seg_px = 0;
if (seg_px > 8) seg_px = 8;
tile_idx = seg_px; // Segmenti centrali (0..8 px)
}
// Imposta il frame dell'animazione
set_sprite_tile(18 + i, base_tile + tile_idx * 2);
}
}
/**
* Disegna la porzione visibile del labirinto e applica il sistema "Fog of War".
*
* Sviluppo & Scelte Architetturali:
* 1. Proiezione Isometrica: Convertiamo le coordinate logiche della griglia X,Y
* in coordinate schermo isometriche. La formula matematica standard è:
* iso_x = (X - Y) * (TileWidth / 2)
* iso_y = (X + Y) * (TileHeight / 2)
* Questo fa ruotare la mappa di 45 gradi e la schiaccia orizzontalmente.
* 2. Distanza di Chebyshev: Usata per la nebbia. Nascondiamo tutti i blocchi che
* distano logicamente più di 2 caselle dal centro (`max(|dx|, |dy|)`).
* Questo crea un quadrato perfetto di visibilità (5x5) attorno al giocatore,
* più leggero da calcolare sul Game Boy rispetto all'Euclidea (che usa la radice quadrata).
* 3. Auto-Tiling Mask: Calcoliamo dinamicamente quali muri sono adiacenti alla cella
* per disegnare bordi e variazioni grafiche (usando bit masking `(has_tl ? 1 : 0) | ...`).
*/
void draw_map(uint8_t center_x, uint8_t center_y) {
// Svuotiamo l'intero buffer della mappa con l'indice 0 (casella nera vuota)
memset(map_buffer, 0, sizeof(map_buffer));
// Calcoliamo la "finestra" di mappa 5x5 da processare (per ottimizzazione, ignoriamo la mappa intera)
int8_t start_x = center_x - 2;
if (start_x < 0) start_x = 0;
int8_t end_x = center_x + 2;
if (end_x >= MAP_SIZE) end_x = MAP_SIZE - 1;
int8_t start_y = center_y - 2;
if (start_y < 0) start_y = 0;
int8_t end_y = center_y + 2;
if (end_y >= MAP_SIZE) end_y = MAP_SIZE - 1;
// Processiamo la mappa in DUE passate per risolvere il problema dell'overlapping isometrico.
// L'engine disegna da Nord a Sud. I tile a Sud sovrascrivono la metà inferiore dei tile a Nord.
// Se disegnassimo le scale nella prima passata, i pavimenti davanti ad esse sovrascriverebbero
// i gradini con i loro angoli trasparenti (che sono di colore piatto).
// Disegnandole per ultime, le scale sovrascriveranno i pavimenti davanti, e grazie alle maschere
// dei propri angoli inferiori, si fonderanno perfettamente con il colore del pavimento.
// Passata 1: Pavimenti normali
for (int8_t ly = start_y; ly <= end_y; ly++) {
for (int8_t lx = start_x; lx <= end_x; lx++) {
#ifdef USE_SPRITE_STAIRS
if (maze[ly][lx] == 1 || maze[ly][lx] == 2) {
#else
if (maze[ly][lx] == 1) {
#endif
// Distanza di Chebyshev dal giocatore
int8_t dx = lx - center_x;
int8_t dy = ly - center_y;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int8_t dist = (dx > dy) ? dx : dy;
if (dist > 2) continue;
int8_t iso_x = (lx - ly) * 2 + 12;
int8_t iso_y = (lx + ly) * 1 + 2;
uint8_t is_alt = ((lx + ly) % 2 == 0);
uint8_t state_tl = get_tile_state(center_x, center_y, lx - 1, ly);
uint8_t state_tr = get_tile_state(center_x, center_y, lx, ly - 1);
uint8_t mask = state_tl + state_tr * 3;
uint8_t v;
if (dist == 2) {
v = is_alt ? (27 + mask) : (18 + mask);
} else {
v = is_alt ? (9 + mask) : mask;
}
for (uint8_t y = 0; y < 2; y++) {
for (uint8_t x = 0; x < 4; x++) {
uint8_t target_x = (iso_x + x) & 31;
uint8_t target_y = (iso_y + y) & 31;
uint8_t src_tile = tiles_map[4 + v * 8 + y * 4 + x];
map_buffer[target_y * 32 + target_x] = src_tile;
}
}
}
}
}
#ifndef USE_SPRITE_STAIRS
// Passata 2: Scala della Vittoria (disegnata sempre sopra)
for (int8_t ly = start_y; ly <= end_y; ly++) {
for (int8_t lx = start_x; lx <= end_x; lx++) {
if (maze[ly][lx] == 2) {
int8_t dx = lx - center_x;
int8_t dy = ly - center_y;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
int8_t dist = (dx > dy) ? dx : dy;
if (dist > 2) continue;
int8_t iso_x = (lx - ly) * 2 + 12;
int8_t iso_y = (lx + ly) * 1 + 2;
uint8_t is_alt = ((lx + ly) % 2 == 0);
uint8_t state_tl = get_tile_state(center_x, center_y, lx - 1, ly);
uint8_t state_tr = get_tile_state(center_x, center_y, lx, ly - 1);
uint8_t state_bl = get_tile_state(center_x, center_y, lx, ly + 1);
uint8_t state_br = get_tile_state(center_x, center_y, lx + 1, ly);
uint16_t mask = state_tl + state_tr * 3 + state_bl * 9 + state_br * 27;
uint16_t v;
if (dist == 2) {
v = is_alt ? (36 + 243 + mask) : (36 + 162 + mask);
} else {
v = is_alt ? (36 + 81 + mask) : (36 + mask);
}
for (uint8_t y = 0; y < 2; y++) {
for (uint8_t x = 0; x < 4; x++) {
uint8_t target_x = (iso_x + x) & 31;
uint8_t target_y = (iso_y + y) & 31;
uint8_t src_tile = tiles_map[4 + v * 8 + y * 4 + x];
map_buffer[target_y * 32 + target_x] = src_tile;
}
}
}
}
}
#endif
update_stamina_display();
// Ottimizzazione hardware critica: trasferire tutta la mappa (1024 bytes) via set_bkg_tiles
// causa lag e sfarfallii (VRAM access). Trasferiamo solo le righe da 2 a 17 (16 righe totali),
// che è la porzione visibile del Game Boy (160x144 px) in cui si svolge l'azione.
set_bkg_tiles(0, 2, 32, 16, &map_buffer[2 * 32]);
}
/**
* Aggiorna il registro scroll hardware della telecamera (SCX, SCY)
* calcolando la proiezione isometrica in pixel e centrandola nello schermo (160/2, 144/2).
*/
void update_camera(void) {
// Coordinate fisiche pixel calcolate allo stesso modo del motore grafico
int16_t px = (player_lx - player_ly) * 16 + 96;
int16_t py = (player_lx + player_ly) * 8 + 16;
// Centra il pixel px,py in mezzo allo schermo (-64 e -72)
scroll_x = px - 64;
scroll_y = py - 72;
// Comando hardware GBDK per scrollare il livello Background
move_bkg(scroll_x, scroll_y);
#ifdef USE_SPRITE_STAIRS
// Calcola e posiziona lo sprite delle scale
int16_t stairs_px = (stairs_lx - stairs_ly) * 16 + 96;
int16_t stairs_py = (stairs_lx + stairs_ly) * 8 + 16;
int16_t stairs_screen_x = ((stairs_px - scroll_x) & 255) + 8; // Offset per sprite
int16_t stairs_screen_y = ((stairs_py - scroll_y) & 255) + 16;
int8_t dx = (int8_t)stairs_lx - (int8_t)player_lx;
int8_t dy = (int8_t)stairs_ly - (int8_t)player_ly;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
uint8_t dist = (dx > dy) ? dx : dy;
// Se le scale sono visibili (dist <= 2), disegnale, altrimenti nascondile
if (dist <= 2 && stairs_screen_x >= -32 && stairs_screen_x <= 168 && stairs_screen_y >= -16 && stairs_screen_y <= 152) {
move_metasprite(stairs_sprite_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT + stamina_TILE_COUNT * 2, 12, stairs_screen_x, stairs_screen_y - 8);
} else {
move_metasprite(stairs_sprite_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT + stamina_TILE_COUNT * 2, 12, 0, 0);
}
#endif
}
/**
* Aggiorna il metasprite 16x16 del giocatore.
* Applica l'animazione di camminata o l'arco parabolico per l'altezza del salto.
*/
void update_player_sprite(void) {
// Animazione di camminata (alterna tra due frame a seconda del `move_progress`)
uint8_t frame_offset = is_moving ? ((move_progress >> 2) & 1) : 0;
uint8_t y_offset = 0;
if (is_jumping) {
// Matematica dell'Arco Parabolico per il salto.
// x * (16 - x) crea una curva che sale a 16 px di apice a metà altezza (8 frame).
y_offset = (move_progress * (16 - move_progress)) >> 2;
}
// move_metasprite è fornito da png2asset e posiziona multipli sprite hardware insieme
// Il giocatore è sempre fisso al centro dello schermo (88, 88). È il mondo (sfondo) a muoversi.
move_metasprite(player_metasprites[player_dir * 2 + frame_offset], 0, 0, 88, 88 - y_offset);
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef RENDER_H
#define RENDER_H
#include <stdint.h>
/**
* ==========================================
* RENDERING & CAMERA SUBSYSTEM
* ==========================================
* Handles updating visual elements on the screen, including the map background,
* stamina bar interface, and the player sprite.
*/
// Draw the isometric map centered around a specific logical tile
void draw_map(uint8_t center_x, uint8_t center_y);
// Update the background camera scroll position to center the player
void update_camera(void);
// Update stamina bar graphics
void update_stamina_display(void);
// Update the player sprite (animation frames, jumping height offset)
void update_player_sprite(void);
#endif
+308
View File
@@ -0,0 +1,308 @@
#include "sound.h"
#include "globals.h"
#include <gb/gb.h>
/**
* ==========================================
* GAME BOY AUDIO HARDWARE (APU)
* ==========================================
* Il Game Boy ha 4 canali audio. Noi usiamo:
* - Canale 1 (Onda quadra con sweep & envelope): Usato per gli arpeggi rapidi (es. Vittoria).
* - Canale 2 (Onda quadra con envelope): Usato per i bassi pulsanti o le melodie tenute.
* - Canale 4 (Rumore bianco): Usato per simulare le percussioni (crash o thud) manipolando le frequenze noise.
*/
// --- Variabili di stato interne per i timer musicali ---
static volatile uint8_t music_timer = 0;
static volatile uint8_t music_step = 0;
static volatile uint8_t gameover_music_timer = 0;
static volatile uint8_t gameover_music_step = 0;
static volatile uint8_t victory_music_timer = 0;
static volatile uint8_t victory_music_step = 0;
static volatile uint8_t title_music_timer = 0;
static volatile uint8_t title_music_step = 0;
// Note frequencies calculation: (2048 - (131072 / frequency))
#define R 0
#define N_E2 458
#define N_F2 547
#define N_G2 742
#define N_A2 857
#define N_AS2 907
#define N_B2 961
#define N_C3 1046
#define N_CS3 1103
#define N_D3 1155
#define N_E3 1253
#define N_F3 1298
#define N_FS3 1340
#define N_G3 1380
#define N_GS3 1417
#define N_A3 1453
#define N_AS3 1486
#define N_B3 1517
#define N_C4 1547
#define N_CS4 1575
#define N_D4 1602
#define N_E4 1651
#define N_F4 1673
#define N_FS4 1694
#define N_G4 1714
#define N_GS4 1732
#define N_A4 1751
#define N_AS4 1767
#define N_B4 1783
#define N_C5 1798
#define N_CS5 1812
#define N_D5 1825
#define N_E5 1849
#define N_F5 1860
#define N_FS5 1871
#define N_G5 1881
#define N_GS5 1890
#define N_A5 1899
// --- Sequences ---
const uint16_t ch1_seq[128] = {
// 1 Dm
N_D3, N_A3, N_D4, N_A3, N_D3, N_A3, N_D4, N_A3,
// 2 A/C#
N_CS3, N_A3, N_CS4, N_A3, N_CS3, N_A3, N_CS4, N_A3,
// 3 F/C
N_C3, N_A3, N_C4, N_A3, N_C3, N_A3, N_C4, N_A3,
// 4 G/B
N_B2, N_G3, N_B3, N_G3, N_B2, N_G3, N_B3, N_G3,
// 5 Bb
N_AS2, N_F3, N_AS3, N_F3, N_AS2, N_F3, N_AS3, N_F3,
// 6 F/A
N_A2, N_F3, N_A3, N_F3, N_A2, N_F3, N_A3, N_F3,
// 7 Gm
N_G2, N_D3, N_G3, N_D3, N_G2, N_D3, N_G3, N_D3,
// 8 A7
N_A2, N_E3, N_A3, N_CS4, N_A2, N_E3, N_A3, N_CS4,
// 9 Dm (intensifies: higher arpeggio)
N_D3, N_D4, N_F4, N_A4, N_D3, N_D4, N_F4, N_A4,
// 10 A/C#
N_CS3, N_CS4, N_E4, N_A4, N_CS3, N_CS4, N_E4, N_A4,
// 11 F/C
N_C3, N_C4, N_F4, N_A4, N_C3, N_C4, N_F4, N_A4,
// 12 G/B
N_B2, N_B3, N_D4, N_G4, N_B2, N_B3, N_D4, N_G4,
// 13 Bb
N_AS2, N_AS3, N_D4, N_F4, N_AS2, N_AS3, N_D4, N_F4,
// 14 F/A
N_A2, N_A3, N_C4, N_F4, N_A2, N_A3, N_C4, N_F4,
// 15 Gm
N_G2, N_G3, N_AS3, N_D4, N_G2, N_G3, N_AS3, N_D4,
// 16 A7 -> Dm resolution
N_A2, N_A3, N_CS4, N_E4, N_A2, R, N_D3, R
};
// Victory Arpeggio (fast ascending)
const uint16_t victory_seq[16] = {
N_C4, N_E4, N_G4, N_C5,
N_E5, N_G5, 1950, 1950,
N_C5, N_E5, N_G5, 1950,
1950, 1950, 1950, R
};
const uint16_t ch2_seq[128] = {
// 1 Dm
N_F4, R, R, R, N_E4, R, R, R,
// 2 A/C#
N_E4, R, R, R, N_D4, R, R, R,
// 3 F/C
N_C4, R, R, R, N_D4, R, N_E4, R,
// 4 G/B
N_D4, R, R, R, N_F4, R, R, R,
// 5 Bb
N_D4, R, R, R, N_C4, R, R, R,
// 6 F/A
N_C4, R, R, R, N_AS3, R, R, R,
// 7 Gm
N_AS3, R, N_A3, N_AS3, N_C4, R, N_AS3, R,
// 8 A7
N_A3, R, R, R, R, R, R, R,
// 9 Dm
N_A4, R, R, R, N_G4, R, N_F4, N_E4,
// 10 A/C#
N_G4, R, R, R, N_F4, R, N_E4, N_D4,
// 11 F/C
N_F4, R, R, R, N_E4, R, N_C4, R,
// 12 G/B
N_D4, R, R, R, N_G4, R, R, R,
// 13 Bb
N_F4, R, R, R, N_E4, R, N_D4, N_CS4,
// 14 F/A
N_E4, R, R, R, N_D4, R, N_C4, N_B3,
// 15 Gm
N_D4, R, R, R, N_C4, R, N_AS3, R,
// 16 A7 -> Dm resolution
N_A3, R, R, R, R, R, N_D4, R
};
// Eerie pulse note frequencies (96-note sequence for normal gameplay)
const uint16_t eerie_reg_vals[] = {
// Pattern 1 (A minor)
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
// Pattern 2 (D minor)
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
// Pattern 3 (E7 tension)
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497
};
const uint16_t title_melody[32] = {
N_A2, N_E3, N_A3, N_E3, N_C4, N_A3, N_E3, N_A2, // Am
N_G2, N_D3, N_G3, N_D3, N_B3, N_G3, N_D3, N_G2, // G
N_F2, N_C3, N_F3, N_C3, N_A3, N_F3, N_C3, N_F2, // F
N_E2, N_B2, N_E3, N_B2, N_GS3, N_E3, N_B2, N_E2 // E
};
void sound_reset_music_state(void) {
music_timer = 0;
music_step = 0;
gameover_music_timer = 0;
gameover_music_step = 0;
victory_music_timer = 0;
victory_music_step = 0;
title_music_timer = 0;
title_music_step = 0;
}
static void play_note(uint16_t reg_val) {
NR21_REG = 0x80; // 50% duty cycle
NR22_REG = 0x73; // volume 7, fade envelope
NR23_REG = reg_val & 0xFF;
NR24_REG = (reg_val >> 8) | 0x80; // trigger note
}
void play_gameover_step(uint8_t step) {
if (step < 128) {
uint16_t n1 = ch1_seq[step];
if (n1 != R) {
NR10_REG = 0x00; // No sweep
NR11_REG = 0x80; // 50% duty
NR12_REG = 0x73; // volume 7, fast fade for arpeggio pluck
NR13_REG = n1 & 0xFF;
NR14_REG = (n1 >> 8) | 0x80; // trigger note
}
uint16_t n2 = ch2_seq[step];
if (n2 != R) {
NR21_REG = 0x80; // 50% duty
NR22_REG = 0xA7; // volume 10, long fade for melody sustain
NR23_REG = n2 & 0xFF;
NR24_REG = (n2 >> 8) | 0x80;
}
// Add dramatic percussion on the first beat of each chord (every 8 steps) using Channel 4 (Noise)
if (step % 8 == 0) {
NR41_REG = 0x01;
NR42_REG = 0xB2; // Volume 11, quick fade
// step < 64 gets a lower tympani, step >= 64 gets a crash
if (step < 64) {
NR43_REG = 0x68; // Low thud freq
} else {
NR43_REG = 0x42; // Crash freq
}
NR44_REG = 0x80; // trigger noise
}
}
}
void play_victory_step(uint8_t step) {
if (step < 16) {
uint16_t n1 = victory_seq[step];
if (n1 != R) {
NR10_REG = 0x00;
NR11_REG = 0x80; // 50% duty
NR12_REG = 0xA7; // bright loud fade
NR13_REG = n1 & 0xFF;
NR14_REG = (n1 >> 8) | 0x80;
}
}
}
/**
* Questo interrupt scatta ogni VBL (Vertical Blanking), ovvero ~60 volte al secondo.
* Funziona come un mini "sequencer" audio: in base allo stato del gioco, controlla il timer
* e riproduce la nota corrispondente all'interno dell'array della traccia.
*/
void play_music_tick(void) {
if (app_state == 0) { // TITLE SCREEN
title_music_timer++;
if (title_music_timer >= 30) { // 30 frames per note
title_music_timer = 0;
uint16_t n = title_melody[title_music_step];
NR10_REG = 0;
NR11_REG = 0x80;
NR12_REG = 0x63; // soft fade
NR13_REG = n & 0xFF;
NR14_REG = (n >> 8) | 0x80;
// Add a slow bass note every 4 steps
if (title_music_step % 4 == 0) {
NR21_REG = 0x80;
NR22_REG = 0x77;
uint16_t bn = n / 2; // Octave lower (divide frequency logic)
NR23_REG = bn & 0xFF;
NR24_REG = (bn >> 8) | 0x80;
}
title_music_step++;
if (title_music_step >= 32) title_music_step = 0;
}
return;
}
if (game_over) { // ENDGAME
if (game_over_timer == 0) { // Se il ritardo di fine partita è terminato
if (game_over == 1) { // Defeat (Ghost)
gameover_music_timer++;
if (gameover_music_timer >= 10) { // 10 frames per note (~6 notes/sec)
gameover_music_timer = 0;
if (gameover_music_step < 128) {
play_gameover_step(gameover_music_step);
gameover_music_step++;
}
}
} else if (game_over == 2) { // Victory (Portal)
victory_music_timer++;
if (victory_music_timer >= 8) { // faster tempo
victory_music_timer = 0;
if (victory_music_step < 16) {
play_victory_step(victory_music_step);
victory_music_step++;
}
}
}
}
return;
}
// GAMEPLAY
music_timer++;
if (music_timer >= 20) {
music_timer = 0;
play_note(eerie_reg_vals[music_step]);
music_step++;
if (music_step >= 96) {
music_step = 0;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef SOUND_H
#define SOUND_H
#include <stdint.h>
/**
* ==========================================
* AUDIO SYNTHESIS & SEQUENCING
* ==========================================
* This module manages all sound generation via the Game Boy APU registers.
*/
// Resets internal music state variables
void sound_reset_music_state(void);
// Callbacks to be hooked into the Game Boy VBL (Vertical Blanking) interrupt
void play_music_tick(void);
// Call to manually play a note from the game over / victory sequences
void play_gameover_step(uint8_t step);
void play_victory_step(uint8_t step);
#endif
+36
View File
@@ -0,0 +1,36 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /stairs_sprite.png -c /stairs_sprite.c -sw 32 -sh 16 -bpp 2 -noflip -keep_palette_order -spr8x16
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(stairs_sprite)
const palette_color_t stairs_sprite_palettes[32] = {
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t stairs_sprite_tiles[64] = {
0x01,0x01,0x07,0x06,0x1f,0x18,0x07,0x06,0x01,0x01,0x06,0x06,0x18,0x18,0x06,0x06,
0x01,0x01,0x07,0x06,0x1f,0x18,0x07,0x06,0x01,0x01,0x06,0x06,0x18,0x18,0x06,0x06,
0x80,0x80,0xe0,0x60,0xf8,0x18,0xe0,0x60,0x80,0x80,0x60,0x60,0x18,0x18,0x60,0x60,
0x80,0x80,0xe0,0x60,0xf8,0x18,0xe0,0x60,0x80,0x80,0x60,0x60,0x18,0x18,0x60,0x60
};
const metasprite_t stairs_sprite_metasprite0[] = {
METASPR_ITEM(-8, -8, 0, S_PAL(0)),
METASPR_ITEM(0, 8, 2, S_PAL(0)),
METASPR_TERM
};
const metasprite_t* const stairs_sprite_metasprites[1] = {
stairs_sprite_metasprite0
};
+31
View File
@@ -0,0 +1,31 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /stairs_sprite.png -c /stairs_sprite.c -sw 32 -sh 16 -bpp 2 -noflip -keep_palette_order -spr8x16
#ifndef METASPRITE_stairs_sprite_H
#define METASPRITE_stairs_sprite_H
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
#define stairs_sprite_TILE_ORIGIN 0
#define stairs_sprite_TILE_W 8
#define stairs_sprite_TILE_H 16
#define stairs_sprite_WIDTH 32
#define stairs_sprite_HEIGHT 16
#define stairs_sprite_TILE_COUNT 4
#define stairs_sprite_PALETTE_COUNT 8
#define stairs_sprite_COLORS_PER_PALETTE 4
#define stairs_sprite_TOTAL_COLORS 32
#define stairs_sprite_PIVOT_X 16
#define stairs_sprite_PIVOT_Y 8
#define stairs_sprite_PIVOT_W 32
#define stairs_sprite_PIVOT_H 16
extern const metasprite_t* const stairs_sprite_metasprites[1];
BANKREF_EXTERN(stairs_sprite)
extern const palette_color_t stairs_sprite_palettes[32];
extern const uint8_t stairs_sprite_tiles[64];
#endif
+2 -2
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: stamina.png -c stamina.c -bpp 2 -noflip -keep_palette_order
// Conversion args: /stamina.png -c /stamina.c -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
@@ -8,7 +8,7 @@
BANKREF(stamina)
const palette_color_t stamina_palettes[32] = {
RGB8(255,255,255), RGB8(170,170,170), RGB8( 85, 85, 85), RGB8( 0, 0, 0),
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
+1 -1
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: stamina.png -c stamina.c -bpp 2 -noflip -keep_palette_order
// Conversion args: /stamina.png -c /stamina.c -bpp 2 -noflip -keep_palette_order
#ifndef METASPRITE_stamina_H
#define METASPRITE_stamina_H
+74
View File
@@ -0,0 +1,74 @@
#include <gb/gb.h>
#include <gbdk/font.h>
#include <stdint.h>
#include <string.h>
#include "tiles.h"
// Buffer di 32x32 tiles (mappa hardware del Game Boy)
uint8_t bkg_buffer[1024];
void main(void) {
// Inizializza il Game Boy
DISPLAY_OFF;
// Carica il tileset in VRAM
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles);
memset(bkg_buffer, 0, sizeof(bkg_buffer));
// Disegniamo una griglia 3x3
// La griglia e formata da blocchi 4x2 hardware tiles
// Il blocco centrale sara le scale (variante 64), il resto pavimenti normali (variante 16)
// Per avere maschere corrette (nessun bordo nero tra di loro):
// La griglia 3x3 sara:
// (0,0), (1,0), (2,0)
// (0,1), (1,1), (2,1)
// (0,2), (1,2), (2,2)
for (int8_t ly = 0; ly < 3; ly++) {
for (int8_t lx = 0; lx < 3; lx++) {
// Coordinate isometriche
int8_t iso_x = (lx - ly) * 2 + 12;
int8_t iso_y = (lx + ly) * 1 + 6; // Spostato giu per centrarlo
uint8_t has_tl = (lx > 0);
uint8_t has_tr = (ly > 0);
uint8_t has_bl = (ly < 2);
uint8_t has_br = (lx < 2);
uint8_t mask = (has_tl ? 1 : 0) | (has_tr ? 2 : 0) | (has_bl ? 4 : 0) | (has_br ? 8 : 0);
uint8_t is_alt = ((lx + ly) % 2 == 0);
uint8_t v;
if (lx == 1 && ly == 1) {
// Scale (centro)
v = 64 + mask;
} else {
// Pavimento normale
v = is_alt ? (16 + mask) : mask;
}
for (uint8_t y = 0; y < 2; y++) {
for (uint8_t x = 0; x < 4; x++) {
uint8_t target_x = (iso_x + x) & 31;
uint8_t target_y = (iso_y + y) & 31;
uint8_t src_tile = tiles_map[4 + v * 8 + y * 4 + x];
bkg_buffer[target_y * 32 + target_x] = src_tile;
}
}
}
}
set_bkg_tiles(0, 0, 32, 32, bkg_buffer);
SHOW_BKG;
DISPLAY_ON;
while(1) {
wait_vbl_done();
}
}
+898
View File
@@ -0,0 +1,898 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /tiles.png -c /tiles.c -map -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(tiles)
const palette_color_t tiles_palettes[32] = {
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t tiles_tiles[2416] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xc0,0xc3,
0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x03,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0xff,0xff,0x3f,0x3f,0x0f,0x0f,0x03,0x03,0x00,0x80,0x80,0x78,0xf8,0x07,0xff,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x7f,0x1f,0x1f,0x07,0x87,0x81,0x71,
0x81,0x8e,0xe0,0xe1,0xf8,0xf8,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x01,0xc0,0xc0,0xf0,0xf0,0xfc,0xfc,0xff,0xff,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc0,0x03,0x03,0x0f,0x0f,0x3f,0x3f,0xff,0xff,
0x03,0xc3,0x0f,0x0f,0x3f,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x01,0x01,0x06,0x07,0x38,0x3f,0xc0,0xff,0x01,0xfc,0x04,0xf0,0x10,0xc0,0x43,
0x3f,0xc0,0xfc,0x00,0xf0,0x00,0xc0,0x00,0x00,0x03,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc0,0xff,0x01,0xff,0x04,0xfc,0x10,0xf0,0x40,0xc3,
0xc0,0xff,0x00,0xfc,0x00,0xf0,0x00,0xc0,0x00,0x03,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0xf0,0x0e,0x3e,0x01,0x0f,0x00,0x03,0x00,0x00,0x80,0x80,0x78,0xf8,0x07,0xff,0x00,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0xfe,0x01,0x7f,0x00,0x1f,0x00,0x07,0x80,0x81,0x70,
0x0f,0xfe,0x01,0x3f,0x00,0x0f,0x00,0x03,0x00,0x80,0x80,0x78,0xf8,0x07,0xff,0x00,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x01,0xff,0x00,0x7f,0x00,0x1f,0x00,0x87,0x80,0x71,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xff,0xf0,0xfc,0xc3,
0xff,0xff,0xff,0xfc,0xff,0xf0,0xff,0xc0,0xfc,0x03,0xe0,0x1c,0x80,0x60,0x00,0x80,
0xff,0xff,0xff,0x3f,0xff,0x0f,0xff,0x03,0x7f,0x80,0x07,0x78,0x00,0x07,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xff,0x1f,0x7f,0x87,0x0f,0x71,
0xf0,0x8e,0xfe,0xe1,0xff,0xf8,0xff,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0xfe,0x01,0xff,0xc0,0xff,0xf0,0xff,0xfc,0xff,0xff,
0x00,0x01,0x01,0x06,0x07,0x38,0x3f,0xc0,0xff,0x03,0xff,0x0f,0xff,0x3f,0xff,0xff,
0x3f,0xc3,0xff,0x0f,0xff,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc0,0x01,0x01,0x07,0x04,0x1f,0x10,0x7c,0x43,
0x00,0xc0,0x03,0x00,0x0f,0x00,0x3f,0x00,0xfc,0x03,0xe0,0x1c,0x80,0x60,0x00,0x80,
0x01,0xff,0x07,0xfe,0x3f,0xf8,0xff,0xc0,0xff,0x01,0xff,0x04,0xff,0x10,0xfc,0x43,
0xff,0xc0,0xff,0x00,0xff,0x00,0xff,0x00,0xfc,0x03,0xe0,0x1c,0x80,0x60,0x00,0x80,
0x01,0x0e,0xc0,0x01,0xf0,0x00,0xfc,0x00,0x7f,0x80,0x07,0x78,0x00,0x07,0x00,0x00,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x01,0x80,0x00,0xe0,0x00,0x78,0x80,0x0e,0x70,
0xfe,0x0f,0xff,0x01,0xff,0x00,0xff,0x00,0x7f,0x80,0x07,0x78,0x00,0x07,0x00,0x00,
0x00,0xff,0xe0,0xff,0xfe,0x1f,0xff,0x01,0xff,0x00,0xff,0x00,0x7f,0x80,0x0f,0x70,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xff,0xf0,0xff,0xc3,0xff,
0xff,0xff,0xfc,0xff,0xf0,0xff,0xc0,0xff,0x03,0xff,0x1c,0xff,0x60,0xff,0x80,0xff,
0xff,0xff,0x3f,0xff,0x0f,0xff,0x03,0xff,0x80,0xff,0x78,0xff,0x07,0xff,0x00,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xff,0x1f,0xff,0x87,0xff,0x71,0xff,
0x8e,0xff,0xe1,0xff,0xf8,0xff,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0xff,0xe0,0xff,0x1e,0xff,0x01,0xff,0xc0,0xff,0xf0,0xff,0xfc,0xff,0xff,0xff,
0x01,0xff,0x06,0xff,0x38,0xff,0xc0,0xff,0x03,0xff,0x0f,0xff,0x3f,0xff,0xff,0xff,
0xc3,0xff,0x0f,0xff,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x01,0x01,0x06,0x07,0x38,0x3f,0xc0,0xff,0x01,0xfc,0x07,0xf0,0x1f,0xc3,0x7f,
0x3f,0xc0,0xfc,0x03,0xf0,0x0f,0xc0,0x3f,0x03,0xff,0x1c,0xff,0x60,0xff,0x80,0xff,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc0,0xff,0x01,0xff,0x04,0xff,0x10,0xff,0x43,0xff,
0xc0,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x03,0xff,0x1c,0xff,0x60,0xff,0x80,0xff,
0xf0,0x0e,0x3e,0xc1,0x0f,0xf0,0x03,0xfc,0x80,0xff,0x78,0xff,0x07,0xff,0x00,0xff,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0xfe,0x01,0x7f,0x80,0x1f,0xe0,0x87,0xf8,0x71,0xfe,
0x0f,0xfe,0x01,0xff,0x00,0xff,0x00,0xff,0x80,0xff,0x78,0xff,0x07,0xff,0x00,0xff,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x01,0xff,0x00,0xff,0x00,0xff,0x80,0xff,0x70,0xff,
0xff,0xff,0xfc,0xff,0xf0,0xff,0xc0,0xff,0x03,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0xff,0xff,0x3f,0xff,0x0f,0xff,0x03,0xff,0x80,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xff,0x1f,0xff,0x87,0xff,0xf1,0x7f,
0x8f,0xfe,0xe1,0xff,0xf8,0xff,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x01,0xff,0xc0,0xff,0xf0,0xff,0xfc,0xff,0xff,0xff,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc0,0xff,0x03,0xff,0x0f,0xff,0x3f,0xff,0xff,0xff,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc0,0x01,0x01,0x04,0x07,0x10,0x1f,0x43,0x7f,
0x00,0xc0,0x00,0x03,0x00,0x0f,0x00,0x3f,0x03,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0x01,0xff,0x07,0xfe,0x3f,0xf8,0xff,0xc0,0xff,0x01,0xfc,0x07,0xf0,0x1f,0xc3,0x7f,
0xff,0xc0,0xfc,0x03,0xf0,0x0f,0xc0,0x3f,0x03,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0x01,0x0e,0x00,0xc1,0x00,0xf0,0x00,0xfc,0x80,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x01,0x00,0x80,0x00,0xe0,0x80,0xf8,0xf0,0x7e,
0xfe,0x0f,0x3f,0xc1,0x0f,0xf0,0x03,0xfc,0x80,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0x00,0xff,0xe0,0xff,0xfe,0x1f,0xff,0x01,0x7f,0x80,0x1f,0xe0,0x87,0xf8,0xf1,0x7e,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,
0xff,0xff,0xff,0xfe,0xff,0xf8,0xc7,0xc6,0x01,0x01,0x06,0x06,0x18,0x18,0x06,0x06,
0xff,0xff,0xff,0x7f,0xff,0x1f,0xe3,0x63,0x80,0x80,0x60,0x60,0x18,0x18,0x60,0x60,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x7f,0x1f,0x1f,0x07,0x07,0x01,0x01,
0x80,0x80,0xe0,0xe0,0xf8,0xf8,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x01,0x01,0x07,0x06,0x1f,0x18,0x07,0x06,0xc1,0xc1,0xf6,0xf6,0xf8,0xf8,0xfe,0xfe,
0x80,0x80,0xe0,0x60,0xf8,0x18,0xe0,0x60,0x83,0x83,0x6f,0x6f,0x1f,0x1f,0x7f,0x7f,
0x03,0x03,0x0f,0x0f,0x3f,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x01,0x01,0x06,0x07,0x38,0x3f,0xc0,0xff,0x01,0xfc,0x04,0xf0,0x10,0xc0,0x40,
0x3f,0xc1,0xff,0x06,0xff,0x18,0xc7,0x06,0x01,0x01,0x06,0x06,0x18,0x18,0x06,0x06,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc0,0xff,0x01,0xff,0x04,0xfc,0x10,0xf0,0x40,0xc0,
0xc1,0xff,0x07,0xfe,0x1f,0xf8,0x07,0xc6,0x01,0x01,0x06,0x06,0x18,0x18,0x06,0x06,
0xf0,0x8e,0xfe,0x61,0xff,0x18,0xe3,0x60,0x80,0x80,0x60,0x60,0x18,0x18,0x60,0x60,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0xfe,0x01,0x7f,0x00,0x1f,0x00,0x07,0x00,0x01,0x00,
0x8f,0xfe,0xe1,0x7f,0xf8,0x1f,0xe0,0x63,0x80,0x80,0x60,0x60,0x18,0x18,0x60,0x60,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x01,0xff,0x00,0x7f,0x00,0x1f,0x00,0x07,0x00,0x01,
0x80,0x00,0xe0,0x00,0xf8,0x00,0xfe,0x00,0x7f,0x80,0x07,0x78,0x00,0x07,0x00,0x00,
0x01,0x01,0x07,0x06,0x1f,0x18,0x07,0x06,0xc1,0x01,0xf6,0x06,0x78,0x98,0x0e,0x76,
0x00,0x80,0x00,0xe0,0x00,0xf8,0x00,0xfe,0x80,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0x01,0x01,0x07,0x06,0x1f,0x18,0x07,0x06,0x01,0xc1,0x06,0xf6,0x98,0xf8,0xf6,0x7e,
0x80,0x80,0xe0,0x60,0xf8,0x18,0xe0,0x60,0x83,0x80,0x6f,0x60,0x1f,0x18,0x7c,0x63,
0x03,0x02,0x0f,0x08,0x3f,0x20,0xff,0x80,0xfc,0x03,0xe0,0x1c,0x80,0x60,0x00,0x80,
0x80,0x80,0xe0,0x60,0xf8,0x18,0xe0,0x60,0x80,0x83,0x60,0x6f,0x18,0x1f,0x63,0x7f,
0x02,0x03,0x08,0x0f,0x20,0x3f,0x80,0xff,0x03,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xff,0xf0,0xff,0xc0,
0xff,0xff,0xff,0xfe,0xff,0xf8,0xff,0xc6,0xff,0x01,0xfe,0x06,0xf8,0x18,0xfe,0x06,
0xff,0xff,0xff,0x7f,0xff,0x1f,0xff,0x63,0xff,0x80,0x7f,0x60,0x1f,0x18,0x7f,0x60,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xff,0x1f,0xff,0x07,0xff,0x01,
0xff,0x80,0xff,0xe0,0xff,0xf8,0xff,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0xc1,0xfe,0xf6,0xf8,0xf8,0xfe,0xfe,
0xff,0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x83,0x7f,0x6f,0x1f,0x1f,0x7f,0x7f,
0xff,0x03,0xff,0x0f,0xff,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc0,0x01,0x01,0x07,0x04,0x1f,0x10,0x7f,0x40,
0x01,0xc1,0x07,0x06,0x1f,0x18,0x3f,0x06,0xff,0x01,0xfe,0x06,0xf8,0x18,0xfe,0x06,
0x01,0xff,0x07,0xfe,0x3f,0xf8,0xff,0xc0,0xff,0x01,0xff,0x04,0xff,0x10,0xff,0x40,
0xff,0xc1,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0x01,0xfe,0x06,0xf8,0x18,0xfe,0x06,
0x81,0x8e,0xe0,0x61,0xf8,0x18,0xfc,0x60,0xff,0x80,0x7f,0x60,0x1f,0x18,0x7f,0x60,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x01,0x80,0x00,0xe0,0x00,0xf8,0x00,0xfe,0x00,
0xfe,0x8f,0xff,0x61,0xff,0x18,0xff,0x60,0xff,0x80,0x7f,0x60,0x1f,0x18,0x7f,0x60,
0x00,0xff,0xe0,0xff,0xfe,0x1f,0xff,0x01,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,
0x7f,0x00,0x1f,0x00,0x07,0x00,0x01,0x00,0x00,0x80,0x80,0x78,0xf8,0x07,0xff,0x00,
0xff,0x01,0xff,0x06,0xff,0x18,0xff,0x06,0x3f,0x01,0x0e,0x06,0x18,0x98,0x86,0x76,
0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x80,0x7f,0xf8,0x07,0xff,0x00,0xff,
0xff,0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0x01,0xfe,0x06,0xf8,0x98,0x7e,0xf6,
0xff,0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xfc,0x80,0x70,0x60,0x18,0x18,0x60,0x63,
0xfe,0x02,0xf8,0x08,0xe0,0x20,0x80,0x80,0x00,0x03,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0xff,0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x80,0x7f,0x60,0x1f,0x18,0x7f,0x63,
0xff,0x02,0xff,0x08,0xff,0x20,0xff,0x80,0xff,0x03,0xfc,0x1f,0xe0,0x7f,0x80,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xff,0xf0,0xff,0xc0,0xff,
0xff,0xff,0xfe,0xff,0xf8,0xff,0xc6,0xff,0x01,0xff,0x07,0xfe,0x1f,0xf8,0x07,0xfe,
0xff,0xff,0x7f,0xff,0x1f,0xff,0x63,0xff,0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xe0,0x7f,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xff,0x1f,0xff,0x07,0xff,0x01,0xff,
0x80,0xff,0xe0,0xff,0xf8,0xff,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0xc1,0xff,0xf7,0xfe,0xff,0xf8,0xff,0xfe,
0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x83,0xff,0xef,0x7f,0xff,0x1f,0xff,0x7f,
0x03,0xff,0x0f,0xff,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x01,0x01,0x06,0x07,0x38,0x3f,0xc0,0xff,0x01,0xfc,0x07,0xf0,0x1f,0xc0,0x7f,
0x3f,0xc1,0xfe,0x07,0xf8,0x1f,0xc6,0x3f,0x01,0xff,0x07,0xfe,0x1f,0xf8,0x07,0xfe,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc0,0xff,0x01,0xff,0x04,0xff,0x10,0xff,0x40,0xff,
0xc1,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0x01,0xff,0x07,0xfe,0x1f,0xf8,0x07,0xfe,
0xf0,0x8e,0x7e,0xe1,0x1f,0xf8,0x63,0xfc,0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xe0,0x7f,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0xfe,0x01,0x7f,0x80,0x1f,0xe0,0x07,0xf8,0x01,0xfe,
0x8f,0xfe,0x61,0xff,0x18,0xff,0x60,0xff,0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xe0,0x7f,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x01,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
0x80,0x7f,0xe0,0x1f,0xf8,0x07,0xfe,0x01,0x7f,0x80,0x07,0x78,0x00,0x07,0x00,0x00,
0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0xc1,0x3f,0xf7,0x0e,0x7f,0x98,0x0f,0x76,
0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x80,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0x01,0xff,0x07,0xfe,0x9f,0xf8,0xf7,0x7e,
0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x83,0xfc,0xef,0x70,0xff,0x18,0xfc,0x63,
0x03,0xfe,0x0f,0xf8,0x3f,0xe0,0xff,0x80,0xfc,0x03,0xe0,0x1c,0x80,0x60,0x00,0x80,
0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xe3,0x7f,
0x02,0xff,0x08,0xff,0x20,0xff,0x80,0xff,0x03,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc0,0x01,0x01,0x04,0x07,0x10,0x1f,0x40,0x7f,
0x01,0xc1,0x06,0x07,0x18,0x1f,0x06,0x3f,0x01,0xff,0x07,0xfe,0x1f,0xf8,0x07,0xfe,
0x01,0xff,0x07,0xfe,0x3f,0xf8,0xff,0xc0,0xff,0x01,0xfc,0x07,0xf0,0x1f,0xc0,0x7f,
0xff,0xc1,0xfe,0x07,0xf8,0x1f,0xc6,0x3f,0x01,0xff,0x07,0xfe,0x1f,0xf8,0x07,0xfe,
0x81,0x8e,0x60,0xe1,0x18,0xf8,0x60,0xfc,0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xe0,0x7f,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x01,0x00,0x80,0x00,0xe0,0x00,0xf8,0x00,0xfe,
0xfe,0x8f,0x7f,0xe1,0x1f,0xf8,0x63,0xfc,0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xe0,0x7f,
0x00,0xff,0xe0,0xff,0xfe,0x1f,0xff,0x01,0x7f,0x80,0x1f,0xe0,0x07,0xf8,0x01,0xfe,
0x00,0x7f,0x00,0x1f,0x00,0x07,0x00,0x01,0x00,0x80,0x80,0x78,0xf8,0x07,0xff,0x00,
0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0x01,0x3f,0x07,0x0e,0x1f,0x98,0x87,0x76,
0x80,0x7f,0xe0,0x1f,0xf8,0x07,0xfe,0x01,0xff,0x80,0x7f,0xf8,0x07,0xff,0x00,0xff,
0x01,0xff,0x06,0xff,0x18,0xff,0x06,0xff,0xc1,0x3f,0xf7,0x0e,0xff,0x98,0x7f,0xf6,
0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x80,0xfc,0xe0,0x70,0xf8,0x18,0xe0,0x63,
0x02,0xfe,0x08,0xf8,0x20,0xe0,0x80,0x80,0x00,0x03,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0x80,0xff,0x60,0xff,0x18,0xff,0x60,0xff,0x83,0xfc,0xef,0x70,0xff,0x18,0xff,0x63,
0x03,0xfe,0x0f,0xf8,0x3f,0xe0,0xff,0x80,0xff,0x03,0xfc,0x1f,0xe0,0x7f,0x80,0xff
};
const unsigned char tiles_map[2884] = {
0x00,0x00,0x00,0x00,
0x01,0x02,0x03,0x04,
0x05,0x06,0x07,0x08,
0x09,0x0a,0x03,0x04,
0x05,0x06,0x07,0x08,
0x0b,0x0c,0x03,0x04,
0x05,0x06,0x07,0x08,
0x01,0x02,0x0d,0x0e,
0x05,0x06,0x07,0x08,
0x09,0x0a,0x0d,0x0e,
0x05,0x06,0x07,0x08,
0x0b,0x0c,0x0d,0x0e,
0x05,0x06,0x07,0x08,
0x01,0x02,0x0f,0x10,
0x05,0x06,0x07,0x08,
0x09,0x0a,0x0f,0x10,
0x05,0x06,0x07,0x08,
0x0b,0x0c,0x0f,0x10,
0x05,0x06,0x07,0x08,
0x11,0x12,0x13,0x14,
0x15,0x16,0x17,0x18,
0x19,0x1a,0x13,0x14,
0x15,0x16,0x17,0x18,
0x1b,0x1c,0x13,0x14,
0x15,0x16,0x17,0x18,
0x11,0x12,0x1d,0x1e,
0x15,0x16,0x17,0x18,
0x19,0x1a,0x1d,0x1e,
0x15,0x16,0x17,0x18,
0x1b,0x1c,0x1d,0x1e,
0x15,0x16,0x17,0x18,
0x11,0x12,0x1f,0x20,
0x15,0x16,0x17,0x18,
0x19,0x1a,0x1f,0x20,
0x15,0x16,0x17,0x18,
0x1b,0x1c,0x1f,0x20,
0x15,0x16,0x17,0x18,
0x21,0x22,0x23,0x24,
0x25,0x26,0x27,0x28,
0x29,0x2a,0x23,0x24,
0x25,0x26,0x27,0x28,
0x2b,0x2c,0x23,0x24,
0x25,0x26,0x27,0x28,
0x21,0x22,0x2d,0x2e,
0x25,0x26,0x27,0x28,
0x29,0x2a,0x2d,0x2e,
0x25,0x26,0x27,0x28,
0x2b,0x2c,0x2d,0x2e,
0x25,0x26,0x27,0x28,
0x21,0x22,0x2f,0x30,
0x25,0x26,0x27,0x28,
0x29,0x2a,0x2f,0x30,
0x25,0x26,0x27,0x28,
0x2b,0x2c,0x2f,0x30,
0x25,0x26,0x27,0x28,
0x21,0x31,0x32,0x33,
0x34,0x35,0x36,0x28,
0x37,0x38,0x32,0x33,
0x34,0x35,0x36,0x28,
0x39,0x3a,0x32,0x33,
0x34,0x35,0x36,0x28,
0x21,0x31,0x3b,0x3c,
0x34,0x35,0x36,0x28,
0x37,0x38,0x3b,0x3c,
0x34,0x35,0x36,0x28,
0x39,0x3a,0x3b,0x3c,
0x34,0x35,0x36,0x28,
0x21,0x31,0x3d,0x3e,
0x34,0x35,0x36,0x28,
0x37,0x38,0x3d,0x3e,
0x34,0x35,0x36,0x28,
0x39,0x3a,0x3d,0x3e,
0x34,0x35,0x36,0x28,
0x3f,0x40,0x41,0x42,
0x43,0x44,0x45,0x46,
0x47,0x48,0x41,0x42,
0x43,0x44,0x45,0x46,
0x49,0x4a,0x41,0x42,
0x43,0x44,0x45,0x46,
0x3f,0x40,0x4b,0x4c,
0x43,0x44,0x45,0x46,
0x47,0x48,0x4b,0x4c,
0x43,0x44,0x45,0x46,
0x49,0x4a,0x4b,0x4c,
0x43,0x44,0x45,0x46,
0x3f,0x40,0x4d,0x4e,
0x43,0x44,0x45,0x46,
0x47,0x48,0x4d,0x4e,
0x43,0x44,0x45,0x46,
0x49,0x4a,0x4d,0x4e,
0x43,0x44,0x45,0x46,
0x3f,0x40,0x41,0x42,
0x4f,0x50,0x45,0x46,
0x47,0x48,0x41,0x42,
0x4f,0x50,0x45,0x46,
0x49,0x4a,0x41,0x42,
0x4f,0x50,0x45,0x46,
0x3f,0x40,0x4b,0x4c,
0x4f,0x50,0x45,0x46,
0x47,0x48,0x4b,0x4c,
0x4f,0x50,0x45,0x46,
0x49,0x4a,0x4b,0x4c,
0x4f,0x50,0x45,0x46,
0x3f,0x40,0x4d,0x4e,
0x4f,0x50,0x45,0x46,
0x47,0x48,0x4d,0x4e,
0x4f,0x50,0x45,0x46,
0x49,0x4a,0x4d,0x4e,
0x4f,0x50,0x45,0x46,
0x3f,0x40,0x41,0x42,
0x51,0x52,0x45,0x46,
0x47,0x48,0x41,0x42,
0x51,0x52,0x45,0x46,
0x49,0x4a,0x41,0x42,
0x51,0x52,0x45,0x46,
0x3f,0x40,0x4b,0x4c,
0x51,0x52,0x45,0x46,
0x47,0x48,0x4b,0x4c,
0x51,0x52,0x45,0x46,
0x49,0x4a,0x4b,0x4c,
0x51,0x52,0x45,0x46,
0x3f,0x40,0x4d,0x4e,
0x51,0x52,0x45,0x46,
0x47,0x48,0x4d,0x4e,
0x51,0x52,0x45,0x46,
0x49,0x4a,0x4d,0x4e,
0x51,0x52,0x45,0x46,
0x3f,0x40,0x41,0x42,
0x43,0x44,0x53,0x54,
0x47,0x48,0x41,0x42,
0x43,0x44,0x53,0x54,
0x49,0x4a,0x41,0x42,
0x43,0x44,0x53,0x54,
0x3f,0x40,0x4b,0x4c,
0x43,0x44,0x53,0x54,
0x47,0x48,0x4b,0x4c,
0x43,0x44,0x53,0x54,
0x49,0x4a,0x4b,0x4c,
0x43,0x44,0x53,0x54,
0x3f,0x40,0x4d,0x4e,
0x43,0x44,0x53,0x54,
0x47,0x48,0x4d,0x4e,
0x43,0x44,0x53,0x54,
0x49,0x4a,0x4d,0x4e,
0x43,0x44,0x53,0x54,
0x3f,0x40,0x41,0x42,
0x4f,0x50,0x53,0x54,
0x47,0x48,0x41,0x42,
0x4f,0x50,0x53,0x54,
0x49,0x4a,0x41,0x42,
0x4f,0x50,0x53,0x54,
0x3f,0x40,0x4b,0x4c,
0x4f,0x50,0x53,0x54,
0x47,0x48,0x4b,0x4c,
0x4f,0x50,0x53,0x54,
0x49,0x4a,0x4b,0x4c,
0x4f,0x50,0x53,0x54,
0x3f,0x40,0x4d,0x4e,
0x4f,0x50,0x53,0x54,
0x47,0x48,0x4d,0x4e,
0x4f,0x50,0x53,0x54,
0x49,0x4a,0x4d,0x4e,
0x4f,0x50,0x53,0x54,
0x3f,0x40,0x41,0x42,
0x51,0x52,0x53,0x54,
0x47,0x48,0x41,0x42,
0x51,0x52,0x53,0x54,
0x49,0x4a,0x41,0x42,
0x51,0x52,0x53,0x54,
0x3f,0x40,0x4b,0x4c,
0x51,0x52,0x53,0x54,
0x47,0x48,0x4b,0x4c,
0x51,0x52,0x53,0x54,
0x49,0x4a,0x4b,0x4c,
0x51,0x52,0x53,0x54,
0x3f,0x40,0x4d,0x4e,
0x51,0x52,0x53,0x54,
0x47,0x48,0x4d,0x4e,
0x51,0x52,0x53,0x54,
0x49,0x4a,0x4d,0x4e,
0x51,0x52,0x53,0x54,
0x3f,0x40,0x41,0x42,
0x43,0x44,0x55,0x56,
0x47,0x48,0x41,0x42,
0x43,0x44,0x55,0x56,
0x49,0x4a,0x41,0x42,
0x43,0x44,0x55,0x56,
0x3f,0x40,0x4b,0x4c,
0x43,0x44,0x55,0x56,
0x47,0x48,0x4b,0x4c,
0x43,0x44,0x55,0x56,
0x49,0x4a,0x4b,0x4c,
0x43,0x44,0x55,0x56,
0x3f,0x40,0x4d,0x4e,
0x43,0x44,0x55,0x56,
0x47,0x48,0x4d,0x4e,
0x43,0x44,0x55,0x56,
0x49,0x4a,0x4d,0x4e,
0x43,0x44,0x55,0x56,
0x3f,0x40,0x41,0x42,
0x4f,0x50,0x55,0x56,
0x47,0x48,0x41,0x42,
0x4f,0x50,0x55,0x56,
0x49,0x4a,0x41,0x42,
0x4f,0x50,0x55,0x56,
0x3f,0x40,0x4b,0x4c,
0x4f,0x50,0x55,0x56,
0x47,0x48,0x4b,0x4c,
0x4f,0x50,0x55,0x56,
0x49,0x4a,0x4b,0x4c,
0x4f,0x50,0x55,0x56,
0x3f,0x40,0x4d,0x4e,
0x4f,0x50,0x55,0x56,
0x47,0x48,0x4d,0x4e,
0x4f,0x50,0x55,0x56,
0x49,0x4a,0x4d,0x4e,
0x4f,0x50,0x55,0x56,
0x3f,0x40,0x41,0x42,
0x51,0x52,0x55,0x56,
0x47,0x48,0x41,0x42,
0x51,0x52,0x55,0x56,
0x49,0x4a,0x41,0x42,
0x51,0x52,0x55,0x56,
0x3f,0x40,0x4b,0x4c,
0x51,0x52,0x55,0x56,
0x47,0x48,0x4b,0x4c,
0x51,0x52,0x55,0x56,
0x49,0x4a,0x4b,0x4c,
0x51,0x52,0x55,0x56,
0x3f,0x40,0x4d,0x4e,
0x51,0x52,0x55,0x56,
0x47,0x48,0x4d,0x4e,
0x51,0x52,0x55,0x56,
0x49,0x4a,0x4d,0x4e,
0x51,0x52,0x55,0x56,
0x57,0x58,0x59,0x5a,
0x5b,0x5c,0x5d,0x5e,
0x5f,0x60,0x59,0x5a,
0x5b,0x5c,0x5d,0x5e,
0x61,0x62,0x59,0x5a,
0x5b,0x5c,0x5d,0x5e,
0x57,0x58,0x63,0x64,
0x5b,0x5c,0x5d,0x5e,
0x5f,0x60,0x63,0x64,
0x5b,0x5c,0x5d,0x5e,
0x61,0x62,0x63,0x64,
0x5b,0x5c,0x5d,0x5e,
0x57,0x58,0x65,0x66,
0x5b,0x5c,0x5d,0x5e,
0x5f,0x60,0x65,0x66,
0x5b,0x5c,0x5d,0x5e,
0x61,0x62,0x65,0x66,
0x5b,0x5c,0x5d,0x5e,
0x57,0x58,0x59,0x5a,
0x67,0x68,0x5d,0x5e,
0x5f,0x60,0x59,0x5a,
0x67,0x68,0x5d,0x5e,
0x61,0x62,0x59,0x5a,
0x67,0x68,0x5d,0x5e,
0x57,0x58,0x63,0x64,
0x67,0x68,0x5d,0x5e,
0x5f,0x60,0x63,0x64,
0x67,0x68,0x5d,0x5e,
0x61,0x62,0x63,0x64,
0x67,0x68,0x5d,0x5e,
0x57,0x58,0x65,0x66,
0x67,0x68,0x5d,0x5e,
0x5f,0x60,0x65,0x66,
0x67,0x68,0x5d,0x5e,
0x61,0x62,0x65,0x66,
0x67,0x68,0x5d,0x5e,
0x57,0x58,0x59,0x5a,
0x69,0x6a,0x5d,0x5e,
0x5f,0x60,0x59,0x5a,
0x69,0x6a,0x5d,0x5e,
0x61,0x62,0x59,0x5a,
0x69,0x6a,0x5d,0x5e,
0x57,0x58,0x63,0x64,
0x69,0x6a,0x5d,0x5e,
0x5f,0x60,0x63,0x64,
0x69,0x6a,0x5d,0x5e,
0x61,0x62,0x63,0x64,
0x69,0x6a,0x5d,0x5e,
0x57,0x58,0x65,0x66,
0x69,0x6a,0x5d,0x5e,
0x5f,0x60,0x65,0x66,
0x69,0x6a,0x5d,0x5e,
0x61,0x62,0x65,0x66,
0x69,0x6a,0x5d,0x5e,
0x57,0x58,0x59,0x5a,
0x5b,0x5c,0x6b,0x6c,
0x5f,0x60,0x59,0x5a,
0x5b,0x5c,0x6b,0x6c,
0x61,0x62,0x59,0x5a,
0x5b,0x5c,0x6b,0x6c,
0x57,0x58,0x63,0x64,
0x5b,0x5c,0x6b,0x6c,
0x5f,0x60,0x63,0x64,
0x5b,0x5c,0x6b,0x6c,
0x61,0x62,0x63,0x64,
0x5b,0x5c,0x6b,0x6c,
0x57,0x58,0x65,0x66,
0x5b,0x5c,0x6b,0x6c,
0x5f,0x60,0x65,0x66,
0x5b,0x5c,0x6b,0x6c,
0x61,0x62,0x65,0x66,
0x5b,0x5c,0x6b,0x6c,
0x57,0x58,0x59,0x5a,
0x67,0x68,0x6b,0x6c,
0x5f,0x60,0x59,0x5a,
0x67,0x68,0x6b,0x6c,
0x61,0x62,0x59,0x5a,
0x67,0x68,0x6b,0x6c,
0x57,0x58,0x63,0x64,
0x67,0x68,0x6b,0x6c,
0x5f,0x60,0x63,0x64,
0x67,0x68,0x6b,0x6c,
0x61,0x62,0x63,0x64,
0x67,0x68,0x6b,0x6c,
0x57,0x58,0x65,0x66,
0x67,0x68,0x6b,0x6c,
0x5f,0x60,0x65,0x66,
0x67,0x68,0x6b,0x6c,
0x61,0x62,0x65,0x66,
0x67,0x68,0x6b,0x6c,
0x57,0x58,0x59,0x5a,
0x69,0x6a,0x6b,0x6c,
0x5f,0x60,0x59,0x5a,
0x69,0x6a,0x6b,0x6c,
0x61,0x62,0x59,0x5a,
0x69,0x6a,0x6b,0x6c,
0x57,0x58,0x63,0x64,
0x69,0x6a,0x6b,0x6c,
0x5f,0x60,0x63,0x64,
0x69,0x6a,0x6b,0x6c,
0x61,0x62,0x63,0x64,
0x69,0x6a,0x6b,0x6c,
0x57,0x58,0x65,0x66,
0x69,0x6a,0x6b,0x6c,
0x5f,0x60,0x65,0x66,
0x69,0x6a,0x6b,0x6c,
0x61,0x62,0x65,0x66,
0x69,0x6a,0x6b,0x6c,
0x57,0x58,0x59,0x5a,
0x5b,0x5c,0x6d,0x6e,
0x5f,0x60,0x59,0x5a,
0x5b,0x5c,0x6d,0x6e,
0x61,0x62,0x59,0x5a,
0x5b,0x5c,0x6d,0x6e,
0x57,0x58,0x63,0x64,
0x5b,0x5c,0x6d,0x6e,
0x5f,0x60,0x63,0x64,
0x5b,0x5c,0x6d,0x6e,
0x61,0x62,0x63,0x64,
0x5b,0x5c,0x6d,0x6e,
0x57,0x58,0x65,0x66,
0x5b,0x5c,0x6d,0x6e,
0x5f,0x60,0x65,0x66,
0x5b,0x5c,0x6d,0x6e,
0x61,0x62,0x65,0x66,
0x5b,0x5c,0x6d,0x6e,
0x57,0x58,0x59,0x5a,
0x67,0x68,0x6d,0x6e,
0x5f,0x60,0x59,0x5a,
0x67,0x68,0x6d,0x6e,
0x61,0x62,0x59,0x5a,
0x67,0x68,0x6d,0x6e,
0x57,0x58,0x63,0x64,
0x67,0x68,0x6d,0x6e,
0x5f,0x60,0x63,0x64,
0x67,0x68,0x6d,0x6e,
0x61,0x62,0x63,0x64,
0x67,0x68,0x6d,0x6e,
0x57,0x58,0x65,0x66,
0x67,0x68,0x6d,0x6e,
0x5f,0x60,0x65,0x66,
0x67,0x68,0x6d,0x6e,
0x61,0x62,0x65,0x66,
0x67,0x68,0x6d,0x6e,
0x57,0x58,0x59,0x5a,
0x69,0x6a,0x6d,0x6e,
0x5f,0x60,0x59,0x5a,
0x69,0x6a,0x6d,0x6e,
0x61,0x62,0x59,0x5a,
0x69,0x6a,0x6d,0x6e,
0x57,0x58,0x63,0x64,
0x69,0x6a,0x6d,0x6e,
0x5f,0x60,0x63,0x64,
0x69,0x6a,0x6d,0x6e,
0x61,0x62,0x63,0x64,
0x69,0x6a,0x6d,0x6e,
0x57,0x58,0x65,0x66,
0x69,0x6a,0x6d,0x6e,
0x5f,0x60,0x65,0x66,
0x69,0x6a,0x6d,0x6e,
0x61,0x62,0x65,0x66,
0x69,0x6a,0x6d,0x6e,
0x6f,0x70,0x71,0x72,
0x73,0x74,0x75,0x76,
0x77,0x78,0x71,0x72,
0x73,0x74,0x75,0x76,
0x79,0x7a,0x71,0x72,
0x73,0x74,0x75,0x76,
0x6f,0x70,0x7b,0x7c,
0x73,0x74,0x75,0x76,
0x77,0x78,0x7b,0x7c,
0x73,0x74,0x75,0x76,
0x79,0x7a,0x7b,0x7c,
0x73,0x74,0x75,0x76,
0x6f,0x70,0x7d,0x7e,
0x73,0x74,0x75,0x76,
0x77,0x78,0x7d,0x7e,
0x73,0x74,0x75,0x76,
0x79,0x7a,0x7d,0x7e,
0x73,0x74,0x75,0x76,
0x6f,0x70,0x71,0x72,
0x7f,0x80,0x75,0x76,
0x77,0x78,0x71,0x72,
0x7f,0x80,0x75,0x76,
0x79,0x7a,0x71,0x72,
0x7f,0x80,0x75,0x76,
0x6f,0x70,0x7b,0x7c,
0x7f,0x80,0x75,0x76,
0x77,0x78,0x7b,0x7c,
0x7f,0x80,0x75,0x76,
0x79,0x7a,0x7b,0x7c,
0x7f,0x80,0x75,0x76,
0x6f,0x70,0x7d,0x7e,
0x7f,0x80,0x75,0x76,
0x77,0x78,0x7d,0x7e,
0x7f,0x80,0x75,0x76,
0x79,0x7a,0x7d,0x7e,
0x7f,0x80,0x75,0x76,
0x6f,0x70,0x71,0x72,
0x81,0x82,0x75,0x76,
0x77,0x78,0x71,0x72,
0x81,0x82,0x75,0x76,
0x79,0x7a,0x71,0x72,
0x81,0x82,0x75,0x76,
0x6f,0x70,0x7b,0x7c,
0x81,0x82,0x75,0x76,
0x77,0x78,0x7b,0x7c,
0x81,0x82,0x75,0x76,
0x79,0x7a,0x7b,0x7c,
0x81,0x82,0x75,0x76,
0x6f,0x70,0x7d,0x7e,
0x81,0x82,0x75,0x76,
0x77,0x78,0x7d,0x7e,
0x81,0x82,0x75,0x76,
0x79,0x7a,0x7d,0x7e,
0x81,0x82,0x75,0x76,
0x6f,0x70,0x71,0x72,
0x73,0x74,0x83,0x84,
0x77,0x78,0x71,0x72,
0x73,0x74,0x83,0x84,
0x79,0x7a,0x71,0x72,
0x73,0x74,0x83,0x84,
0x6f,0x70,0x7b,0x7c,
0x73,0x74,0x83,0x84,
0x77,0x78,0x7b,0x7c,
0x73,0x74,0x83,0x84,
0x79,0x7a,0x7b,0x7c,
0x73,0x74,0x83,0x84,
0x6f,0x70,0x7d,0x7e,
0x73,0x74,0x83,0x84,
0x77,0x78,0x7d,0x7e,
0x73,0x74,0x83,0x84,
0x79,0x7a,0x7d,0x7e,
0x73,0x74,0x83,0x84,
0x6f,0x70,0x71,0x72,
0x7f,0x80,0x83,0x84,
0x77,0x78,0x71,0x72,
0x7f,0x80,0x83,0x84,
0x79,0x7a,0x71,0x72,
0x7f,0x80,0x83,0x84,
0x6f,0x70,0x7b,0x7c,
0x7f,0x80,0x83,0x84,
0x77,0x78,0x7b,0x7c,
0x7f,0x80,0x83,0x84,
0x79,0x7a,0x7b,0x7c,
0x7f,0x80,0x83,0x84,
0x6f,0x70,0x7d,0x7e,
0x7f,0x80,0x83,0x84,
0x77,0x78,0x7d,0x7e,
0x7f,0x80,0x83,0x84,
0x79,0x7a,0x7d,0x7e,
0x7f,0x80,0x83,0x84,
0x6f,0x70,0x71,0x72,
0x81,0x82,0x83,0x84,
0x77,0x78,0x71,0x72,
0x81,0x82,0x83,0x84,
0x79,0x7a,0x71,0x72,
0x81,0x82,0x83,0x84,
0x6f,0x70,0x7b,0x7c,
0x81,0x82,0x83,0x84,
0x77,0x78,0x7b,0x7c,
0x81,0x82,0x83,0x84,
0x79,0x7a,0x7b,0x7c,
0x81,0x82,0x83,0x84,
0x6f,0x70,0x7d,0x7e,
0x81,0x82,0x83,0x84,
0x77,0x78,0x7d,0x7e,
0x81,0x82,0x83,0x84,
0x79,0x7a,0x7d,0x7e,
0x81,0x82,0x83,0x84,
0x6f,0x70,0x71,0x72,
0x73,0x74,0x85,0x86,
0x77,0x78,0x71,0x72,
0x73,0x74,0x85,0x86,
0x79,0x7a,0x71,0x72,
0x73,0x74,0x85,0x86,
0x6f,0x70,0x7b,0x7c,
0x73,0x74,0x85,0x86,
0x77,0x78,0x7b,0x7c,
0x73,0x74,0x85,0x86,
0x79,0x7a,0x7b,0x7c,
0x73,0x74,0x85,0x86,
0x6f,0x70,0x7d,0x7e,
0x73,0x74,0x85,0x86,
0x77,0x78,0x7d,0x7e,
0x73,0x74,0x85,0x86,
0x79,0x7a,0x7d,0x7e,
0x73,0x74,0x85,0x86,
0x6f,0x70,0x71,0x72,
0x7f,0x80,0x85,0x86,
0x77,0x78,0x71,0x72,
0x7f,0x80,0x85,0x86,
0x79,0x7a,0x71,0x72,
0x7f,0x80,0x85,0x86,
0x6f,0x70,0x7b,0x7c,
0x7f,0x80,0x85,0x86,
0x77,0x78,0x7b,0x7c,
0x7f,0x80,0x85,0x86,
0x79,0x7a,0x7b,0x7c,
0x7f,0x80,0x85,0x86,
0x6f,0x70,0x7d,0x7e,
0x7f,0x80,0x85,0x86,
0x77,0x78,0x7d,0x7e,
0x7f,0x80,0x85,0x86,
0x79,0x7a,0x7d,0x7e,
0x7f,0x80,0x85,0x86,
0x6f,0x70,0x71,0x72,
0x81,0x82,0x85,0x86,
0x77,0x78,0x71,0x72,
0x81,0x82,0x85,0x86,
0x79,0x7a,0x71,0x72,
0x81,0x82,0x85,0x86,
0x6f,0x70,0x7b,0x7c,
0x81,0x82,0x85,0x86,
0x77,0x78,0x7b,0x7c,
0x81,0x82,0x85,0x86,
0x79,0x7a,0x7b,0x7c,
0x81,0x82,0x85,0x86,
0x6f,0x70,0x7d,0x7e,
0x81,0x82,0x85,0x86,
0x77,0x78,0x7d,0x7e,
0x81,0x82,0x85,0x86,
0x79,0x7a,0x7d,0x7e,
0x81,0x82,0x85,0x86,
0x6f,0x70,0x71,0x72,
0x73,0x74,0x75,0x76,
0x87,0x88,0x71,0x72,
0x73,0x74,0x75,0x76,
0x89,0x8a,0x71,0x72,
0x73,0x74,0x75,0x76,
0x6f,0x70,0x8b,0x8c,
0x73,0x74,0x75,0x76,
0x87,0x88,0x8b,0x8c,
0x73,0x74,0x75,0x76,
0x89,0x8a,0x8b,0x8c,
0x73,0x74,0x75,0x76,
0x6f,0x70,0x8d,0x8e,
0x73,0x74,0x75,0x76,
0x87,0x88,0x8d,0x8e,
0x73,0x74,0x75,0x76,
0x89,0x8a,0x8d,0x8e,
0x73,0x74,0x75,0x76,
0x6f,0x70,0x71,0x72,
0x8f,0x90,0x75,0x76,
0x87,0x88,0x71,0x72,
0x8f,0x90,0x75,0x76,
0x89,0x8a,0x71,0x72,
0x8f,0x90,0x75,0x76,
0x6f,0x70,0x8b,0x8c,
0x8f,0x90,0x75,0x76,
0x87,0x88,0x8b,0x8c,
0x8f,0x90,0x75,0x76,
0x89,0x8a,0x8b,0x8c,
0x8f,0x90,0x75,0x76,
0x6f,0x70,0x8d,0x8e,
0x8f,0x90,0x75,0x76,
0x87,0x88,0x8d,0x8e,
0x8f,0x90,0x75,0x76,
0x89,0x8a,0x8d,0x8e,
0x8f,0x90,0x75,0x76,
0x6f,0x70,0x71,0x72,
0x91,0x92,0x75,0x76,
0x87,0x88,0x71,0x72,
0x91,0x92,0x75,0x76,
0x89,0x8a,0x71,0x72,
0x91,0x92,0x75,0x76,
0x6f,0x70,0x8b,0x8c,
0x91,0x92,0x75,0x76,
0x87,0x88,0x8b,0x8c,
0x91,0x92,0x75,0x76,
0x89,0x8a,0x8b,0x8c,
0x91,0x92,0x75,0x76,
0x6f,0x70,0x8d,0x8e,
0x91,0x92,0x75,0x76,
0x87,0x88,0x8d,0x8e,
0x91,0x92,0x75,0x76,
0x89,0x8a,0x8d,0x8e,
0x91,0x92,0x75,0x76,
0x6f,0x70,0x71,0x72,
0x73,0x74,0x93,0x94,
0x87,0x88,0x71,0x72,
0x73,0x74,0x93,0x94,
0x89,0x8a,0x71,0x72,
0x73,0x74,0x93,0x94,
0x6f,0x70,0x8b,0x8c,
0x73,0x74,0x93,0x94,
0x87,0x88,0x8b,0x8c,
0x73,0x74,0x93,0x94,
0x89,0x8a,0x8b,0x8c,
0x73,0x74,0x93,0x94,
0x6f,0x70,0x8d,0x8e,
0x73,0x74,0x93,0x94,
0x87,0x88,0x8d,0x8e,
0x73,0x74,0x93,0x94,
0x89,0x8a,0x8d,0x8e,
0x73,0x74,0x93,0x94,
0x6f,0x70,0x71,0x72,
0x8f,0x90,0x93,0x94,
0x87,0x88,0x71,0x72,
0x8f,0x90,0x93,0x94,
0x89,0x8a,0x71,0x72,
0x8f,0x90,0x93,0x94,
0x6f,0x70,0x8b,0x8c,
0x8f,0x90,0x93,0x94,
0x87,0x88,0x8b,0x8c,
0x8f,0x90,0x93,0x94,
0x89,0x8a,0x8b,0x8c,
0x8f,0x90,0x93,0x94,
0x6f,0x70,0x8d,0x8e,
0x8f,0x90,0x93,0x94,
0x87,0x88,0x8d,0x8e,
0x8f,0x90,0x93,0x94,
0x89,0x8a,0x8d,0x8e,
0x8f,0x90,0x93,0x94,
0x6f,0x70,0x71,0x72,
0x91,0x92,0x93,0x94,
0x87,0x88,0x71,0x72,
0x91,0x92,0x93,0x94,
0x89,0x8a,0x71,0x72,
0x91,0x92,0x93,0x94,
0x6f,0x70,0x8b,0x8c,
0x91,0x92,0x93,0x94,
0x87,0x88,0x8b,0x8c,
0x91,0x92,0x93,0x94,
0x89,0x8a,0x8b,0x8c,
0x91,0x92,0x93,0x94,
0x6f,0x70,0x8d,0x8e,
0x91,0x92,0x93,0x94,
0x87,0x88,0x8d,0x8e,
0x91,0x92,0x93,0x94,
0x89,0x8a,0x8d,0x8e,
0x91,0x92,0x93,0x94,
0x6f,0x70,0x71,0x72,
0x73,0x74,0x95,0x96,
0x87,0x88,0x71,0x72,
0x73,0x74,0x95,0x96,
0x89,0x8a,0x71,0x72,
0x73,0x74,0x95,0x96,
0x6f,0x70,0x8b,0x8c,
0x73,0x74,0x95,0x96,
0x87,0x88,0x8b,0x8c,
0x73,0x74,0x95,0x96,
0x89,0x8a,0x8b,0x8c,
0x73,0x74,0x95,0x96,
0x6f,0x70,0x8d,0x8e,
0x73,0x74,0x95,0x96,
0x87,0x88,0x8d,0x8e,
0x73,0x74,0x95,0x96,
0x89,0x8a,0x8d,0x8e,
0x73,0x74,0x95,0x96,
0x6f,0x70,0x71,0x72,
0x8f,0x90,0x95,0x96,
0x87,0x88,0x71,0x72,
0x8f,0x90,0x95,0x96,
0x89,0x8a,0x71,0x72,
0x8f,0x90,0x95,0x96,
0x6f,0x70,0x8b,0x8c,
0x8f,0x90,0x95,0x96,
0x87,0x88,0x8b,0x8c,
0x8f,0x90,0x95,0x96,
0x89,0x8a,0x8b,0x8c,
0x8f,0x90,0x95,0x96,
0x6f,0x70,0x8d,0x8e,
0x8f,0x90,0x95,0x96,
0x87,0x88,0x8d,0x8e,
0x8f,0x90,0x95,0x96,
0x89,0x8a,0x8d,0x8e,
0x8f,0x90,0x95,0x96,
0x6f,0x70,0x71,0x72,
0x91,0x92,0x95,0x96,
0x87,0x88,0x71,0x72,
0x91,0x92,0x95,0x96,
0x89,0x8a,0x71,0x72,
0x91,0x92,0x95,0x96,
0x6f,0x70,0x8b,0x8c,
0x91,0x92,0x95,0x96,
0x87,0x88,0x8b,0x8c,
0x91,0x92,0x95,0x96,
0x89,0x8a,0x8b,0x8c,
0x91,0x92,0x95,0x96,
0x6f,0x70,0x8d,0x8e,
0x91,0x92,0x95,0x96,
0x87,0x88,0x8d,0x8e,
0x91,0x92,0x95,0x96,
0x89,0x8a,0x8d,0x8e,
0x91,0x92,0x95,0x96,
};
+5 -5
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: tiles.png -c tiles.c -map -bpp 2 -noflip -keep_palette_order
// Conversion args: /tiles.png -c /tiles.c -map -bpp 2 -noflip -keep_palette_order
#ifndef METASPRITE_tiles_H
#define METASPRITE_tiles_H
@@ -12,18 +12,18 @@
#define tiles_TILE_W 8
#define tiles_TILE_H 8
#define tiles_WIDTH 32
#define tiles_HEIGHT 1032
#define tiles_TILE_COUNT 65
#define tiles_HEIGHT 5768
#define tiles_TILE_COUNT 151
#define tiles_PALETTE_COUNT 8
#define tiles_COLORS_PER_PALETTE 4
#define tiles_TOTAL_COLORS 32
#define tiles_MAP_ATTRIBUTES 0
extern const unsigned char tiles_map[516];
extern const unsigned char tiles_map[2884];
#define tiles_map_attributes tiles_map
BANKREF_EXTERN(tiles)
extern const palette_color_t tiles_palettes[32];
extern const uint8_t tiles_tiles[1040];
extern const uint8_t tiles_tiles[2416];
#endif
+1 -1
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: title_bg.png -c title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
// Conversion args: /title_bg.png -c /title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
#include <stdint.h>
#include <gbdk/platform.h>
+1 -1
View File
@@ -1,5 +1,5 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: title_bg.png -c title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
// Conversion args: /title_bg.png -c /title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
#ifndef METASPRITE_title_bg_H
#define METASPRITE_title_bg_H
+51
View File
@@ -0,0 +1,51 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /victory.png -c /victory.c -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(victory)
const palette_color_t victory_palettes[32] = {
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t victory_tiles[224] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x88,0xff,0x88,0xdd,0x88,0xdd,0x88,0xdd,
0xc1,0xeb,0xc1,0xe3,0xe3,0xf7,0xf7,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xc3,0xff,0xc3,0xe7,0xe3,0xf7,0xe3,0xf7,
0xe3,0xf7,0xe3,0xf7,0xc3,0xe7,0xc3,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xc3,0xff,0x81,0xc3,0x83,0xdf,0x8f,0xdf,
0x8f,0xdf,0x83,0xdf,0x81,0xc3,0xc3,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x81,0xff,0x80,0xc1,0xe3,0xf7,0xe3,0xf7,
0xe3,0xf7,0xe3,0xf7,0xe3,0xf7,0xe3,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xc3,0xff,0x81,0xc3,0x81,0xdb,0x81,0xdb,
0x81,0xdb,0x81,0xdb,0x81,0xc3,0xc3,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x83,0xff,0x81,0xc3,0x81,0xdb,0x81,0xc3,
0x81,0xdb,0x89,0xdb,0x89,0xdb,0x99,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x99,0xff,0x88,0xdd,0xc1,0xeb,0xc1,0xe3,
0xe3,0xf7,0xe3,0xf7,0xe3,0xf7,0xe3,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};
const metasprite_t victory_metasprite0[] = {
METASPR_ITEM(-8, -28, 0, S_PAL(0)),
METASPR_ITEM(0, 8, 2, S_PAL(0)),
METASPR_ITEM(0, 8, 4, S_PAL(0)),
METASPR_ITEM(0, 8, 6, S_PAL(0)),
METASPR_ITEM(0, 8, 8, S_PAL(0)),
METASPR_ITEM(0, 8, 10, S_PAL(0)),
METASPR_ITEM(0, 8, 12, S_PAL(0)),
METASPR_TERM
};
const metasprite_t* const victory_metasprites[1] = {
victory_metasprite0
};
+31
View File
@@ -0,0 +1,31 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: /victory.png -c /victory.c -bpp 2 -noflip -keep_palette_order
#ifndef METASPRITE_victory_H
#define METASPRITE_victory_H
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
#define victory_TILE_ORIGIN 0
#define victory_TILE_W 8
#define victory_TILE_H 16
#define victory_WIDTH 56
#define victory_HEIGHT 16
#define victory_TILE_COUNT 14
#define victory_PALETTE_COUNT 8
#define victory_COLORS_PER_PALETTE 4
#define victory_TOTAL_COLORS 32
#define victory_PIVOT_X 28
#define victory_PIVOT_Y 8
#define victory_PIVOT_W 56
#define victory_PIVOT_H 16
extern const metasprite_t* const victory_metasprites[1];
BANKREF_EXTERN(victory)
extern const palette_color_t victory_palettes[32];
extern const uint8_t victory_tiles[224];
#endif
-220
View File
@@ -1,220 +0,0 @@
//AUTOGENERATED FILE FROM png2asset
// Conversion args: tiles.png -c tiles.c -map -bpp 2 -noflip -keep_palette_order
#include <stdint.h>
#include <gbdk/platform.h>
#include <gbdk/metasprites.h>
BANKREF(tiles)
const palette_color_t tiles_palettes[32] = {
RGB8(255,255,255), RGB8(170,170,170), RGB8( 85, 85, 85), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0),
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
};
const uint8_t tiles_tiles[1040] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xff,0xf8,0xfe,0xe0,0xf8,0x80,0xe3,
0xfe,0xff,0xf8,0xfe,0xe0,0xf8,0x80,0xe0,0x00,0x83,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0x7f,0xff,0x1f,0x7f,0x07,0x1f,0x01,0x07,0x00,0x81,0x80,0x78,0xf8,0x07,0xff,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0xff,0x0f,0x3f,0x03,0x8f,0x80,0x73,
0x01,0xce,0xc0,0xf1,0xf0,0xfc,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x81,0x80,0xe0,0xe0,0xf8,0xf8,0xfe,0xfe,0xff,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc1,0x01,0x07,0x07,0x1f,0x1f,0x7f,0x7f,0xff,
0x01,0xc7,0x07,0x1f,0x1f,0x7f,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xfe,0x01,0xf8,0x06,0xe0,0x18,0x80,0x63,
0xfe,0x01,0xf8,0x06,0xe0,0x18,0x80,0x60,0x00,0x83,0x03,0x1c,0x1f,0x60,0x7f,0x80,
0x7f,0x80,0x1f,0x60,0x07,0x18,0x01,0x06,0x00,0x81,0x80,0x78,0xf8,0x07,0xff,0x00,
0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x3f,0xc0,0x0f,0x30,0x03,0x8c,0x80,0x73,
0x01,0xce,0xc0,0x31,0xf0,0x0c,0xfc,0x03,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,
0xff,0x00,0x1f,0xe0,0x01,0x1e,0x00,0x81,0x80,0x60,0xe0,0x18,0xf8,0x06,0xfe,0x01,
0xfe,0x01,0xf8,0x06,0xc0,0x38,0x00,0xc1,0x01,0x06,0x07,0x18,0x1f,0x60,0x7f,0x80,
0x01,0xc6,0x07,0x18,0x1f,0x60,0x7f,0x80,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xff,0xf9,0xfe,0xe7,0xf8,0x9c,0xe3,
0xfe,0xff,0xf9,0xfe,0xe7,0xf8,0x9f,0xe0,0x7c,0x83,0xe0,0x1c,0x80,0x60,0x00,0x80,
0x7f,0xff,0x9f,0x7f,0xe7,0x1f,0xf9,0x07,0x7e,0x81,0x07,0x78,0x00,0x07,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0xff,0xcf,0x3f,0x73,0x8f,0x0c,0x73,
0x30,0xce,0xce,0xf1,0xf3,0xfc,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0x7e,0x81,0x9f,0xe0,0xe7,0xf8,0xf9,0xfe,0xfe,0xff,
0x00,0x01,0x01,0x06,0x07,0x38,0x3e,0xc1,0xf9,0x07,0xe7,0x1f,0x9f,0x7f,0x7f,0xff,
0x39,0xc7,0xe7,0x1f,0x9f,0x7f,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x07,0x18,0x1c,0x63,
0x00,0x01,0x01,0x06,0x07,0x18,0x1f,0x60,0x7c,0x83,0xe0,0x1c,0x80,0x60,0x00,0x80,
0x00,0x80,0x80,0x60,0xe0,0x18,0xf8,0x06,0x7e,0x81,0x07,0x78,0x00,0x07,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x30,0x70,0x8c,0x0c,0x73,
0x30,0xce,0x0e,0x31,0x03,0x0c,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xe0,0xe0,0x1e,0x7e,0x81,0x1f,0x60,0x07,0x18,0x01,0x06,0x00,0x01,
0x00,0x01,0x01,0x06,0x07,0x38,0x3e,0xc1,0xf8,0x06,0xe0,0x18,0x80,0x60,0x00,0x80,
0x38,0xc6,0xe0,0x18,0x80,0x60,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xff,0xf8,0xff,0xe3,
0xff,0xff,0xff,0xfe,0xff,0xf8,0xff,0xe0,0xff,0x83,0xfc,0x1f,0xe0,0x7f,0x80,0xff,
0xff,0xff,0xff,0x7f,0xff,0x1f,0xff,0x07,0xff,0x81,0x7f,0xf8,0x07,0xff,0x00,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0xff,0x8f,0x7f,0xf3,
0xfe,0xcf,0xff,0xf1,0xff,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0xff,0xe0,0xff,0xfe,0x1f,0xff,0x81,0xff,0xe0,0xff,0xf8,0xff,0xfe,0xff,0xff,
0x01,0xff,0x07,0xfe,0x3f,0xf8,0xff,0xc1,0xff,0x07,0xff,0x1f,0xff,0x7f,0xff,0xff,
0xff,0xc7,0xff,0x1f,0xff,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x01,0xff,0x07,0xfe,0x1f,0xf8,0x7f,0xe3,
0x01,0xff,0x07,0xfe,0x1f,0xf8,0x7f,0xe0,0xff,0x83,0xfc,0x1f,0xe0,0x7f,0x80,0xff,
0x80,0xff,0xe0,0x7f,0xf8,0x1f,0xfe,0x07,0xff,0x81,0x7f,0xf8,0x07,0xff,0x00,0xff,
0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xc0,0xff,0xf0,0x3f,0xfc,0x8f,0x7f,0xf3,
0xfe,0xcf,0x3f,0xf1,0x0f,0xfc,0x03,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
0x00,0xff,0xe0,0xff,0xfe,0x1f,0xff,0x81,0x7f,0xe0,0x1f,0xf8,0x07,0xfe,0x01,0xff,
0x01,0xff,0x07,0xfe,0x3f,0xf8,0xff,0xc1,0xfe,0x07,0xf8,0x1f,0xe0,0x7f,0x80,0xff,
0xfe,0xc7,0xf8,0x1f,0xe0,0x7f,0x80,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xff,0xf8,0xff,0xe3,0xff,
0xff,0xff,0xfe,0xff,0xf8,0xff,0xe0,0xff,0x83,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0xff,0xff,0x7f,0xff,0x1f,0xff,0x07,0xff,0x81,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0xff,0x8f,0xff,0xf3,0x7f,
0xcf,0xfe,0xf1,0xff,0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x81,0xff,0xe0,0xff,0xf8,0xff,0xfe,0xff,0xff,0xff,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc1,0xff,0x07,0xff,0x1f,0xff,0x7f,0xff,0xff,0xff,
0xc7,0xff,0x1f,0xff,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x01,0xfe,0x07,0xf8,0x1f,0xe3,0x7f,
0xff,0x01,0xfe,0x07,0xf8,0x1f,0xe0,0x7f,0x83,0xff,0x1f,0xfc,0x7f,0xe0,0xff,0x80,
0xff,0x80,0x7f,0xe0,0x1f,0xf8,0x07,0xfe,0x81,0xff,0xf8,0x7f,0xff,0x07,0xff,0x00,
0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xc0,0x3f,0xf0,0x8f,0xfc,0xf3,0x7f,
0xcf,0xfe,0xf1,0x3f,0xfc,0x0f,0xff,0x03,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,
0xff,0x00,0xff,0xe0,0x1f,0xfe,0x81,0xff,0xe0,0x7f,0xf8,0x1f,0xfe,0x07,0xff,0x01,
0xff,0x01,0xfe,0x07,0xf8,0x3f,0xc1,0xff,0x07,0xfe,0x1f,0xf8,0x7f,0xe0,0xff,0x80,
0xc7,0xfe,0x1f,0xf8,0x7f,0xe0,0xff,0x80,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00
};
const unsigned char tiles_map[516] = {
0x00,0x00,0x00,0x00,
0x01,0x02,0x03,0x04,
0x05,0x06,0x07,0x08,
0x09,0x0a,0x03,0x04,
0x05,0x06,0x07,0x08,
0x01,0x02,0x0b,0x0c,
0x05,0x06,0x07,0x08,
0x09,0x0a,0x0b,0x0c,
0x05,0x06,0x07,0x08,
0x01,0x02,0x03,0x04,
0x0d,0x0e,0x07,0x08,
0x09,0x0a,0x03,0x04,
0x0d,0x0e,0x07,0x08,
0x01,0x02,0x0b,0x0c,
0x0d,0x0e,0x07,0x08,
0x09,0x0a,0x0b,0x0c,
0x0d,0x0e,0x07,0x08,
0x01,0x02,0x03,0x04,
0x05,0x06,0x0f,0x10,
0x09,0x0a,0x03,0x04,
0x05,0x06,0x0f,0x10,
0x01,0x02,0x0b,0x0c,
0x05,0x06,0x0f,0x10,
0x09,0x0a,0x0b,0x0c,
0x05,0x06,0x0f,0x10,
0x01,0x02,0x03,0x04,
0x0d,0x0e,0x0f,0x10,
0x09,0x0a,0x03,0x04,
0x0d,0x0e,0x0f,0x10,
0x01,0x02,0x0b,0x0c,
0x0d,0x0e,0x0f,0x10,
0x09,0x0a,0x0b,0x0c,
0x0d,0x0e,0x0f,0x10,
0x11,0x12,0x13,0x14,
0x15,0x16,0x17,0x18,
0x19,0x1a,0x13,0x14,
0x15,0x16,0x17,0x18,
0x11,0x12,0x1b,0x1c,
0x15,0x16,0x17,0x18,
0x19,0x1a,0x1b,0x1c,
0x15,0x16,0x17,0x18,
0x11,0x12,0x13,0x14,
0x1d,0x1e,0x17,0x18,
0x19,0x1a,0x13,0x14,
0x1d,0x1e,0x17,0x18,
0x11,0x12,0x1b,0x1c,
0x1d,0x1e,0x17,0x18,
0x19,0x1a,0x1b,0x1c,
0x1d,0x1e,0x17,0x18,
0x11,0x12,0x13,0x14,
0x15,0x16,0x1f,0x20,
0x19,0x1a,0x13,0x14,
0x15,0x16,0x1f,0x20,
0x11,0x12,0x1b,0x1c,
0x15,0x16,0x1f,0x20,
0x19,0x1a,0x1b,0x1c,
0x15,0x16,0x1f,0x20,
0x11,0x12,0x13,0x14,
0x1d,0x1e,0x1f,0x20,
0x19,0x1a,0x13,0x14,
0x1d,0x1e,0x1f,0x20,
0x11,0x12,0x1b,0x1c,
0x1d,0x1e,0x1f,0x20,
0x19,0x1a,0x1b,0x1c,
0x1d,0x1e,0x1f,0x20,
0x21,0x22,0x23,0x24,
0x25,0x26,0x27,0x28,
0x29,0x2a,0x23,0x24,
0x25,0x26,0x27,0x28,
0x21,0x22,0x2b,0x2c,
0x25,0x26,0x27,0x28,
0x29,0x2a,0x2b,0x2c,
0x25,0x26,0x27,0x28,
0x21,0x22,0x23,0x24,
0x2d,0x2e,0x27,0x28,
0x29,0x2a,0x23,0x24,
0x2d,0x2e,0x27,0x28,
0x21,0x22,0x2b,0x2c,
0x2d,0x2e,0x27,0x28,
0x29,0x2a,0x2b,0x2c,
0x2d,0x2e,0x27,0x28,
0x21,0x22,0x23,0x24,
0x25,0x26,0x2f,0x30,
0x29,0x2a,0x23,0x24,
0x25,0x26,0x2f,0x30,
0x21,0x22,0x2b,0x2c,
0x25,0x26,0x2f,0x30,
0x29,0x2a,0x2b,0x2c,
0x25,0x26,0x2f,0x30,
0x21,0x22,0x23,0x24,
0x2d,0x2e,0x2f,0x30,
0x29,0x2a,0x23,0x24,
0x2d,0x2e,0x2f,0x30,
0x21,0x22,0x2b,0x2c,
0x2d,0x2e,0x2f,0x30,
0x29,0x2a,0x2b,0x2c,
0x2d,0x2e,0x2f,0x30,
0x31,0x32,0x33,0x34,
0x35,0x36,0x37,0x38,
0x39,0x3a,0x33,0x34,
0x35,0x36,0x37,0x38,
0x31,0x32,0x3b,0x3c,
0x35,0x36,0x37,0x38,
0x39,0x3a,0x3b,0x3c,
0x35,0x36,0x37,0x38,
0x31,0x32,0x33,0x34,
0x3d,0x3e,0x37,0x38,
0x39,0x3a,0x33,0x34,
0x3d,0x3e,0x37,0x38,
0x31,0x32,0x3b,0x3c,
0x3d,0x3e,0x37,0x38,
0x39,0x3a,0x3b,0x3c,
0x3d,0x3e,0x37,0x38,
0x31,0x32,0x33,0x34,
0x35,0x36,0x3f,0x40,
0x39,0x3a,0x33,0x34,
0x35,0x36,0x3f,0x40,
0x31,0x32,0x3b,0x3c,
0x35,0x36,0x3f,0x40,
0x39,0x3a,0x3b,0x3c,
0x35,0x36,0x3f,0x40,
0x31,0x32,0x33,0x34,
0x3d,0x3e,0x3f,0x40,
0x39,0x3a,0x33,0x34,
0x3d,0x3e,0x3f,0x40,
0x31,0x32,0x3b,0x3c,
0x3d,0x3e,0x3f,0x40,
0x39,0x3a,0x3b,0x3c,
0x3d,0x3e,0x3f,0x40,
};
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB