HaltingMachine 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- HaltingMachine.py +4 -0
- haltingmachine-0.1.dist-info/METADATA +6 -0
- haltingmachine-0.1.dist-info/RECORD +10 -0
- haltingmachine-0.1.dist-info/WHEEL +5 -0
- haltingmachine-0.1.dist-info/entry_points.txt +2 -0
- haltingmachine-0.1.dist-info/top_level.txt +2 -0
- machine/__init__.py +1 -0
- machine/field.py +75 -0
- machine/inverter.py +14 -0
- machine/observer.py +102 -0
HaltingMachine.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
HaltingMachine.py,sha256=PtDmybCXkio4mQ1MCNVIo7iU62z1qoN7Fjq8Xyoga2M,70
|
|
2
|
+
machine/__init__.py,sha256=gWfketXvBDaqOxZrk4DnoRPuCXCrdQO3NTkB8Y-netc,53
|
|
3
|
+
machine/field.py,sha256=PcvQ99mHSLnNSzlxtm4-aIBxMmvCOoaTrA335Pxs_C0,2988
|
|
4
|
+
machine/inverter.py,sha256=SUyPNGN-q-OnWRucvTqp74yIVvDOWmUTDndds6roJXU,277
|
|
5
|
+
machine/observer.py,sha256=tVvYZT8DMMccW821GEGmic5xxaR67lNg--cPDiOhe6U,4229
|
|
6
|
+
haltingmachine-0.1.dist-info/METADATA,sha256=YsblUXiZ3Ba3DLTneGokT3n2egtJ1eaFYVPu_cON2Y0,167
|
|
7
|
+
haltingmachine-0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
haltingmachine-0.1.dist-info/entry_points.txt,sha256=xFGFsuNord-JFO1IXqag_U7lVtBmc_xyg5UA3hJzrlY,55
|
|
9
|
+
haltingmachine-0.1.dist-info/top_level.txt,sha256=KptXOzAYcX_GJzLAQXUlBc4KyuhsXMKvn9ZF7cpgqWo,23
|
|
10
|
+
haltingmachine-0.1.dist-info/RECORD,,
|
machine/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HaltingMachine: observer, inverter, and field."""
|
machine/field.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import os, platform, random, shlex, shutil, signal, subprocess, sys, termios, tty
|
|
2
|
+
from wcwidth import wcswidth
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def paint():
|
|
6
|
+
sys.stdout.write("\033[40m\033[97m\033[2J\033[H\033[?25l")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def clear():
|
|
10
|
+
sys.stdout.write("\033[?25h\033[0m\033[2J\033[H"); sys.stdout.flush()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def center(text):
|
|
14
|
+
return " " * max(0, (os.get_terminal_size().columns - wcswidth(text)) // 2) + text
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def choose(label, lo, hi, value):
|
|
18
|
+
old = termios.tcgetattr(sys.stdin); tty.setcbreak(sys.stdin.fileno())
|
|
19
|
+
try:
|
|
20
|
+
while True:
|
|
21
|
+
paint(); print("\n\n" + center("HaltingMachine"), "\n", center(label), "", center(str(value)), sep="\n", flush=True)
|
|
22
|
+
key = os.read(sys.stdin.fileno(), 3)
|
|
23
|
+
if key in (b"\r", b"\n"): return value
|
|
24
|
+
if key == b"\x1b[A": value = min(hi, value + 1)
|
|
25
|
+
elif key == b"\x1b[B": value = max(lo, value - 1)
|
|
26
|
+
finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def terminal_command(cmd):
|
|
30
|
+
names = ("kitty", "konsole", "gnome-terminal", "xfce4-terminal", "alacritty")
|
|
31
|
+
terminal = next((shutil.which(x) for x in names if shutil.which(x)), None)
|
|
32
|
+
terminal = terminal or shutil.which(os.environ.get("TERMINAL", ""))
|
|
33
|
+
terminal = terminal or shutil.which("x-terminal-emulator") or shutil.which("xterm")
|
|
34
|
+
if not terminal: raise SystemExit("No terminal emulator found")
|
|
35
|
+
name = os.path.basename(os.path.realpath(terminal))
|
|
36
|
+
if name == "gnome-terminal": return [terminal, "--", *cmd]
|
|
37
|
+
if name == "xfce4-terminal": return [terminal, "--command", shlex.join(cmd)]
|
|
38
|
+
if name == "alacritty": return [terminal, "-T", "HaltingMachine", "-e", *cmd]
|
|
39
|
+
if name == "kitty": return [terminal, "--title", "HaltingMachine", *cmd]
|
|
40
|
+
return [terminal, "-e", *cmd]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def spawn(module, index, total):
|
|
44
|
+
cmd = [sys.executable, "-m", module, str(index), str(total)]
|
|
45
|
+
env = os.environ.copy(); env["PYTHONIOENCODING"] = "utf-8"
|
|
46
|
+
if platform.system() == "Darwin":
|
|
47
|
+
line = shlex.join(cmd).replace('"', '\\"')
|
|
48
|
+
subprocess.Popen(["osascript", "-e", f'tell application "Terminal" to do script "{line}"'])
|
|
49
|
+
else: subprocess.Popen(terminal_command(cmd), env=env)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
old_handlers = {s: signal.getsignal(s) for s in (signal.SIGTSTP, signal.SIGQUIT)}
|
|
54
|
+
for s in old_handlers: signal.signal(s, signal.SIG_IGN)
|
|
55
|
+
try:
|
|
56
|
+
total = choose("OBSERVERS", 1, 10, 5)
|
|
57
|
+
inverted = choose("INVERTERS", 0, total, min(1, total))
|
|
58
|
+
first = total - inverted
|
|
59
|
+
index = random.randrange(total)
|
|
60
|
+
for i in range(total):
|
|
61
|
+
if i != index:
|
|
62
|
+
spawn("machine.inverter" if i >= first else "machine.observer", i, total)
|
|
63
|
+
if index >= first:
|
|
64
|
+
from .inverter import run
|
|
65
|
+
else:
|
|
66
|
+
from .observer import run
|
|
67
|
+
run(index, total)
|
|
68
|
+
except (KeyboardInterrupt, EOFError):
|
|
69
|
+
pass
|
|
70
|
+
finally:
|
|
71
|
+
for s, h in old_handlers.items(): signal.signal(s, h)
|
|
72
|
+
clear()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__": main()
|
machine/inverter.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from .observer import REVERSE, run as observe
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def run(index, total):
|
|
6
|
+
observe(index, total, step=REVERSE, invert_controls=True, invert_mode=True)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
index, total = map(int, sys.argv[1:3])
|
|
11
|
+
run(index, total)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__": main()
|
machine/observer.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os, select, signal, socket, sys, termios, time, tty
|
|
2
|
+
from wcwidth import wcswidth
|
|
3
|
+
|
|
4
|
+
ROCK, PAPER, SCISSORS = "🪨", "📜", "✂️"
|
|
5
|
+
FORWARD = {ROCK: PAPER, PAPER: SCISSORS, SCISSORS: ROCK}
|
|
6
|
+
REVERSE = {v: k for k, v in FORWARD.items()}
|
|
7
|
+
ART = {ROCK: ROCK * 4, PAPER: PAPER * 4, SCISSORS: "✂️ " * 4}
|
|
8
|
+
GREEK = ("Γ", "Δ", "Ε", "Θ", "Λ", "Π", "Σ", "Φ", "Ψ", "Ω")
|
|
9
|
+
BASE = 43120
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def paint():
|
|
13
|
+
sys.stdout.write("\033[40m\033[97m\033[2J\033[H\033[?25l")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def clear():
|
|
17
|
+
sys.stdout.write("\033[?25h\033[0m\033[2J\033[H"); sys.stdout.flush()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def center(text, width=None):
|
|
21
|
+
width = wcswidth(text) if width is None else width
|
|
22
|
+
return " " * max(0, (os.get_terminal_size().columns - width) // 2) + text
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def draw(identity, mode, state, inverted=False, genesis=None):
|
|
26
|
+
paint(); print("\n\n" + center(f"{identity} HaltingMachine {identity}"), "\n")
|
|
27
|
+
if genesis is not None:
|
|
28
|
+
print(center("GENESIS"), "\n", center(genesis), flush=True); return
|
|
29
|
+
face = "🙃" if inverted else "🙂"
|
|
30
|
+
halt = f"{face}HALT{face}" if mode == 0 else "HALT"
|
|
31
|
+
repeat = f"{face}REPEAT{face}" if mode else "REPEAT"
|
|
32
|
+
print(center(halt), center(repeat), "", center(ART[state], 8), "", center("SPACE TO EXIT"), sep="\n", flush=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def send(sock, total, payload, skip):
|
|
36
|
+
data = payload.encode("utf-8")
|
|
37
|
+
for i in range(total):
|
|
38
|
+
if i != skip:
|
|
39
|
+
try: sock.sendto(data, ("127.0.0.1", BASE + i))
|
|
40
|
+
except OSError: pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run(index, total, step=FORWARD, invert_controls=False, invert_mode=False):
|
|
44
|
+
identity = GREEK[index]
|
|
45
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
46
|
+
sock.setblocking(False); sock.bind(("127.0.0.1", BASE + index))
|
|
47
|
+
seen, state, mode = {index}, ROCK, 0
|
|
48
|
+
old = termios.tcgetattr(sys.stdin); tty.setcbreak(sys.stdin.fileno())
|
|
49
|
+
ignored = (signal.SIGTSTP, signal.SIGQUIT)
|
|
50
|
+
handlers = {s: signal.getsignal(s) for s in ignored}
|
|
51
|
+
for s in ignored: signal.signal(s, signal.SIG_IGN)
|
|
52
|
+
hello_at = ready_at = emit_at = 0.0; shown_seen = -1
|
|
53
|
+
try:
|
|
54
|
+
while True:
|
|
55
|
+
now = time.monotonic()
|
|
56
|
+
if now >= hello_at:
|
|
57
|
+
send(sock, total, f"H{index}", index); hello_at = now + .25
|
|
58
|
+
while True:
|
|
59
|
+
try: msg = sock.recvfrom(64)[0].decode("utf-8")
|
|
60
|
+
except BlockingIOError: break
|
|
61
|
+
if msg.startswith("H"):
|
|
62
|
+
seen.add(int(msg[1:]))
|
|
63
|
+
elif now >= ready_at:
|
|
64
|
+
# ================= LINCHPIN ================= #
|
|
65
|
+
admissible = msg == step[state]
|
|
66
|
+
# ============================================ #
|
|
67
|
+
if admissible:
|
|
68
|
+
state = msg; ready_at = emit_at = now + 1.0
|
|
69
|
+
send(sock, total, msg, index)
|
|
70
|
+
draw(identity, mode, state, invert_controls)
|
|
71
|
+
if len(seen) < total:
|
|
72
|
+
if len(seen) != shown_seen:
|
|
73
|
+
draw(identity, mode, state, invert_controls, f"{len(seen)} / {total}"); shown_seen = len(seen)
|
|
74
|
+
time.sleep(.05); continue
|
|
75
|
+
if ready_at == 0.0:
|
|
76
|
+
draw(identity, mode, state, invert_controls); ready_at = emit_at = now + 1.0
|
|
77
|
+
active = (1 - mode) if invert_mode else mode
|
|
78
|
+
if active and now >= ready_at and now >= emit_at:
|
|
79
|
+
send(sock, total, step[state], index); emit_at = now + 1.0
|
|
80
|
+
ready, _, _ = select.select([sys.stdin], [], [], .05)
|
|
81
|
+
if ready:
|
|
82
|
+
key = os.read(sys.stdin.fileno(), 3)
|
|
83
|
+
if key == b" ": break
|
|
84
|
+
if key in (b"\x1b[A", b"\x1b[B"):
|
|
85
|
+
down = key == b"\x1b[B"
|
|
86
|
+
mode = 1 if (not down if invert_controls else down) else 0
|
|
87
|
+
emit_at = time.monotonic() + 1.0
|
|
88
|
+
draw(identity, mode, state, invert_controls)
|
|
89
|
+
except (KeyboardInterrupt, EOFError):
|
|
90
|
+
pass
|
|
91
|
+
finally:
|
|
92
|
+
for s, h in handlers.items(): signal.signal(s, h)
|
|
93
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
|
|
94
|
+
clear(); sock.close()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main():
|
|
98
|
+
index, total = map(int, sys.argv[1:3])
|
|
99
|
+
run(index, total)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__": main()
|