diff --git a/assets/Rat/BMP_1_E.png b/assets/Rat/BMP_1_E.png index cb995c9..4f82a72 100644 Binary files a/assets/Rat/BMP_1_E.png and b/assets/Rat/BMP_1_E.png differ diff --git a/assets/Rat/BMP_1_EN.png b/assets/Rat/BMP_1_EN.png index 6f8e7bf..e4d5d35 100644 Binary files a/assets/Rat/BMP_1_EN.png and b/assets/Rat/BMP_1_EN.png differ diff --git a/assets/Rat/BMP_1_ES.png b/assets/Rat/BMP_1_ES.png index 9986257..2970915 100644 Binary files a/assets/Rat/BMP_1_ES.png and b/assets/Rat/BMP_1_ES.png differ diff --git a/assets/Rat/BMP_1_N.png b/assets/Rat/BMP_1_N.png index 8e601e8..a59619d 100644 Binary files a/assets/Rat/BMP_1_N.png and b/assets/Rat/BMP_1_N.png differ diff --git a/assets/Rat/BMP_1_NE.png b/assets/Rat/BMP_1_NE.png index 00d7193..b7f2175 100644 Binary files a/assets/Rat/BMP_1_NE.png and b/assets/Rat/BMP_1_NE.png differ diff --git a/assets/Rat/BMP_1_NW.png b/assets/Rat/BMP_1_NW.png index 92b6a64..ce5d17a 100644 Binary files a/assets/Rat/BMP_1_NW.png and b/assets/Rat/BMP_1_NW.png differ diff --git a/assets/Rat/BMP_1_S.png b/assets/Rat/BMP_1_S.png index 0a86857..eae5daa 100644 Binary files a/assets/Rat/BMP_1_S.png and b/assets/Rat/BMP_1_S.png differ diff --git a/assets/Rat/BMP_1_SE.png b/assets/Rat/BMP_1_SE.png index 71b03ab..2fa5a8b 100644 Binary files a/assets/Rat/BMP_1_SE.png and b/assets/Rat/BMP_1_SE.png differ diff --git a/assets/Rat/BMP_1_SW.png b/assets/Rat/BMP_1_SW.png index 941a2b1..66663bb 100644 Binary files a/assets/Rat/BMP_1_SW.png and b/assets/Rat/BMP_1_SW.png differ diff --git a/assets/Rat/BMP_1_W.png b/assets/Rat/BMP_1_W.png index c0b8372..b36ea24 100644 Binary files a/assets/Rat/BMP_1_W.png and b/assets/Rat/BMP_1_W.png differ diff --git a/assets/Rat/BMP_1_WN.png b/assets/Rat/BMP_1_WN.png index d5c7822..e4e90be 100644 Binary files a/assets/Rat/BMP_1_WN.png and b/assets/Rat/BMP_1_WN.png differ diff --git a/assets/Rat/BMP_1_WS.png b/assets/Rat/BMP_1_WS.png index 7a7614a..025a914 100644 Binary files a/assets/Rat/BMP_1_WS.png and b/assets/Rat/BMP_1_WS.png differ diff --git a/assets/Rat/BMP_2_E.png b/assets/Rat/BMP_2_E.png index 95246fb..6bd1568 100644 Binary files a/assets/Rat/BMP_2_E.png and b/assets/Rat/BMP_2_E.png differ diff --git a/assets/Rat/BMP_2_EN.png b/assets/Rat/BMP_2_EN.png index cb263cb..431b790 100644 Binary files a/assets/Rat/BMP_2_EN.png and b/assets/Rat/BMP_2_EN.png differ diff --git a/assets/Rat/BMP_2_ES.png b/assets/Rat/BMP_2_ES.png index 65de7b0..85dea39 100644 Binary files a/assets/Rat/BMP_2_ES.png and b/assets/Rat/BMP_2_ES.png differ diff --git a/assets/Rat/BMP_2_N.png b/assets/Rat/BMP_2_N.png index 1257507..ef79e54 100644 Binary files a/assets/Rat/BMP_2_N.png and b/assets/Rat/BMP_2_N.png differ diff --git a/assets/Rat/BMP_2_NE.png b/assets/Rat/BMP_2_NE.png index 773a8d5..594b78a 100644 Binary files a/assets/Rat/BMP_2_NE.png and b/assets/Rat/BMP_2_NE.png differ diff --git a/assets/Rat/BMP_2_NW.png b/assets/Rat/BMP_2_NW.png index 6f02b0a..c8754ad 100644 Binary files a/assets/Rat/BMP_2_NW.png and b/assets/Rat/BMP_2_NW.png differ diff --git a/assets/Rat/BMP_2_S.png b/assets/Rat/BMP_2_S.png index b6d2dbe..2994804 100644 Binary files a/assets/Rat/BMP_2_S.png and b/assets/Rat/BMP_2_S.png differ diff --git a/assets/Rat/BMP_2_SE.png b/assets/Rat/BMP_2_SE.png index 23ef874..b29a2e8 100644 Binary files a/assets/Rat/BMP_2_SE.png and b/assets/Rat/BMP_2_SE.png differ diff --git a/assets/Rat/BMP_2_SW.png b/assets/Rat/BMP_2_SW.png index d1527fb..990508e 100644 Binary files a/assets/Rat/BMP_2_SW.png and b/assets/Rat/BMP_2_SW.png differ diff --git a/assets/Rat/BMP_2_W.png b/assets/Rat/BMP_2_W.png index af5d651..c15527f 100644 Binary files a/assets/Rat/BMP_2_W.png and b/assets/Rat/BMP_2_W.png differ diff --git a/assets/Rat/BMP_2_WN.png b/assets/Rat/BMP_2_WN.png index 2a1e098..5c43b9a 100644 Binary files a/assets/Rat/BMP_2_WN.png and b/assets/Rat/BMP_2_WN.png differ diff --git a/assets/Rat/BMP_2_WS.png b/assets/Rat/BMP_2_WS.png index c436b68..cd46ce1 100644 Binary files a/assets/Rat/BMP_2_WS.png and b/assets/Rat/BMP_2_WS.png differ diff --git a/assets/Rat/BMP_3_E.png b/assets/Rat/BMP_3_E.png index 4aed5e0..a70d235 100644 Binary files a/assets/Rat/BMP_3_E.png and b/assets/Rat/BMP_3_E.png differ diff --git a/assets/Rat/BMP_3_EN.png b/assets/Rat/BMP_3_EN.png index 8a073cb..a8a2913 100644 Binary files a/assets/Rat/BMP_3_EN.png and b/assets/Rat/BMP_3_EN.png differ diff --git a/assets/Rat/BMP_3_ES.png b/assets/Rat/BMP_3_ES.png index 6a37848..4881047 100644 Binary files a/assets/Rat/BMP_3_ES.png and b/assets/Rat/BMP_3_ES.png differ diff --git a/assets/Rat/BMP_3_N.png b/assets/Rat/BMP_3_N.png index 6a9f7c7..2bac54b 100644 Binary files a/assets/Rat/BMP_3_N.png and b/assets/Rat/BMP_3_N.png differ diff --git a/assets/Rat/BMP_3_NE.png b/assets/Rat/BMP_3_NE.png index 0b2e913..c18538c 100644 Binary files a/assets/Rat/BMP_3_NE.png and b/assets/Rat/BMP_3_NE.png differ diff --git a/assets/Rat/BMP_3_NW.png b/assets/Rat/BMP_3_NW.png index 50437b2..75a57e7 100644 Binary files a/assets/Rat/BMP_3_NW.png and b/assets/Rat/BMP_3_NW.png differ diff --git a/assets/Rat/BMP_3_S.png b/assets/Rat/BMP_3_S.png index e0079b2..7ac628d 100644 Binary files a/assets/Rat/BMP_3_S.png and b/assets/Rat/BMP_3_S.png differ diff --git a/assets/Rat/BMP_3_SE.png b/assets/Rat/BMP_3_SE.png index 7c232ea..e5e7bca 100644 Binary files a/assets/Rat/BMP_3_SE.png and b/assets/Rat/BMP_3_SE.png differ diff --git a/assets/Rat/BMP_3_SW.png b/assets/Rat/BMP_3_SW.png index 38a5718..e3434c5 100644 Binary files a/assets/Rat/BMP_3_SW.png and b/assets/Rat/BMP_3_SW.png differ diff --git a/assets/Rat/BMP_3_W.png b/assets/Rat/BMP_3_W.png index 77144cf..8648b42 100644 Binary files a/assets/Rat/BMP_3_W.png and b/assets/Rat/BMP_3_W.png differ diff --git a/assets/Rat/BMP_3_WN.png b/assets/Rat/BMP_3_WN.png index 4b938ff..18ce0ca 100644 Binary files a/assets/Rat/BMP_3_WN.png and b/assets/Rat/BMP_3_WN.png differ diff --git a/assets/Rat/BMP_3_WS.png b/assets/Rat/BMP_3_WS.png index 33d0e30..f5229e3 100644 Binary files a/assets/Rat/BMP_3_WS.png and b/assets/Rat/BMP_3_WS.png differ diff --git a/assets/Rat/BMP_4_E.png b/assets/Rat/BMP_4_E.png index 94dc069..d47f494 100644 Binary files a/assets/Rat/BMP_4_E.png and b/assets/Rat/BMP_4_E.png differ diff --git a/assets/Rat/BMP_4_EN.png b/assets/Rat/BMP_4_EN.png index 8451fa7..28bf7ba 100644 Binary files a/assets/Rat/BMP_4_EN.png and b/assets/Rat/BMP_4_EN.png differ diff --git a/assets/Rat/BMP_4_ES.png b/assets/Rat/BMP_4_ES.png index b2ad7a1..379dea9 100644 Binary files a/assets/Rat/BMP_4_ES.png and b/assets/Rat/BMP_4_ES.png differ diff --git a/assets/Rat/BMP_4_N.png b/assets/Rat/BMP_4_N.png index ca432a4..43987e1 100644 Binary files a/assets/Rat/BMP_4_N.png and b/assets/Rat/BMP_4_N.png differ diff --git a/assets/Rat/BMP_4_NE.png b/assets/Rat/BMP_4_NE.png index bdf1bdd..28df5f6 100644 Binary files a/assets/Rat/BMP_4_NE.png and b/assets/Rat/BMP_4_NE.png differ diff --git a/assets/Rat/BMP_4_NW.png b/assets/Rat/BMP_4_NW.png index 33d2de2..e17bdea 100644 Binary files a/assets/Rat/BMP_4_NW.png and b/assets/Rat/BMP_4_NW.png differ diff --git a/assets/Rat/BMP_4_S.png b/assets/Rat/BMP_4_S.png index 0d61c56..21695ab 100644 Binary files a/assets/Rat/BMP_4_S.png and b/assets/Rat/BMP_4_S.png differ diff --git a/assets/Rat/BMP_4_SE.png b/assets/Rat/BMP_4_SE.png index f2464da..fa4257c 100644 Binary files a/assets/Rat/BMP_4_SE.png and b/assets/Rat/BMP_4_SE.png differ diff --git a/assets/Rat/BMP_4_SW.png b/assets/Rat/BMP_4_SW.png index 67bcc0d..8f289ff 100644 Binary files a/assets/Rat/BMP_4_SW.png and b/assets/Rat/BMP_4_SW.png differ diff --git a/assets/Rat/BMP_4_W.png b/assets/Rat/BMP_4_W.png index ea529f9..7db9fe0 100644 Binary files a/assets/Rat/BMP_4_W.png and b/assets/Rat/BMP_4_W.png differ diff --git a/assets/Rat/BMP_4_WN.png b/assets/Rat/BMP_4_WN.png index 69b833e..f1a90f3 100644 Binary files a/assets/Rat/BMP_4_WN.png and b/assets/Rat/BMP_4_WN.png differ diff --git a/assets/Rat/BMP_4_WS.png b/assets/Rat/BMP_4_WS.png index 4e41f88..bcca247 100644 Binary files a/assets/Rat/BMP_4_WS.png and b/assets/Rat/BMP_4_WS.png differ diff --git a/tools/redraw_walls.py b/tools/redraw_walls.py new file mode 100644 index 0000000..e6160e3 --- /dev/null +++ b/tools/redraw_walls.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python3 +"""Regenerate the 32x32 wall/bush tile sprites for the maze with more detail. + +Replaces the noisy checkerboard BMP_{theme}_{N,S,E,W,NE,NW,SE,SW,EN,ES,WN,WS}.png +tiles with hand-designed pixel art bush clusters, keeping the original +palette per theme (or a richer one for theme 1). + +Each tile is 32x32. The wall/bush area is on a specific side(s) of the tile: + N = wall on the top side, passage below + S = wall on the bottom side, passage above + E = wall on the right side, passage left + W = wall on the left side, passage right + NE = wall on top + right (outer corner) + NW = wall on top + left + SE = wall on bottom + right + SW = wall on bottom + left + EN = inner corner: passage top-left, wall bottom-right (L-shape) + ES = inner corner: passage top-right, wall bottom-left + WN = inner corner: passage bottom-left, wall top-right + WS = inner corner: passage bottom-right, wall top-left + +Run from repo root: + python3 tools/redraw_walls.py +""" +import os +import sys +from pathlib import Path + +# Output directory +OUT_DIR = Path(__file__).resolve().parents[1] / "assets" / "Rat" +OUT_DIR.mkdir(parents=True, exist_ok=True) + +SIZE = 32 +TRANSPARENT = (128, 128, 128, 255) # engine treats this color as transparent + +# Palette per theme. Each theme gets a 5-color bush palette: +# (deep_shadow, dark, mid, light, highlight) +THEME_PALETTES = { + # Theme 1: lush green bushes (the canonical "hedge maze" look). + 1: { + "deep": (0, 70, 0, 255), + "dark": (0, 110, 0, 255), + "mid": (0, 160, 0, 255), + "light": (60, 220, 60, 255), + "highlight": (170, 255, 170, 255), + }, + # Theme 2: yellow/golden dry brush. + 2: { + "deep": (90, 90, 0, 255), + "dark": (130, 130, 0, 255), + "mid": (200, 200, 0, 255), + "light": (240, 240, 80, 255), + "highlight": (255, 255, 200, 255), + }, + # Theme 3: white/silver snowy brush. + 3: { + "deep": (140, 140, 140, 255), + "dark": (180, 180, 180, 255), + "mid": (220, 220, 220, 255), + "light": (245, 245, 245, 255), + "highlight": (255, 255, 255, 255), + }, + # Theme 4: ashen/red lava brush. + 4: { + "deep": (90, 0, 0, 255), + "dark": (150, 25, 25, 255), + "mid": (210, 50, 50, 255), + "light": (240, 110, 90, 255), + "highlight": (255, 200, 180, 255), + }, +} + + +# --------------------------------------------------------------------------- +# Deterministic pseudo-random helpers. +# --------------------------------------------------------------------------- +def _hash(x: int, y: int, salt: int = 0) -> int: + """Tiny integer hash in [0, 65536).""" + h = (x * 374761393 + y * 668265263 + salt * 2147483647) & 0xFFFFFFFF + h = (h ^ (h >> 13)) * 1274126177 & 0xFFFFFFFF + return h & 0xFFFF + + +def _chance(x: int, y: int, threshold: int, salt: int = 0) -> bool: + return _hash(x, y, salt) < threshold + + +def _hash_seed(x: int, y: int, salt: int) -> int: + """Return a deterministic seed in [0, 8) used to pick a cluster variant.""" + return _hash(x, y, salt) % 8 + + +# --------------------------------------------------------------------------- +# Cluster patterns: small 3x3 / 4x4 / 5x5 stamps used to add visible "leaves" +# inside the bush body. Each cluster is a small relative offset (+dx, +dy) and +# a color choice. +# --------------------------------------------------------------------------- +# A cluster is (dx, dy, color_key). The cluster is placed at integer pixel +# positions; each pixel within a small radius is set to that color (overriding +# the base body color). This adds visible "leaf clusters" on top of the body. +def _leaf_cluster_positions(cx: int, cy: int, salt: int) -> list: + """Return [(dx, dy, color_key), ...] relative to (cx, cy) for one leaf.""" + # Choose a variant: 0-7 + variant = _hash_seed(cx, cy, salt) + # Variants: + # 0: 3 leaves in a triangle + # 1: 4 leaves in a small plus + # 2: 5 leaves in a + shape + # 3: small cluster of 3 + # 4: scattered 3 + # 5: 4 leaves diagonal + # 6: single + 2 around + # 7: tight 5 cluster + clusters = { + 0: [(0, 0, "highlight"), (-2, 1, "light"), (1, 2, "light")], + 1: [(0, 0, "highlight"), (2, 0, "light"), (-2, 0, "light"), (0, 2, "light")], + 2: [(0, 0, "highlight"), (2, 0, "light"), (-2, 0, "light"), (0, 2, "light"), (0, -2, "mid")], + 3: [(0, 0, "light"), (-1, 1, "mid"), (1, -1, "mid")], + 4: [(0, 0, "highlight"), (2, 2, "light"), (-2, -1, "light")], + 5: [(0, 0, "highlight"), (2, 2, "light"), (-2, -2, "light"), (1, -1, "mid")], + 6: [(0, 0, "highlight"), (-1, 0, "light"), (0, -1, "light")], + 7: [(0, 0, "highlight"), (1, 0, "light"), (-1, 0, "light"), (0, 1, "light"), (0, -1, "light")], + } + return clusters[variant] + + +# --------------------------------------------------------------------------- +# Tile layout: where is the wall (muro di cespugli)? +# --------------------------------------------------------------------------- +def ideal_mask(shape: str) -> list: + """Return a 32x32 list-of-lists mask of the IDEAL wall footprint (True = bush).""" + mask = [[False] * SIZE for _ in range(SIZE)] + if shape == "N": + for y in range(SIZE // 2): + for x in range(SIZE): + mask[y][x] = True + elif shape == "S": + for y in range(SIZE // 2, SIZE): + for x in range(SIZE): + mask[y][x] = True + elif shape == "E": + for y in range(SIZE): + for x in range(SIZE // 2, SIZE): + mask[y][x] = True + elif shape == "W": + for y in range(SIZE): + for x in range(SIZE // 2): + mask[y][x] = True + elif shape == "NE": + for y in range(SIZE // 2): + for x in range(SIZE): + mask[y][x] = True + for y in range(SIZE): + for x in range(SIZE // 2, SIZE): + mask[y][x] = True + elif shape == "NW": + for y in range(SIZE // 2): + for x in range(SIZE): + mask[y][x] = True + for y in range(SIZE): + for x in range(SIZE // 2): + mask[y][x] = True + elif shape == "SE": + for y in range(SIZE // 2, SIZE): + for x in range(SIZE): + mask[y][x] = True + for y in range(SIZE): + for x in range(SIZE // 2, SIZE): + mask[y][x] = True + elif shape == "SW": + for y in range(SIZE // 2, SIZE): + for x in range(SIZE): + mask[y][x] = True + for y in range(SIZE): + for x in range(SIZE // 2): + mask[y][x] = True + elif shape == "EN": + for y in range(SIZE // 2, SIZE): + for x in range(SIZE // 2, SIZE): + mask[y][x] = True + for x in range(SIZE // 2, SIZE): + mask[SIZE // 2][x] = True + for y in range(SIZE // 2, SIZE): + mask[y][SIZE // 2] = True + elif shape == "ES": + for y in range(SIZE // 2, SIZE): + for x in range(SIZE // 2): + mask[y][x] = True + for x in range(SIZE // 2): + mask[SIZE // 2][x] = True + for y in range(SIZE // 2, SIZE): + mask[y][SIZE // 2 - 1] = True + elif shape == "WN": + for y in range(SIZE // 2): + for x in range(SIZE // 2, SIZE): + mask[y][x] = True + for x in range(SIZE // 2, SIZE): + mask[SIZE // 2 - 1][x] = True + for y in range(SIZE // 2): + mask[y][SIZE // 2] = True + elif shape == "WS": + for y in range(SIZE // 2): + for x in range(SIZE // 2): + mask[y][x] = True + for x in range(SIZE // 2): + mask[SIZE // 2 - 1][x] = True + for y in range(SIZE // 2): + mask[y][SIZE // 2 - 1] = True + else: + raise ValueError(f"Unknown shape: {shape}") + return mask + + +def erode_boundary(mask: list, salt: int) -> list: + """Make the wall/passage boundary irregular and natural.""" + new_mask = [row[:] for row in mask] + for y in range(SIZE): + for x in range(SIZE): + is_wall = mask[y][x] + diffs = 0 + for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + ny, nx = y + dy, x + dx + if not (0 <= ny < SIZE and 0 <= nx < SIZE) or mask[ny][nx] != is_wall: + diffs += 1 + if diffs == 0: + continue + if is_wall: + # carve a notch out of the bush (more aggressive) + if _chance(x, y, 7500, salt): + new_mask[y][x] = False + else: + # let a leaf cluster stick out into the passage + if _chance(x, y, 5500, salt + 1): + near = False + for dy in range(-2, 3): + for dx in range(-2, 3): + ny, nx = y + dy, x + dx + if 0 <= ny < SIZE and 0 <= nx < SIZE and mask[ny][nx]: + near = True + break + if near: + break + if near: + new_mask[y][x] = True + return new_mask + + +def compute_depth(mask: list) -> list: + """For each wall pixel, return its distance from the wall/passage boundary. + + Used to determine which shade of green to use (deeper = darker). + """ + depth = [[0] * SIZE for _ in range(SIZE)] + # Boundary pixels have depth 1; interior grows with distance from boundary. + for y in range(SIZE): + for x in range(SIZE): + if not mask[y][x]: + continue + # find nearest passage pixel via BFS-lite (4-direction) + # Use a simple expansion: search outward until we find a False. + d = 1 + found = False + while d < SIZE: + ring = False + for dy in range(-d, d + 1): + for dx in range(-d, d + 1): + if abs(dy) != d and abs(dx) != d: + continue + ny, nx = y + dy, x + dx + if not (0 <= ny < SIZE and 0 <= nx < SIZE): + ring = True + break + if not mask[ny][nx]: + ring = True + break + if ring: + break + if ring: + found = True + break + d += 1 + depth[y][x] = d if found else SIZE + return depth + + +def fill_bush_detail(mask: list, depth: list, pal: dict, salt: int) -> list: + """Assign palette colors to wall pixels, with leaf clusters overlaid.""" + pixels = [[None] * SIZE for _ in range(SIZE)] + # 1) base layer: pick color by depth. + for y in range(SIZE): + for x in range(SIZE): + if not mask[y][x]: + pixels[y][x] = TRANSPARENT + continue + d = depth[y][x] + if d <= 1: + pixels[y][x] = pal["dark"] + elif d == 2: + pixels[y][x] = pal["dark"] + elif d == 3: + pixels[y][x] = pal["mid"] + elif d == 4: + pixels[y][x] = pal["mid"] + else: + pixels[y][x] = pal["deep"] + # 2) scattered mid/light/highlight "speckles" to break uniformity. + for y in range(SIZE): + for x in range(SIZE): + if not mask[y][x]: + continue + d = depth[y][x] + # A light speckle anywhere on the bush + if d <= 4 and _chance(x, y, 4500, salt + 11): + pixels[y][x] = pal["light"] + # Highlight speckle near the edge only + if d <= 2 and _chance(x, y, 3000, salt + 23): + pixels[y][x] = pal["highlight"] + # 3) Overlay leaf clusters at deterministic anchor points. + # Anchors are picked from a low-density grid so each tile gets a few clusters. + anchor_spacing = 6 + for ay in range(2, SIZE - 2, anchor_spacing): + for ax in range(2, SIZE - 2, anchor_spacing): + if not mask[ay][ax]: + continue + # Some anchors are skipped for variety + if not _chance(ax, ay, 22000, salt + 31): + continue + leaves = _leaf_cluster_positions(ax, ay, salt + 41) + for dx, dy, color_key in leaves: + nx, ny = ax + dx, ay + dy + if 0 <= nx < SIZE and 0 <= ny < SIZE and mask[ny][nx]: + pixels[ny][nx] = pal[color_key] + # 4) Soft "rim" highlight along the wall/passage boundary on the + # passage-facing side (depth=1) — adds a sense of light catching the + # outer leaves. + for y in range(SIZE): + for x in range(SIZE): + if not mask[y][x]: + continue + if depth[y][x] == 1 and _chance(x, y, 5000, salt + 53): + pixels[y][x] = pal["light"] + return pixels + + +def render_tile(shape: str, theme_index: int) -> list: + pal = THEME_PALETTES[theme_index] + salt = (theme_index * 1000) + sum(ord(c) for c in shape) + base = ideal_mask(shape) + eroded = erode_boundary(base, salt) + depth = compute_depth(eroded) + pixels = fill_bush_detail(eroded, depth, pal, salt + 99) + return pixels + + +# --------------------------------------------------------------------------- +# PNG writer (PIL mode P matching original BMP_{theme}_{shape}.png format). +# --------------------------------------------------------------------------- +def save_png_mode_p(pixels: list, path: Path) -> None: + from PIL import Image + # Build a unique palette + used = {} + # Reserve index 0 for the transparent gray (engine convention) + used[TRANSPARENT] = 0 + for y in range(SIZE): + for x in range(SIZE): + c = pixels[y][x] + if c not in used: + used[c] = len(used) + palette = [0] * (256 * 3) + for color, idx in used.items(): + palette[idx * 3] = color[0] + palette[idx * 3 + 1] = color[1] + palette[idx * 3 + 2] = color[2] + pal_img = Image.new("P", (SIZE, SIZE)) + pal_img.putpalette(palette) + for y in range(SIZE): + for x in range(SIZE): + pal_img.putpixel((x, y), used[pixels[y][x]]) + pal_img.save(path, optimize=True) + + +SHAPES = ["N", "S", "E", "W", + "NE", "NW", "SE", "SW", + "EN", "ES", "WN", "WS"] + + +def main(): + written = [] + for theme_index in range(1, 5): + for shape in SHAPES: + pixels = render_tile(shape, theme_index) + path = OUT_DIR / f"BMP_{theme_index}_{shape}.png" + save_png_mode_p(pixels, path) + written.append(str(path.relative_to(OUT_DIR.parents[1]))) + print(f"Wrote {len(written)} tile sprites.") + for w in written: + print(f" {w}") + + +if __name__ == "__main__": + main() \ No newline at end of file