/machines/1RB---_1LC1RE_1LD0LC_1LE1LC_1RF0RE_1RA0RB/phaseview.py · raw
"""Phase view for 1RB---_1LC1RE_1LD0LC_1LE1LC_1RF0RE_1RA0RB (machine #14, observe stage).

Rows come from tools/diagram.py (rows_events: records / every-K / state rows). Each cell is coloured by the
period-5 pattern it sits in: dark form (11000, written by C sweeping left) or light form (11110, written by B
sweeping right), hue = dark-equivalent phase (0-4), so a wall (phase step) is a hue boundary. Period-4 right
region (0001 / 1111) is grey. Anything else: black 0 / white 1. Head = red.

usage: python3 phaseview.py OUT.png (records[:L|R] | every:K | state:X) STEPS [--from @cfg --pos P --step S]
       [--frame left|abs] [--max-rows N] [--scale K] [--solid 1] [--head 0]

--solid 1: one flat colour per phase (dark and light forms alike), period-4 zone flat grey. This hides the
           half-converted blocks of the moment, so sampled rows (every:K) show only where the walls are.
--head 0:  do not draw the head.
"""
import re
import sys

sys.path.insert(0, "[local working file]")
import diagram as dg  # noqa: E402
import sim  # noqa: E402

TM = "1RB---_1LC1RE_1LD0LC_1LE1LC_1RF0RE_1RA0RB"
HUES = [(230, 60, 60), (240, 170, 30), (60, 170, 70), (40, 120, 230), (160, 70, 200)]


def classify(cells: bytes, lo: int):
    """Per cell: (kind, phase) with kind 'D' dark, 'L' light, '4' period-4, None unclassified."""
    t = "".join("1" if c else "0" for c in cells)
    n = len(t)
    out = [None] * n
    for kind, word in (("D", "11000"), ("L", "11110"), ("4", "0001"), ("4", "1111")):
        P = len(word)
        for ph in range(P):
            tile = "".join(word[(i + lo - ph) % P] for i in range(n))
            eq = "".join("1" if a == b else "0" for a, b in zip(t, tile))
            for mt in re.finditer(r"1{10,}", eq):
                for i in range(mt.start(), mt.end()):
                    if out[i] is None:
                        dph = ph if kind == "D" else (ph - 2) % 5 if kind == "L" else 0
                        out[i] = (kind, dph)
    return out


SOLID = False


def colour(cls, sym):
    if SOLID and cls is not None:
        kind, ph = cls
        return (128, 128, 128) if kind == "4" else HUES[ph]
    if cls is None:
        return (255, 255, 255) if sym else (0, 0, 0)
    kind, ph = cls
    if kind == "4":
        return (150, 150, 150) if sym else (90, 90, 90)
    r, g, b = HUES[ph]
    if kind == "D":
        return (r * 55 // 100, g * 55 // 100, b * 55 // 100)
    return (min(255, r + 60), min(255, g + 60), min(255, b + 60))


def main(argv):
    out, mode, steps = argv[0], argv[1], int(float(argv[2]))
    opts = dict(zip(argv[3::2], argv[4::2]))
    global SOLID
    SOLID = opts.get("--solid", "0") == "1"
    draw_head = opts.get("--head", "1") == "1"
    cfg = None
    if "--from" in opts:
        src = opts["--from"]
        text = open(src[1:]).read() if src.startswith("@") else src
        cfg = sim.Config.parse(text, pos=int(opts.get("--pos", 0)), step=int(opts.get("--step", 0)))
    kw = {}
    if mode.startswith("records"):
        kw["records"] = mode.split(":")[1] if ":" in mode else "LR"
    elif mode.startswith("every:"):
        kw["every"] = int(float(mode.split(":")[1]))
    elif mode.startswith("state:"):
        kw["state"] = mode.split(":", 1)[1]
    rows, info = dg.rows_events(TM, cfg, steps, max_rows=int(opts.get("--max-rows", 5000)), **kw)
    frame = opts.get("--frame", "abs")
    lo = min(r.lo for r in rows)
    hi = max(r.hi for r in rows)
    if frame == "left":
        width = max(r.hi - r.lo for r in rows) + 1
    else:
        width = hi - lo + 1
    data = bytearray()
    for r in rows:
        cls = classify(r.cells, r.clo)
        line = bytearray(b"\x10\x10\x10" * width)
        base = r.lo if frame == "left" else lo
        for i, c in enumerate(r.cells):
            x = r.clo + i - base
            if 0 <= x < width:
                line[3 * x:3 * x + 3] = bytes(colour(cls[i], c))
        x = r.pos - base
        if draw_head and 0 <= x < width:
            line[3 * x:3 * x + 3] = b"\xff\x00\x00"
        data += line
    k = int(opts.get("--scale", 1))
    h = len(rows)
    if k > 1:
        big = bytearray()
        for y in range(h):
            row = data[3 * width * y:3 * width * (y + 1)]
            wide = bytearray()
            for x in range(width):
                wide += row[3 * x:3 * x + 3] * k
            big += wide * k
        data, width, h = big, width * k, h * k
    open(out, "wb").write(dg.encode_png(width, h, bytes(data), 3))
    print({"out": out, "rows": len(rows), "width": width, "lo": lo, "hi": hi, "frame": frame,
           "first_step": rows[0].step, "last_step": rows[-1].step})


if __name__ == "__main__":
    main(sys.argv[1:])