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).")