diff --git a/assets/Rat/BMP_1_E.png b/assets/Rat/BMP_1_E.png index 4f82a72..cb995c9 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 e4d5d35..6f8e7bf 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 2970915..9986257 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 a59619d..8e601e8 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 b7f2175..00d7193 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 ce5d17a..92b6a64 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 eae5daa..0a86857 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 2fa5a8b..71b03ab 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 66663bb..941a2b1 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 b36ea24..c0b8372 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 e4e90be..d5c7822 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 025a914..7a7614a 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 6bd1568..95246fb 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 431b790..cb263cb 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 85dea39..65de7b0 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 ef79e54..1257507 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 594b78a..773a8d5 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 c8754ad..6f02b0a 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 2994804..b6d2dbe 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 b29a2e8..23ef874 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 990508e..d1527fb 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 c15527f..af5d651 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 5c43b9a..2a1e098 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 cd46ce1..c436b68 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 a70d235..4aed5e0 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 a8a2913..8a073cb 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 4881047..6a37848 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 2bac54b..6a9f7c7 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 c18538c..0b2e913 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 75a57e7..50437b2 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 7ac628d..e0079b2 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 e5e7bca..7c232ea 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 e3434c5..38a5718 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 8648b42..77144cf 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 18ce0ca..4b938ff 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 f5229e3..33d0e30 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 d47f494..94dc069 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 28bf7ba..8451fa7 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 379dea9..b2ad7a1 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 43987e1..ca432a4 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 28df5f6..bdf1bdd 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 e17bdea..33d2de2 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 21695ab..0d61c56 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 fa4257c..f2464da 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 8f289ff..67bcc0d 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 7db9fe0..ea529f9 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 f1a90f3..69b833e 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 bcca247..4e41f88 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 deleted file mode 100644 index e6160e3..0000000 --- a/tools/redraw_walls.py +++ /dev/null @@ -1,401 +0,0 @@ -#!/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