nomeansnoquest/tools/gen_sprites.py
m3ultra 50817ced59 NOMEANSNO QUEST v1.0 — the first MRPCI game
Six rooms of band history: the 1979 basement (Portastudio, Mom, the Castle box), D.O.A. at UVic, the alley wall that named the band, Alternative Tentacles 1987 (Jello signs you), the Roskilde main stage 1994 (MODELBEAST flux matte painting quantized by the engine; ops paint only walkability + cycling footlights), and the Polish field 1997 (the $2,500 van ransom — with one fatal dialogue option to prove checkpoint mercy). 60 points, deterministic playthrough in tests/, zero engine forks: pure game data + rhai. Generated art via tools/gen_sprites.py + gen_rooms.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:49:05 +10:00

587 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
"""Generate NOMEANSNO QUEST sprites as PNGs — stdlib only (zlib + struct).
Sprites are ASCII grids: each char maps to an RGB in the sprite's legend,
'.' is transparent. MRPCI quantizes them per-room, so colors here are
intent, not law. Run from the repo root: python3 tools/gen_sprites.py
"""
import os
import struct
import zlib
OUT = os.path.join(os.path.dirname(__file__), "..", "sprites")
def write_png(path, rows, legend, scale=1):
rows = [r for r in rows for _ in range(scale)]
h = len(rows)
w = max(len(r) for r in rows) * scale
raw = b""
for y in range(h):
raw += b"\x00" # filter: none
row = rows[y].ljust(w // scale, ".")
for ch in row:
px = b"\x00\x00\x00\x00" if ch == "." else struct.pack("BBBB", *legend[ch], 255)
raw += px * scale
def chunk(tag, data):
c = struct.pack(">I", len(data)) + tag + data
return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
png = b"\x89PNG\r\n\x1a\n"
png += chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0))
png += chunk(b"IDAT", zlib.compress(raw, 9))
png += chunk(b"IEND", b"")
with open(path, "wb") as f:
f.write(png)
print(f" {os.path.basename(path)} {w}x{h}")
S = {} # name -> (rows, legend)
# --- people -------------------------------------------------------------
# Rob Wright — the ego. Tall, calm, bass slung low. (Feet = bottom center.)
S["rob"] = (
[
"....hhhh....",
"....hhhh....",
"....ffff....",
"....f2f2....",
"....ffff....",
".....ff.....",
"...ssssss...",
"..ssssssss..",
"..s.ssss.s..",
"..s.ssss.s..",
"..b.ssss....",
".bbbrssss...",
".bb.ssss....",
"..b.pppp....",
"....pppp....",
"....p..p....",
"....p..p....",
"....p..p....",
"....p..p....",
"...oo..oo...",
],
{
"h": (72, 52, 36), # hair
"f": (214, 178, 148),# face
"2": (40, 40, 48), # eyes
"s": (52, 56, 64), # dark shirt
"p": (44, 48, 88), # jeans
"o": (30, 30, 34), # boots
"b": (150, 60, 40), # bass body
"r": (220, 200, 160),# bass neck
},
)
# John Wright — behind the kit or beside it; sticks in hand.
S["john"] = (
[
"...hhhhh...",
"...hhhhh...",
"...ffff....",
"...f2f2....",
"...ffff....",
"....ff.....",
"..tttttt...",
".tttttttt..",
".t.tttt.t..",
".w.tttt.w..",
"...tttt....",
"...pppp....",
"...p..p....",
"...p..p....",
"...p..p....",
"..oo..oo...",
],
{
"h": (60, 44, 30),
"f": (216, 182, 152),
"2": (40, 40, 48),
"t": (140, 40, 44), # red tee
"w": (222, 214, 180),# drumsticks
"p": (40, 42, 50),
"o": (30, 30, 34),
},
)
S["mom"] = (
[
"...hhhhh...",
"..hhhhhhh..",
"...ffff....",
"...f2f2....",
"...ffff....",
"....ff.....",
"..dddddd...",
".dddddddd..",
".d.aaaa.d..",
"...aaaa....",
"...aaaa....",
"...dddd....",
"...d..d....",
"..oo..oo...",
],
{
"h": (168, 160, 150),
"f": (216, 184, 156),
"2": (50, 44, 40),
"d": (90, 110, 140), # housedress
"a": (228, 226, 214),# apron
"o": (60, 50, 44),
},
)
# Joey Shithead of D.O.A., on stage. Mohawk not optional.
S["joey"] = (
[
"....kk.....",
"...kkkk....",
"....kk.....",
"...ffff....",
"...f2f2....",
"...ffff....",
"....ff.....",
"..lllllll..",
".lllllllll.",
".l.lllll.l.",
".g.lllll...",
".gg.lll....",
"...pppp....",
"...p..p....",
"...p..p....",
"..oo..oo...",
],
{
"k": (30, 160, 60), # green mohawk
"f": (210, 172, 140),
"2": (30, 30, 36),
"l": (36, 36, 40), # leather
"g": (170, 120, 60), # guitar neck
"p": (46, 46, 52),
"o": (26, 26, 30),
},
)
S["biafra"] = (
[
"...hhhhh...",
"...hhhhh...",
"...ffff....",
"...f2f2....",
"...ffff....",
"....ff.....",
"..jjjjjj...",
".jjjjjjjj..",
".j.tttt.j..",
"...tttt....",
"...tttt....",
"...pppp....",
"...p..p....",
"...p..p....",
"..oo..oo...",
],
{
"h": (50, 38, 30),
"f": (214, 178, 148),
"2": (36, 40, 48),
"j": (70, 60, 90), # thrift blazer
"t": (200, 60, 50), # loud tee
"p": (50, 50, 58),
"o": (30, 30, 34),
},
)
S["stagehand"] = (
[
"...ccccc...",
"...ffff....",
"...f2f2....",
"...ffff....",
"....ff.....",
"..vvvvvv...",
".vvvvvvvv..",
".v.vvvv.v..",
"...vvvv....",
"...vvvv....",
"...pppp....",
"...p..p....",
"...p..p....",
"..oo..oo...",
],
{
"c": (40, 40, 48), # headset cap
"f": (208, 172, 140),
"2": (36, 36, 44),
"v": (240, 180, 40), # hi-vis
"p": (44, 46, 54),
"o": (28, 28, 32),
},
)
S["mobster"] = (
[
"...bbbbb...",
"...ffff....",
"...f2f2....",
"...ffff....",
"...ffnf....",
"....ff.....",
".ttttttttt.",
".tttttttte.",
".t.tttt.te.",
".t.tttt.t..",
"...tttt....",
"...tttt....",
"...ssss....",
"...s..s....",
"...s..s....",
"..oo..oo...",
],
{
"b": (30, 30, 34), # flat cap
"f": (206, 168, 138),
"2": (30, 34, 40),
"n": (150, 110, 90), # that nose
"t": (60, 90, 60), # tracksuit
"e": (220, 200, 80), # gold stripe
"s": (54, 56, 60),
"o": (26, 26, 30),
},
)
# --- portraits (24x24, drawn 2x in core windows, 6x in the GUI) ----------
S["john-face"] = (
[
"bbbbbbbbbbbbbbbbbbbbbbbb",
"bbbbbbbbbbbbbbbbbbbbbbbb",
"bbbbbhhhhhhhhhhhhhbbbbbb",
"bbbbhhhhhhhhhhhhhhhbbbbb",
"bbbhhhhhhhhhhhhhhhhhbbbb",
"bbbhhfffffffffffffhhbbbb",
"bbbhffffffffffffffhhbbbb",
"bbbhffffffffffffffhbbbbb",
"bbbhff22ffffff22ffhbbbbb",
"bbbhff22ffffff22ffhbbbbb",
"bbbbffffffffffffffbbbbbb",
"bbbbffffffnnffffffbbbbbb",
"bbbbffffffnnffffffbbbbbb",
"bbbbffffffffffffffbbbbbb",
"bbbbfffmmmmmmmmfffbbbbbb",
"bbbbffffmmmmmmffffbbbbbb",
"bbbbbffffffffffffbbbbbbb",
"bbbbbbffffffffffbbbbbbbb",
"bbbbbbbttttttttbbbbbbbbb",
"bbbbbbttttttttttbbbbbbbb",
"bbbbbttttttttttttbbbbbbb",
"bbbbttttttttttttttbbbbbb",
"bbbbttttttttttttttbbbbbb",
"bbbbbbbbbbbbbbbbbbbbbbbb",
],
{
"b": (24, 26, 34),
"h": (60, 44, 30),
"f": (216, 182, 152),
"2": (40, 40, 48),
"n": (190, 150, 122),
"m": (150, 90, 80),
"t": (140, 40, 44),
},
)
S["mom-face"] = (
[
"bbbbbbbbbbbbbbbbbbbbbbbb",
"bbbbbhhhhhhhhhhhhhbbbbbb",
"bbbhhhhhhhhhhhhhhhhbbbbb",
"bbhhhhhhhhhhhhhhhhhhbbbb",
"bbhhhffffffffffffhhhbbbb",
"bbhhffffffffffffffhhbbbb",
"bbhhffffffffffffffhhbbbb",
"bbhhff22ffffff22ffhhbbbb",
"bbhhff22ffffff22ffhhbbbb",
"bbhhffffffffffffffhhbbbb",
"bbhhffffffnnffffffhhbbbb",
"bbhhffffffffffffffhhbbbb",
"bbhhfffmmmmmmmmfffhhbbbb",
"bbhhffffmmmmmmffffhhbbbb",
"bbbhhffffffffffffhhbbbbb",
"bbbhhhffffffffffhhhbbbbb",
"bbbbhhhhffffffhhhhbbbbbb",
"bbbbbbddddddddddbbbbbbbb",
"bbbbbddddddddddddbbbbbbb",
"bbbbddddddddddddddbbbbbb",
"bbbbddddaaaaaaddddbbbbbb",
"bbbbdddaaaaaaaadddbbbbbb",
"bbbbdddaaaaaaaadddbbbbbb",
"bbbbbbbbbbbbbbbbbbbbbbbb",
],
{
"b": (26, 26, 32),
"h": (168, 160, 150),
"f": (216, 184, 156),
"2": (50, 44, 40),
"n": (192, 154, 128),
"m": (160, 100, 90),
"d": (90, 110, 140),
"a": (228, 226, 214),
},
)
S["biafra-face"] = (
[
"bbbbbbbbbbbbbbbbbbbbbbbb",
"bbbbhhhhhhhhhhhhhhbbbbbb",
"bbbhhhhhhhhhhhhhhhhbbbbb",
"bbbhhhhhhhhhhhhhhhhbbbbb",
"bbbhhffffffffffffhhbbbbb",
"bbbhffffffffffffffhbbbbb",
"bbbhffffffffffffffhbbbbb",
"bbbhffeeffffffeeffhbbbbb",
"bbbhff22ffffff22ffhbbbbb",
"bbbhffffffffffffffhbbbbb",
"bbbhffffffnnffffffhbbbbb",
"bbbhffffffffffffffhbbbbb",
"bbbbffffffffffffffbbbbbb",
"bbbbfffmmmmmmmmfffbbbbbb",
"bbbbffmmmmmmmmmmffbbbbbb",
"bbbbbfffmmmmmmfffbbbbbbb",
"bbbbbbffffffffffbbbbbbbb",
"bbbbbbbjjjjjjjjbbbbbbbbb",
"bbbbbjjjjjjjjjjjjbbbbbbb",
"bbbbjjjjttttttjjjjbbbbbb",
"bbbbjjjttttttttjjjbbbbbb",
"bbbbjjjttttttttjjjbbbbbb",
"bbbbjjjttttttttjjjbbbbbb",
"bbbbbbbbbbbbbbbbbbbbbbbb",
],
{
"b": (28, 24, 34),
"h": (50, 38, 30),
"f": (214, 178, 148),
"2": (36, 40, 48),
"e": (90, 90, 110), # that stare, pre-twinkle
"n": (188, 148, 120),
"m": (150, 84, 76), # the grin
"j": (70, 60, 90),
"t": (200, 60, 50),
},
)
S["mobster-face"] = (
[
"bbbbbbbbbbbbbbbbbbbbbbbb",
"bbbbkkkkkkkkkkkkkkbbbbbb",
"bbbkkkkkkkkkkkkkkkkbbbbb",
"bbbkkkkkkkkkkkkkkkkbbbbb",
"bbbbffffffffffffffbbbbbb",
"bbbfffffffffffffffbbbbbb",
"bbbff2222ffff2222ffbbbbb",
"bbbff2222ffff2222ffbbbbb",
"bbbffffffffffffffffbbbbb",
"bbbfffffnnnnnnfffffbbbbb",
"bbbfffffnnnnnnfffffbbbbb",
"bbbffffffffffffffffbbbbb",
"bbbbffffffffffffffbbbbbb",
"bbbbfffmmmmmmmmfffbbbbbb",
"bbbbffffffffffffffbbbbbb",
"bbbbbffffffffffffbbbbbbb",
"bbbbbbffffffffffbbbbbbbb",
"bbbbbbbttttttttbbbbbbbbb",
"bbbbbttttttttttttbbbbbbb",
"bbbbtttteeeeeetttbbbbbbb",
"bbbbttttttttttttttbbbbbb",
"bbbbttttttttttttttbbbbbb",
"bbbbttttttttttttttbbbbbb",
"bbbbbbbbbbbbbbbbbbbbbbbb",
],
{
"b": (24, 28, 26),
"k": (30, 30, 34),
"f": (206, 168, 138),
"2": (30, 34, 40),
"n": (150, 110, 90),
"m": (120, 70, 64),
"t": (60, 90, 60),
"e": (220, 200, 80),
},
)
# --- props ----------------------------------------------------------------
S["portastudio"] = (
[
"gggggggggggggggggggggggggg",
"gssssssssssssssssssssssssg",
"gsrrrrrssgggggggssrrrrrssg",
"gsr...rssgddddggssr...rssg",
"gsr.o.rssgddddggssr.o.rssg",
"gsr...rssgggggggssr...rssg",
"gsrrrrrssssssssssssrrrrssg",
"gsssssssskkkkkkkksssssssg.",
"gsswwswwsswwsswwsswwswwsg.",
"gsswwswwsswwsswwsswwswwsg.",
"gggggggggggggggggggggggg..",
],
{
"g": (70, 72, 78),
"s": (98, 100, 108),
"r": (50, 50, 56),
"o": (200, 190, 170),
"d": (180, 60, 50), # VU meters
"k": (36, 36, 40),
"w": (210, 208, 200),# faders
},
)
S["castlebox"] = (
[
"cccccccccccccccccccccc",
"cttttttttttttttttttttc",
"ct__CASTLE__(RIP)___tc",
"cttttttttttttttttttttc",
"cccccccccccccccccccccc",
"cccccccccccccccccccccc",
"cccccccccccccccccccccc",
"cccccccccccccccccccccc",
],
{
"c": (150, 110, 66),
"t": (120, 86, 50),
"_": (120, 86, 50),
"C": (60, 44, 30),
"A": (60, 44, 30),
"S": (60, 44, 30),
"T": (60, 44, 30),
"L": (60, 44, 30),
"E": (60, 44, 30),
"R": (60, 44, 30),
"I": (60, 44, 30),
"P": (60, 44, 30),
"(": (60, 44, 30),
")": (60, 44, 30),
},
)
S["flyer"] = (
[
"wwwwwwwwww",
"wkkkkkkkkw",
"wwwwwwwwww",
"wkkkwwkkww",
"wwwwwwwwww",
"wkkkkkwwww",
"wwwwwwwwww",
],
{"w": (226, 222, 210), "k": (40, 40, 44)},
)
S["drumkit"] = (
[
"........cc..........cc........",
".........c..........c.........",
"....hhhhhhhhh..hhhhhhhhh......",
"....h.......h..h.......h......",
"....hhhhhhhhh..hhhhhhhhh......",
"..bbbbbbbbbbbbbbbbbbbbbbbb....",
".bbwwwwwwwwwwwwwwwwwwwwwwbb...",
".bbwwwwwwwwwwwwwwwwwwwwwwbb...",
".bbwwwwwwwwwwwwwwwwwwwwwwbb...",
".bbbbbbbbbbbbbbbbbbbbbbbbbb...",
"...ss...ss........ss...ss.....",
"..ss.....ss......ss.....ss....",
],
{
"c": (210, 190, 120), # cymbals
"h": (120, 60, 50), # toms
"b": (140, 40, 44), # kick shell
"w": (230, 226, 214), # kick head
"s": (140, 140, 150),
},
)
S["rig"] = (
[
"kkkkkkkkkkkkkkkkkkkkkkkkkkkk",
"kggggggggggggkkggggggggggggk",
"kg..........gkkg..........gk",
"kg.oooooooo.gkkg.oooooooo.gk",
"kg.oooooooo.gkkg.oooooooo.gk",
"kg..........gkkg..........gk",
"kkkkkkkkkkkkkkkkkkkkkkkkkkkk",
"kggggggggggggkkggggggggggggk",
"kg.oooooooo.gkkg.oooooooo.gk",
"kg.oooooooo.gkkg.oooooooo.gk",
"kg..........gkkg..........gk",
"kkkkkkkkkkkkkkkkkkkkkkkkkkkk",
"kkrrkkkkkkkkkkkkkkkkkkkkrrkk",
],
{
"k": (30, 30, 34),
"g": (56, 56, 62),
"o": (80, 76, 70), # speaker cones
"r": (200, 60, 50), # power lamps
},
)
S["van"] = (
[
"..........wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww..........",
"........wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww........",
".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......",
".......wggggggwwwwwwwwwwwwwwwwwwwwwwwwwwwggggggww.......",
".......wggggggwwwwwwwwwwwwwwwwwwwwwwwwwwwggggggww.......",
".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......",
".......wwwwkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkwwwww.......",
".......wwwwkNOMEANSNOwwwwwwwwwwwwwwwwwwwwwwkwwwww.......",
".......wwwwkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkwwwww.......",
".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......",
"........bb..tttt..bbbbbbbbbbbbbbbbbb..tttt..bb...........",
"........bb.tttttt.bbbbbbbbbbbbbbbbbb.tttttt.bb...........",
"............tttt......................tttt..............",
],
{
"w": (216, 214, 206),
"g": (110, 140, 160), # windows
"k": (60, 60, 66), # hand-painted band stripe
"N": (220, 60, 50),
"O": (220, 60, 50),
"M": (220, 60, 50),
"E": (220, 60, 50),
"A": (220, 60, 50),
"S": (220, 60, 50),
"b": (70, 70, 76),
"t": (30, 30, 34),
},
)
S["dumpster"] = (
[
"gggggggggggggggggggggggggggggggggggg",
"ggllllllllllllllllllllllllllllllllgg",
"gg..............................gg..",
"ggllllllllllllllllllllllllllllllgg..",
"gg..............................gg..",
"gggggggggggggggggggggggggggggggggg..",
"..kk..........................kk....",
"..kk..........................kk....",
],
{"g": (60, 90, 70), "l": (80, 112, 88), "k": (30, 30, 34)},
)
def main():
os.makedirs(OUT, exist_ok=True)
print("writing sprites:")
for name, (rows, legend) in S.items():
# People and props render 2x (320-wide rooms); portraits and the
# tiny flyer stay 1:1.
scale = 1 if name.endswith("-face") or name == "flyer" else 2
write_png(os.path.join(OUT, f"{name}.png"), rows, legend, scale)
if __name__ == "__main__":
main()