Implement 32x32 BFS visitor navigation and daily contracts system with companion .npc journals; add native assembly scenes for Clover slots, Pyramid cards, Blackjack Party and Russian Party; package animation and sprite packs; add comprehensive offline test suites and rebuild shipping binaries.
77 lines
3.8 KiB
Python
77 lines
3.8 KiB
Python
"""Inspect snapshots from the real MASM reel renderer, not a Python simulation."""
|
||
import hashlib
|
||
import struct
|
||
from pathlib import Path
|
||
|
||
from PIL import Image, ImageChops
|
||
from check_coin_detail import font, text_matches
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
OUTPUT = ROOT / "build/actions"
|
||
NAMES = ["slots-ready", "slots-600", "slots-1799", "slots-1800",
|
||
"slots-2400", "slots-win", "slots-paytable", "slots-exit"]
|
||
|
||
|
||
def main():
|
||
packs = ROOT / "assets/animations/slots"
|
||
raw = (packs / "cabinet-v1.bin").read_bytes()
|
||
assert struct.unpack_from("<4s4I", raw) == (b"ANM1", 640, 480, 1, 640 * 480 * 4)
|
||
cabinet = Image.frombytes("RGBA", (640, 480), raw[20:], "raw", "BGRA").convert("RGB")
|
||
assert set(raw[23::4]) == {255}, "Fast cabinet copy requires fully opaque pixels"
|
||
symbols = (packs / "symbols-v1.bin").read_bytes()
|
||
assert struct.unpack_from("<4s4I", symbols) == (b"ANM1", 64, 64, 8, 64 * 64 * 4 * 8)
|
||
assert len(symbols) == 20 + 64 * 64 * 4 * 8
|
||
assert set(symbols[23::4]) == {0, 255}
|
||
stride = 64 * 64 * 4
|
||
assert len({hashlib.sha256(symbols[20 + index * stride:20 + (index + 1) * stride]).digest()
|
||
for index in range(8)}) == 8
|
||
frames = {}
|
||
for name in NAMES:
|
||
frame = Image.open(OUTPUT / (name + ".bmp")).convert("RGB")
|
||
assert frame.size == (640, 480)
|
||
assert len(frame.getcolors(640 * 480)) > 2000, name
|
||
frames[name] = frame
|
||
frame.save(OUTPUT / (name + ".png"))
|
||
assert len({hashlib.sha256(frame.tobytes()).digest() for frame in frames.values()}) == len(NAMES)
|
||
# No symbol may cover the reel separators, chrome frame or marquee.
|
||
for name in NAMES:
|
||
if name == "slots-paytable":
|
||
continue
|
||
difference = ImageChops.difference(frames[name], cabinet)
|
||
for y in range(100, 272):
|
||
if 188 <= y < 194:
|
||
continue # Payline markers and win illumination are intentional.
|
||
for x in range(170, 477):
|
||
in_reel = 124 <= y < 260 and any(left <= x < left + 88 for left in (180, 278, 376))
|
||
if not in_reel:
|
||
assert difference.getpixel((x, y)) == (0, 0, 0), (name, x, y)
|
||
first = (180, 124, 268, 260)
|
||
second = (278, 124, 366, 260)
|
||
third = (376, 124, 464, 260)
|
||
assert frames["slots-1800"].crop(first).tobytes() == frames["slots-2400"].crop(first).tobytes()
|
||
# The gold winning line is the only permitted difference on a stopped drum.
|
||
for rect in (first, second):
|
||
left, top, right, bottom = rect
|
||
for box in ((left, top, right, 188), (left, 194, right, bottom)):
|
||
assert frames["slots-2400"].crop(box).tobytes() == frames["slots-win"].crop(box).tobytes()
|
||
assert frames["slots-2400"].crop(third).tobytes() != frames["slots-win"].crop(third).tobytes()
|
||
contact = Image.new("RGB", (640 * 4, 480 * 2))
|
||
for index, name in enumerate(NAMES):
|
||
contact.paste(frames[name], ((index % 4) * 640, (index // 4) * 480))
|
||
contact.save(OUTPUT / "slots-contact.png")
|
||
glyphs = font()
|
||
for name, gross, net, status, ink in (
|
||
("slots-refund", "100", "0", "ВОЗВРАТ", (208, 212, 208)),
|
||
("slots-net-win", "200", "+100", "ВЫПЛАТА", (168, 221, 185)),
|
||
("slots-loss", "0", "-100", "ПРОИГРЫШ", (238, 153, 136))):
|
||
frame = Image.open(OUTPUT / (name + ".bmp")).convert("RGB")
|
||
text_matches(frame, gross, 384, 402, 2, (244, 243, 232), glyphs)
|
||
text_matches(frame, net, 384, 422, 2, ink, glyphs)
|
||
text_matches(frame, status, 208, 298, 2, (232, 215, 173), glyphs)
|
||
frame.save(OUTPUT / (name + ".png"))
|
||
print("PASS: 8 distinct native slot frames; pixel-accurate reel clipping; sequential stops; opaque cabinet and 8 real symbol sprites")
|
||
print("PASS: exact native gross/net/refund/loss Russian glyphs; refund is not a net win")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|