KAZIK/tools/generators/pack_intro_sequence.py
Artvasil09 3ad5bf4c9d Develop assembly-only casino scenes and native games
Add native Durak, poker, blackjack, roulette, Buckshot and billiards scenes with animation assets and regression tests. Improve arrival, city, hall, profiles, shops, audio and exit handling; remove the portable C game core and refresh shipping executables.
2026-10-08 22:39:19 +03:00

128 lines
5 KiB
Python

"""Compile the original ANSI animation into compact native glyph/color frames."""
import json
import re
import struct
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
COLS, ROWS = 72, 21
CELL_W, CELL_H = 8, 16
ESCAPE = re.compile(r"\x1b\[([0-9;?]*)([A-Za-z])")
def palette():
colors = [0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xC0C0C0,
0x808080, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF]
levels = [0, 95, 135, 175, 215, 255]
colors += [(r << 16) | (g << 8) | b for r in levels for g in levels for b in levels]
colors += [((8 + i * 10) * 0x010101) for i in range(24)]
return colors
def parse_frame(text):
cells = [[(" ", 15) for _ in range(COLS)] for _ in range(ROWS)]
x = y = 0
color = 15
offset = 0
while offset < len(text):
match = ESCAPE.match(text, offset)
if match:
parameters, command = match.groups()
if command == "m":
values = [int(v) for v in parameters.split(";") if v]
if len(values) == 3 and values[:2] == [38, 5]:
color = values[2]
elif values == [0]:
color = 15
else:
raise ValueError(f"Unsupported foreground escape {match.group()!r}")
elif command == "H":
x = y = 0
elif command == "J" and parameters == "2":
cells = [[(" ", 15) for _ in range(COLS)] for _ in range(ROWS)]
elif parameters == "?25" and command in ("h", "l"):
pass
else:
raise ValueError(f"Unsupported escape {match.group()!r}")
offset = match.end()
continue
char = text[offset]
offset += 1
if char == "\r":
x = 0
elif char == "\n":
x = 0
y += 1
else:
if char != " " and (x >= COLS or y >= ROWS):
raise ValueError(f"Visible cell outside frame: ({x}, {y})")
if x < COLS and y < ROWS:
cells[y][x] = (char, color)
x += 1
return [cell for row in cells for cell in row]
def glyph(char, font):
image = Image.new("L", (CELL_W, CELL_H))
draw = ImageDraw.Draw(image)
# Box/block glyphs fill their cells, so the old logo remains continuous.
if char == "\u2588":
draw.rectangle((0, 0, 7, 15), fill=255)
elif char in "\u2550\u2551\u2554\u2557\u255a\u255d":
if char == "\u2550":
draw.line((0, 6, 7, 6), fill=255)
draw.line((0, 9, 7, 9), fill=255)
elif char == "\u2551":
draw.line((2, 0, 2, 15), fill=255)
draw.line((5, 0, 5, 15), fill=255)
else:
left = char in "\u2554\u255a"
top = char in "\u2554\u2557"
edge_x, end_y = (7 if left else 0), (15 if top else 0)
for x, y in [(2, 6), (5, 9)]:
draw.line((x, y, x, end_y), fill=255)
draw.line((x, y, edge_x, y), fill=255)
elif char != " ":
draw.text((0, 0), char, font=font, fill=255)
return bytes(sum((1 << (7 - x)) for x in range(CELL_W) if image.getpixel((x, y)) >= 96)
for y in range(CELL_H))
def main():
root = Path(__file__).resolve().parents[2]
source = root / "assets/animations/casino_intro.ans"
raw = source.read_bytes()
text = raw.decode("utf-8")
parts = text.replace("\r\n", "\n").split("\f")
frames, loop_start = [], None
for part in parts:
if part.strip() == "@@LOOP@@":
loop_start = len(frames)
elif part:
frames.append(parse_frame(part))
if loop_start is None or not 0 <= loop_start < len(frames):
raise ValueError("Missing loop boundary")
characters = [" "] + sorted({char for frame in frames for char, _ in frame} - {" "})
if len(characters) > 256:
raise ValueError("Too many glyphs")
indexes = {char: i for i, char in enumerate(characters)}
font = ImageFont.truetype("C:/Windows/Fonts/consola.ttf", 13)
masks = b"".join(glyph(char, font) for char in characters).ljust(4096, b"\0")
colors = palette()
payload = struct.pack("<256I", *colors) + masks
payload += b"".join(struct.pack("<H", indexes[char] | color << 8)
for frame in frames for char, color in frame)
output = root / "assets/animations/intro/eblan-sequence.bin"
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(struct.pack("<4s8I", b"EBGA", COLS, ROWS, len(frames), loop_start,
45, len(characters), len(payload), 1) + payload)
manifest = {"source": str(source.relative_to(root)), "frames": len(frames),
"loop_start": loop_start, "frame_ms": 45, "glyphs": len(characters),
"bytes": output.stat().st_size, "encoding": "UTF-8 original / native binary runtime"}
(output.with_suffix(".json")).write_text(json.dumps(manifest, indent=2), encoding="utf-8")
print(json.dumps(manifest))
if __name__ == "__main__":
main()