commit 20a5d8604b824d2da074c433eab1a702da610306 Author: enne2 Date: Sat Jun 20 16:30:15 2026 +0200 Initial commit of isometric Game Boy adventure prototype diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbf6059 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# GBDK build artifacts +*.o +*.lst +*.map +*.sym +*.cdb +*.ihx +*.noi +*.asm + +# Compiled ROMs +*.gb + +# Python caches +__pycache__/ +*.pyc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..246321d --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +GBDK_HOME = /home/enne2/.local/gbdk +LCC = $(GBDK_HOME)/bin/lcc +PNG2ASSET = $(GBDK_HOME)/bin/png2asset + +all: hello_iso.gb + +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 + +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 + +clean: + rm -f *.o *.lst *.map *.gb *.ihx *.sym *.cdb *.adb *.asm diff --git a/engine.c b/engine.c new file mode 100644 index 0000000..6c3786d --- /dev/null +++ b/engine.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include "engine.h" + +// Generated by png2asset +#include "tiles.h" +#include "player.h" + +#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 walk_timer = 0; +uint8_t scroll_x = 0; +uint8_t scroll_y = 0; + +// DAS variables +uint8_t das_timer = 0; +uint8_t das_active = 0; +#define DAS_DELAY 12 +#define DAS_REPEAT 6 + +static void generate_maze(void) { + // Clear maze (all 0/walls) + memset(maze, 0, sizeof(maze)); + + // Stack for backtracking (3x3 grid has 9 odd cells max) + uint8_t stack_x[9]; + uint8_t stack_y[9]; + 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 <= 3 && 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 <= 3 && 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; + } + } +} + +static void draw_map(void) { + // Fill entire map with tile index 0 (completely black empty tile) + memset(map_buffer, 0, sizeof(map_buffer)); + + // Draw logical map path tiles + for (int8_t ly = 0; ly < MAP_SIZE; ly++) { + for (int8_t lx = 0; lx < MAP_SIZE; lx++) { + if (maze[ly][lx] == 1) { + // Centering math for 7x7 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); + uint8_t 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; + } + } + } + } + } + + set_bkg_tiles(0, 0, 32, 32, map_buffer); +} + +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 = (walk_timer > 0) ? ((walk_timer >> 2) & 1) : 0; + move_metasprite(player_metasprites[player_dir * 2 + frame_offset], 0, 0, 88, 88); +} + +void engine_init(void) { + SPRITES_8x16; + + // 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) + 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); + + draw_map(); + update_camera(); + update_player_sprite(); +} + +void engine_update(uint8_t keys, uint8_t prev_keys) { + if (walk_timer > 0) { + walk_timer--; + update_player_sprite(); + } + + uint8_t keys_pressed = keys & ~prev_keys; + + int8_t move_lx = 0; + int8_t move_ly = 0; + + if (keys_pressed) { + das_timer = DAS_DELAY; + das_active = 1; + } else if (keys && das_active) { + das_timer--; + if (das_timer == 0) { + das_timer = DAS_REPEAT; + keys_pressed = keys; // trigger move + } + } else { + das_active = 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) { + 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) { + player_lx = new_lx; + player_ly = new_ly; + walk_timer = 12; // 12 frames of walk cycle + } + } + update_player_sprite(); + update_camera(); + } +} diff --git a/engine.h b/engine.h new file mode 100644 index 0000000..2e3a42d --- /dev/null +++ b/engine.h @@ -0,0 +1,9 @@ +#ifndef ENGINE_H +#define ENGINE_H + +#include + +void engine_init(void); +void engine_update(uint8_t keys, uint8_t prev_keys); + +#endif diff --git a/generate_assets.py b/generate_assets.py new file mode 100644 index 0000000..b2bb766 --- /dev/null +++ b/generate_assets.py @@ -0,0 +1,197 @@ +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): + # 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) + neighbor_color = 0 if is_alt else 1 + + # 4 corners + # Bit 0 (1): Top-Left + # Bit 1 (2): Top-Right + # Bit 2 (4): Bottom-Left + # Bit 3 (8): Bottom-Right + + 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) + +def generate_tiles(): + # 32 variants total: + # 0..15: Tile 1 (Normal Floor) with masks 0..15 + # 16..31: Tile 2 (Alt Floor) with masks 0..15 + # Height is 8 pixels (black spacer) + 32 variants * 16 pixels = 520 pixels + img = Image.new("P", (32, 520), 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) + + # Draw Tile 2 variants (start at y=8 + 256 = 264) + for mask in range(16): + draw_autotile(draw, 264 + mask * 16, True, mask) + + img.save("tiles.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") + +if __name__ == "__main__": + generate_tiles() + generate_player() + print("PNG assets generated (32x520 tiles, 8-frame player animation set created).") diff --git a/hello_iso_gb.png b/hello_iso_gb.png new file mode 100644 index 0000000..ee3f2ce Binary files /dev/null and b/hello_iso_gb.png differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..f6abe22 --- /dev/null +++ b/main.c @@ -0,0 +1,19 @@ +#include +#include "engine.h" + +void main(void) { + engine_init(); + + SHOW_BKG; + SHOW_SPRITES; + DISPLAY_ON; + + uint8_t prev_keys = 0; + + while (1) { + wait_vbl_done(); + uint8_t keys = joypad(); + engine_update(keys, prev_keys); + prev_keys = keys; + } +} diff --git a/opencv_analyze_tiles.py b/opencv_analyze_tiles.py new file mode 100644 index 0000000..d93b5f6 --- /dev/null +++ b/opencv_analyze_tiles.py @@ -0,0 +1,56 @@ +import cv2 +import numpy as np + +def main(): + img = cv2.imread('hello_iso_gb.png') + if img is None: + print("Error: Could not load hello_iso_gb.png") + return + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + h, w = gray.shape + + # Create a markup copy of the image + markup = img.copy() + + # We want to detect the rendering glitches (black V-shapes or gaps cutting into the floor tiles) + # The floor tiles are White (255) and Light Gray (153) with Dark Gray (85) lines. + # The empty background is Black (0). + # Inside the walkable path, any Black (0) pixel that is surrounded by floor tile colors + # represents a cutout glitch. + # Let's use morphological operations to find these black cutouts inside the tiles. + + # Threshold to find floor tiles (value > 50, so anything not black) + _, floor_mask = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY) + + # Invert to find black pixels + black_mask = cv2.bitwise_not(floor_mask) + + # Find contours of the black regions + contours, _ = cv2.findContours(black_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + + glitch_count = 0 + for cnt in contours: + x, y, rw, rh = cv2.boundingRect(cnt) + + # The screen border is black, so we ignore black regions touching the screen boundaries + if x <= 2 or y <= 2 or (x + rw) >= w - 2 or (y + rh) >= h - 2: + continue + + # Glitches are small black gaps/triangles. Typically small (e.g. area between 4 and 40 pixels) + area = cv2.contourArea(cnt) + if 2 <= area <= 50: + # Check if it's inside the checkerboard area + # (which is roughly centered on the screen) + cv2.rectangle(markup, (x, y), (x + rw, y + rh), (0, 0, 255), 1) # Red box + glitch_count += 1 + + print(f"Detected {glitch_count} rendering glitches (black cutouts/gaps) inside the floor map.") + + # Save the marked-up image to the artifacts directory + output_path = '/home/enne2/.gemini/antigravity-ide/brain/eebf5acd-ae2e-41bb-858e-6a4a5ca41897/analyzed_tiles.png' + cv2.imwrite(output_path, markup) + print(f"Saved analysis markup to: {output_path}") + +if __name__ == '__main__': + main() diff --git a/opencv_check_centering.py b/opencv_check_centering.py new file mode 100644 index 0000000..16e92d0 --- /dev/null +++ b/opencv_check_centering.py @@ -0,0 +1,30 @@ +import cv2 +import numpy as np + +def main(): + img = cv2.imread('hello_iso_gb.png') + if img is None: + print("Error: Could not load hello_iso_gb.png") + return + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + h, w = gray.shape + print(f"Image dimensions: {w}x{h}") + + # Print all unique gray values on screen + unique_vals = np.unique(gray) + print(f"Unique gray values: {unique_vals}") + + # Find all contours in the image + contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + + print("\nAll non-trivial contours:") + for idx, cnt in enumerate(contours): + x, y, rw, rh = cv2.boundingRect(cnt) + area = cv2.contourArea(cnt) + # Filter out screen-sized or tiny noise contours + if 4 <= area <= 10000: + print(f"Contour {idx}: x={x}, y={y}, w={rw}, h={rh}, area={area}, center=({x + rw/2}, {y + rh/2})") + +if __name__ == '__main__': + main() diff --git a/player.c b/player.c new file mode 100644 index 0000000..bbd2cbe --- /dev/null +++ b/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(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/player.h b/player.h new file mode 100644 index 0000000..5d16cf8 --- /dev/null +++ b/player.h @@ -0,0 +1,31 @@ +//AUTOGENERATED FILE FROM png2asset +// 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 + +#include +#include +#include + +#define player_TILE_ORIGIN 0 +#define player_TILE_W 8 +#define player_TILE_H 16 +#define player_WIDTH 16 +#define player_HEIGHT 16 +#define player_TILE_COUNT 28 +#define player_PALETTE_COUNT 8 +#define player_COLORS_PER_PALETTE 4 +#define player_TOTAL_COLORS 32 +#define player_PIVOT_X 8 +#define player_PIVOT_Y 8 +#define player_PIVOT_W 16 +#define player_PIVOT_H 16 +extern const metasprite_t* const player_metasprites[8]; + +BANKREF_EXTERN(player) + +extern const palette_color_t player_palettes[32]; +extern const uint8_t player_tiles[448]; + +#endif diff --git a/player.png b/player.png new file mode 100644 index 0000000..c491f37 Binary files /dev/null and b/player.png differ diff --git a/test_movement.py b/test_movement.py new file mode 100644 index 0000000..d4143ad --- /dev/null +++ b/test_movement.py @@ -0,0 +1,59 @@ +from pyboy import PyBoy +from pyboy.utils import WindowEvent + +def main(): + pyboy = PyBoy('hello_iso.gb', window='null', cgb=False) + # Let the game boot and generate the maze + for _ in range(60 * 2): + pyboy.tick() + + # Read player position from RAM + # player_lx at 0xC4E8, player_ly at 0xC4E9 + lx = pyboy.memory[0xC4E8] + ly = pyboy.memory[0xC4E9] + print(f"Initial player coordinates in RAM: lx={lx}, ly={ly}") + + # Read maze array from RAM (7x7 starting at 0xC0B1) + maze = [] + for y in range(7): + row = [] + for x in range(7): + val = pyboy.memory[0xC0B1 + y * 7 + x] + row.append(val) + maze.append(row) + + print("\nMaze layout in RAM (0=wall, 1=path):") + for y, row in enumerate(maze): + row_str = f"{y:02d}: " + for x, val in enumerate(row): + if x == lx and y == ly: + row_str += "P" # Player + else: + row_str += str(val) + print(row_str) + + # Check neighbors of player + print(f"\nPlayer neighbors: Up={maze[ly-1][lx]}, Down={maze[ly+1][lx]}, Left={maze[ly][lx-1]}, Right={maze[ly][lx+1]}") + + # Try pressing Down (Down-Left) + print("\nSimulating pressing DOWN...") + pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + pyboy.tick() + pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN) + for _ in range(30): + pyboy.tick() + print(f"Player coordinates after DOWN: lx={pyboy.memory[0xC4E8]}, ly={pyboy.memory[0xC4E9]}") + + # Try pressing Right (Down-Right) + print("\nSimulating pressing RIGHT...") + pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) + pyboy.tick() + pyboy.send_input(WindowEvent.RELEASE_ARROW_RIGHT) + for _ in range(30): + pyboy.tick() + print(f"Player coordinates after RIGHT: lx={pyboy.memory[0xC4E8]}, ly={pyboy.memory[0xC4E9]}") + + pyboy.stop() + +if __name__ == "__main__": + main() diff --git a/test_pyboy.py b/test_pyboy.py new file mode 100644 index 0000000..44f26c4 --- /dev/null +++ b/test_pyboy.py @@ -0,0 +1,14 @@ +from pyboy import PyBoy + +def main(): + pyboy = PyBoy('hello_iso.gb', window='null', cgb=False) + # Tick a few frames to let the map render + for _ in range(60 * 2): + pyboy.tick() + + pyboy.screen.image.save('hello_iso_gb.png') + pyboy.stop() + print("Screenshot saved to hello_iso_gb.png") + +if __name__ == "__main__": + main() diff --git a/tiles.c b/tiles.c new file mode 100644 index 0000000..f065d66 --- /dev/null +++ b/tiles.c @@ -0,0 +1,124 @@ +//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[528] = { + 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 + }; + + +const unsigned char tiles_map[260] = { + 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, +}; diff --git a/tiles.h b/tiles.h new file mode 100644 index 0000000..798a40c --- /dev/null +++ b/tiles.h @@ -0,0 +1,29 @@ +//AUTOGENERATED FILE FROM png2asset +// Conversion args: tiles.png -c tiles.c -map -bpp 2 -noflip -keep_palette_order + +#ifndef METASPRITE_tiles_H +#define METASPRITE_tiles_H + +#include +#include +#include + +#define tiles_TILE_ORIGIN 0 +#define tiles_TILE_W 8 +#define tiles_TILE_H 8 +#define tiles_WIDTH 32 +#define tiles_HEIGHT 520 +#define tiles_TILE_COUNT 33 +#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[260]; +#define tiles_map_attributes tiles_map + +BANKREF_EXTERN(tiles) + +extern const palette_color_t tiles_palettes[32]; +extern const uint8_t tiles_tiles[528]; + +#endif diff --git a/tiles.png b/tiles.png new file mode 100644 index 0000000..859604a Binary files /dev/null and b/tiles.png differ