diff --git a/Makefile b/Makefile index e560c27..7909da8 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bg.png b/assets/bg.png similarity index 100% rename from bg.png rename to assets/bg.png diff --git a/copertina.png b/assets/copertina.png similarity index 100% rename from copertina.png rename to assets/copertina.png diff --git a/assets/current_enemy.png b/assets/current_enemy.png new file mode 100644 index 0000000..9929159 Binary files /dev/null and b/assets/current_enemy.png differ diff --git a/assets/enemy.png b/assets/enemy.png new file mode 100644 index 0000000..56a64c9 Binary files /dev/null and b/assets/enemy.png differ diff --git a/gameover.png b/assets/gameover.png similarity index 86% rename from gameover.png rename to assets/gameover.png index bb33cbf..c11bffc 100644 Binary files a/gameover.png and b/assets/gameover.png differ diff --git a/gameover_rendering.png b/assets/gameover_rendering.png similarity index 100% rename from gameover_rendering.png rename to assets/gameover_rendering.png diff --git a/assets/ghost_enemy.png b/assets/ghost_enemy.png new file mode 100644 index 0000000..3911348 Binary files /dev/null and b/assets/ghost_enemy.png differ diff --git a/assets/ghost_enemy_indexed.png b/assets/ghost_enemy_indexed.png new file mode 100644 index 0000000..bff1bdc Binary files /dev/null and b/assets/ghost_enemy_indexed.png differ diff --git a/hello_iso_gb.png b/assets/hello_iso_gb.png similarity index 100% rename from hello_iso_gb.png rename to assets/hello_iso_gb.png diff --git a/player.png b/assets/original_player.png similarity index 100% rename from player.png rename to assets/original_player.png diff --git a/assets/peasant_16x16.png b/assets/peasant_16x16.png new file mode 100644 index 0000000..93df002 Binary files /dev/null and b/assets/peasant_16x16.png differ diff --git a/assets/peasant_preview.png b/assets/peasant_preview.png new file mode 100644 index 0000000..744735e Binary files /dev/null and b/assets/peasant_preview.png differ diff --git a/assets/peasant_preview2.png b/assets/peasant_preview2.png new file mode 100644 index 0000000..ac49e7e Binary files /dev/null and b/assets/peasant_preview2.png differ diff --git a/assets/peasant_preview3.png b/assets/peasant_preview3.png new file mode 100644 index 0000000..e5d3a1f Binary files /dev/null and b/assets/peasant_preview3.png differ diff --git a/screenshot.png b/assets/peasant_screenshot.png similarity index 100% rename from screenshot.png rename to assets/peasant_screenshot.png diff --git a/assets/player.png b/assets/player.png new file mode 100644 index 0000000..d982079 Binary files /dev/null and b/assets/player.png differ diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..16c9cc3 Binary files /dev/null and b/assets/screenshot.png differ diff --git a/assets/stairs_sprite.png b/assets/stairs_sprite.png new file mode 100644 index 0000000..7f3c222 Binary files /dev/null and b/assets/stairs_sprite.png differ diff --git a/stamina.png b/assets/stamina.png similarity index 85% rename from stamina.png rename to assets/stamina.png index 0b57171..16cf938 100644 Binary files a/stamina.png and b/assets/stamina.png differ diff --git a/assets/test_peasant.png b/assets/test_peasant.png new file mode 100644 index 0000000..2bc5cf6 Binary files /dev/null and b/assets/test_peasant.png differ diff --git a/assets/tiles.png b/assets/tiles.png new file mode 100644 index 0000000..bea98ae Binary files /dev/null and b/assets/tiles.png differ diff --git a/title_bg.png b/assets/title_bg.png similarity index 100% rename from title_bg.png rename to assets/title_bg.png diff --git a/assets/victory.png b/assets/victory.png new file mode 100644 index 0000000..4629a03 Binary files /dev/null and b/assets/victory.png differ diff --git a/bg.zip b/bg.zip deleted file mode 100644 index eeb2a82..0000000 Binary files a/bg.zip and /dev/null differ diff --git a/doc/ai.md b/doc/ai.md new file mode 100644 index 0000000..e94747f --- /dev/null +++ b/doc/ai.md @@ -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! diff --git a/doc/architecture.md b/doc/architecture.md new file mode 100644 index 0000000..ad9ff83 --- /dev/null +++ b/doc/architecture.md @@ -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. diff --git a/doc/audio.md b/doc/audio.md new file mode 100644 index 0000000..7d1f75a --- /dev/null +++ b/doc/audio.md @@ -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. diff --git a/doc/gameplay.md b/doc/gameplay.md new file mode 100644 index 0000000..c4b77c1 --- /dev/null +++ b/doc/gameplay.md @@ -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`. diff --git a/doc/generation.md b/doc/generation.md new file mode 100644 index 0000000..30cf47f --- /dev/null +++ b/doc/generation.md @@ -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. diff --git a/doc/graphics.md b/doc/graphics.md new file mode 100644 index 0000000..8a2a07f --- /dev/null +++ b/doc/graphics.md @@ -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). diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..b201bdc --- /dev/null +++ b/doc/index.md @@ -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. diff --git a/engine.c b/engine.c deleted file mode 100644 index 8702020..0000000 --- a/engine.c +++ /dev/null @@ -1,971 +0,0 @@ -#include -#include -#include -#include -#include -#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(); - } - } - } -} diff --git a/generate_assets.py b/generate_assets.py deleted file mode 100644 index 6ecfc70..0000000 --- a/generate_assets.py +++ /dev/null @@ -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).") diff --git a/player.c b/player.c deleted file mode 100644 index bbd2cbe..0000000 --- a/player.c +++ /dev/null @@ -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 -#include -#include - -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 -}; diff --git a/scripts/build_peasant.py b/scripts/build_peasant.py new file mode 100644 index 0000000..4dd473e --- /dev/null +++ b/scripts/build_peasant.py @@ -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") diff --git a/scripts/generate_assets.py b/scripts/generate_assets.py new file mode 100644 index 0000000..ee450ff --- /dev/null +++ b/scripts/generate_assets.py @@ -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).") diff --git a/scripts/generate_enemy.py b/scripts/generate_enemy.py new file mode 100644 index 0000000..07433df --- /dev/null +++ b/scripts/generate_enemy.py @@ -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") diff --git a/scripts/generate_ghost.py b/scripts/generate_ghost.py new file mode 100644 index 0000000..d5ad3cc --- /dev/null +++ b/scripts/generate_ghost.py @@ -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") diff --git a/scripts/generate_peasant.py b/scripts/generate_peasant.py new file mode 100644 index 0000000..ca28984 --- /dev/null +++ b/scripts/generate_peasant.py @@ -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") diff --git a/scripts/generate_peasant2.py b/scripts/generate_peasant2.py new file mode 100644 index 0000000..d2ad9ba --- /dev/null +++ b/scripts/generate_peasant2.py @@ -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") diff --git a/scripts/generate_peasant3.py b/scripts/generate_peasant3.py new file mode 100644 index 0000000..cb04d95 --- /dev/null +++ b/scripts/generate_peasant3.py @@ -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") diff --git a/scripts/generate_stairs_sprite.py b/scripts/generate_stairs_sprite.py new file mode 100644 index 0000000..f35f906 --- /dev/null +++ b/scripts/generate_stairs_sprite.py @@ -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() diff --git a/scripts/make_victory.py b/scripts/make_victory.py new file mode 100644 index 0000000..49064f8 --- /dev/null +++ b/scripts/make_victory.py @@ -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() diff --git a/opencv_analyze_tiles.py b/scripts/opencv_analyze_tiles.py similarity index 100% rename from opencv_analyze_tiles.py rename to scripts/opencv_analyze_tiles.py diff --git a/opencv_check_centering.py b/scripts/opencv_check_centering.py similarity index 100% rename from opencv_check_centering.py rename to scripts/opencv_check_centering.py diff --git a/process_cover.py b/scripts/process_cover.py similarity index 100% rename from process_cover.py rename to scripts/process_cover.py diff --git a/take_screenshot.py b/scripts/take_screenshot.py similarity index 100% rename from take_screenshot.py rename to scripts/take_screenshot.py diff --git a/test_alignment.py b/scripts/test_alignment.py similarity index 100% rename from test_alignment.py rename to scripts/test_alignment.py diff --git a/test_movement.py b/scripts/test_movement.py similarity index 100% rename from test_movement.py rename to scripts/test_movement.py diff --git a/test_music.py b/scripts/test_music.py similarity index 100% rename from test_music.py rename to scripts/test_music.py diff --git a/test_pyboy.py b/scripts/test_pyboy.py similarity index 100% rename from test_pyboy.py rename to scripts/test_pyboy.py diff --git a/scripts/test_script.py b/scripts/test_script.py new file mode 100644 index 0000000..cfb74d2 --- /dev/null +++ b/scripts/test_script.py @@ -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") diff --git a/scripts/update_assets.py b/scripts/update_assets.py new file mode 100644 index 0000000..aed87c9 --- /dev/null +++ b/scripts/update_assets.py @@ -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) diff --git a/src/enemy.c b/src/enemy.c new file mode 100644 index 0000000..0f40f15 --- /dev/null +++ b/src/enemy.c @@ -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 +#include +#include + +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 +}; diff --git a/src/enemy.h b/src/enemy.h new file mode 100644 index 0000000..dd87940 --- /dev/null +++ b/src/enemy.h @@ -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 +#include +#include + +#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 diff --git a/src/enemy_logic.c b/src/enemy_logic.c new file mode 100644 index 0000000..b4acd8c --- /dev/null +++ b/src/enemy_logic.c @@ -0,0 +1,170 @@ +#include "enemy_logic.h" +#include "globals.h" +#include "render.h" // For update_stamina_display +#include + +// 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 + } +} diff --git a/src/enemy_logic.h b/src/enemy_logic.h new file mode 100644 index 0000000..943140f --- /dev/null +++ b/src/enemy_logic.h @@ -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 diff --git a/src/engine.c b/src/engine.c new file mode 100644 index 0000000..80a69fd --- /dev/null +++ b/src/engine.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include + +#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). +} diff --git a/engine.h b/src/engine.h similarity index 100% rename from engine.h rename to src/engine.h diff --git a/gameover.c b/src/gameover.c similarity index 94% rename from gameover.c rename to src/gameover.c index f069cf0..6652a1c 100644 --- a/gameover.c +++ b/src/gameover.c @@ -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 #include @@ -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), diff --git a/gameover.h b/src/gameover.h similarity index 89% rename from gameover.h rename to src/gameover.h index 4ad394f..12649f5 100644 --- a/gameover.h +++ b/src/gameover.h @@ -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 diff --git a/src/globals.c b/src/globals.c new file mode 100644 index 0000000..bfd405f --- /dev/null +++ b/src/globals.c @@ -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; diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 0000000..5ae6328 --- /dev/null +++ b/src/globals.h @@ -0,0 +1,72 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +#include + +/** + * ========================================== + * 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 diff --git a/main.c b/src/main.c similarity index 100% rename from main.c rename to src/main.c diff --git a/src/maze.c b/src/maze.c new file mode 100644 index 0000000..3f618cd --- /dev/null +++ b/src/maze.c @@ -0,0 +1,132 @@ +#include "maze.h" +#include "globals.h" +#include +#include + +/** + * 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; + } +} diff --git a/src/maze.h b/src/maze.h new file mode 100644 index 0000000..4b34682 --- /dev/null +++ b/src/maze.h @@ -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 diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..c5f584c --- /dev/null +++ b/src/player.c @@ -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 +#include +#include + +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 +}; diff --git a/player.h b/src/player.h similarity index 88% rename from player.h rename to src/player.h index 5d16cf8..93cd388 100644 --- a/player.h +++ b/src/player.h @@ -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 diff --git a/src/player_logic.c b/src/player_logic.c new file mode 100644 index 0000000..6db6481 --- /dev/null +++ b/src/player_logic.c @@ -0,0 +1,196 @@ +#include "player_logic.h" +#include "globals.h" +#include "render.h" +#include +#include + +// 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 là. 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(); + } + } +} diff --git a/src/player_logic.h b/src/player_logic.h new file mode 100644 index 0000000..1fa499d --- /dev/null +++ b/src/player_logic.h @@ -0,0 +1,17 @@ +#ifndef PLAYER_LOGIC_H +#define PLAYER_LOGIC_H + +#include + +/** + * ========================================== + * 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 diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..c6d7b34 --- /dev/null +++ b/src/render.c @@ -0,0 +1,273 @@ +#include "render.h" +#include "globals.h" +#include +#include +#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); +} diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..633f7ce --- /dev/null +++ b/src/render.h @@ -0,0 +1,26 @@ +#ifndef RENDER_H +#define RENDER_H + +#include + +/** + * ========================================== + * 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 diff --git a/src/sound.c b/src/sound.c new file mode 100644 index 0000000..cd8d9fa --- /dev/null +++ b/src/sound.c @@ -0,0 +1,308 @@ +#include "sound.h" +#include "globals.h" +#include + +/** + * ========================================== + * 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; + } + } +} diff --git a/src/sound.h b/src/sound.h new file mode 100644 index 0000000..f5db889 --- /dev/null +++ b/src/sound.h @@ -0,0 +1,23 @@ +#ifndef SOUND_H +#define SOUND_H + +#include + +/** + * ========================================== + * 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 diff --git a/src/stairs_sprite.c b/src/stairs_sprite.c new file mode 100644 index 0000000..8b3992c --- /dev/null +++ b/src/stairs_sprite.c @@ -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 +#include +#include + +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 +}; diff --git a/src/stairs_sprite.h b/src/stairs_sprite.h new file mode 100644 index 0000000..b31a9fa --- /dev/null +++ b/src/stairs_sprite.h @@ -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 +#include +#include + +#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 diff --git a/stamina.c b/src/stamina.c similarity index 95% rename from stamina.c rename to src/stamina.c index bf23bf7..cb946de 100644 --- a/stamina.c +++ b/src/stamina.c @@ -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 #include @@ -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), diff --git a/stamina.h b/src/stamina.h similarity index 89% rename from stamina.h rename to src/stamina.h index 3f37314..9e74a16 100644 --- a/stamina.h +++ b/src/stamina.h @@ -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 diff --git a/test_gameover_render.c b/src/test_gameover_render.c similarity index 100% rename from test_gameover_render.c rename to src/test_gameover_render.c diff --git a/src/test_stairs_connect.c b/src/test_stairs_connect.c new file mode 100644 index 0000000..f62b6ff --- /dev/null +++ b/src/test_stairs_connect.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +#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(); + } +} diff --git a/src/tiles.c b/src/tiles.c new file mode 100644 index 0000000..0057469 --- /dev/null +++ b/src/tiles.c @@ -0,0 +1,898 @@ +//AUTOGENERATED FILE FROM png2asset +// Conversion args: /tiles.png -c /tiles.c -map -bpp 2 -noflip -keep_palette_order + +#include +#include +#include + +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, +}; diff --git a/tiles.h b/src/tiles.h similarity index 69% rename from tiles.h rename to src/tiles.h index 40d2b5d..c2b9b8b 100644 --- a/tiles.h +++ b/src/tiles.h @@ -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 diff --git a/title_bg.c b/src/title_bg.c similarity index 99% rename from title_bg.c rename to src/title_bg.c index 49ab181..2f5b997 100644 --- a/title_bg.c +++ b/src/title_bg.c @@ -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 #include diff --git a/title_bg.h b/src/title_bg.h similarity index 87% rename from title_bg.h rename to src/title_bg.h index 4e7c3c1..1cc8f70 100644 --- a/title_bg.h +++ b/src/title_bg.h @@ -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 diff --git a/src/victory.c b/src/victory.c new file mode 100644 index 0000000..ee98e8b --- /dev/null +++ b/src/victory.c @@ -0,0 +1,51 @@ +//AUTOGENERATED FILE FROM png2asset +// Conversion args: /victory.png -c /victory.c -bpp 2 -noflip -keep_palette_order + +#include +#include +#include + +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 +}; diff --git a/src/victory.h b/src/victory.h new file mode 100644 index 0000000..8bac069 --- /dev/null +++ b/src/victory.h @@ -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 +#include +#include + +#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 diff --git a/tiles.c b/tiles.c deleted file mode 100644 index 1b423fe..0000000 --- a/tiles.c +++ /dev/null @@ -1,220 +0,0 @@ -//AUTOGENERATED FILE FROM png2asset -// Conversion args: tiles.png -c tiles.c -map -bpp 2 -noflip -keep_palette_order - -#include -#include -#include - -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, -}; diff --git a/tiles.png b/tiles.png deleted file mode 100644 index 9f5d607..0000000 Binary files a/tiles.png and /dev/null differ