9Volt 0.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.
- 9volt-0.0.1.dist-info/METADATA +6 -0
- 9volt-0.0.1.dist-info/RECORD +11 -0
- 9volt-0.0.1.dist-info/WHEEL +5 -0
- 9volt-0.0.1.dist-info/entry_points.txt +2 -0
- 9volt-0.0.1.dist-info/top_level.txt +2 -0
- HaltingMachine.py +7 -0
- Machine/Spacewalk.py +50 -0
- Machine/__init__.py +1 -0
- Machine/field.py +75 -0
- Machine/inverter.py +14 -0
- Machine/observer.py +134 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
HaltingMachine.py,sha256=uFP7vjkHDTU7iowZ8J6zi_swSH3CDSGJWLOQDkR71Xo,292
|
|
2
|
+
Machine/Spacewalk.py,sha256=L-n4-_9j7_BkiqEbc9pPpOPlKK5OesJ7g0X6eJ2UQQI,1847
|
|
3
|
+
Machine/__init__.py,sha256=gWfketXvBDaqOxZrk4DnoRPuCXCrdQO3NTkB8Y-netc,53
|
|
4
|
+
Machine/field.py,sha256=x0HVUWjfyvgSJ4p0K7yfJPmH6Ait_aIQv0a4ZBku1yc,2988
|
|
5
|
+
Machine/inverter.py,sha256=SUyPNGN-q-OnWRucvTqp74yIVvDOWmUTDndds6roJXU,277
|
|
6
|
+
Machine/observer.py,sha256=WsZ1oUMyXItKBPbl34Uj4WfbW7rQod-AiM2dSRluXUA,6286
|
|
7
|
+
9volt-0.0.1.dist-info/METADATA,sha256=mU-vVUk-OMguM7LHTu5hCg6tALbY_skSkOy3e4ddZcI,160
|
|
8
|
+
9volt-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
9volt-0.0.1.dist-info/entry_points.txt,sha256=xFGFsuNord-JFO1IXqag_U7lVtBmc_xyg5UA3hJzrlY,55
|
|
10
|
+
9volt-0.0.1.dist-info/top_level.txt,sha256=KqgqQ1tFPdtcFboqn1xrkVgv7iRX2pjJP8iCFkN1NZM,23
|
|
11
|
+
9volt-0.0.1.dist-info/RECORD,,
|
HaltingMachine.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import os, sys
|
|
2
|
+
from Machine.field import main
|
|
3
|
+
if __name__ == "__main__":
|
|
4
|
+
if sys.argv[1:] not in ([], ["Spacewalk.py"], ["Spacewalk"]):
|
|
5
|
+
raise SystemExit("Usage: python HaltingMachine.py [Spacewalk.py]")
|
|
6
|
+
os.environ["HALTING_SPACEWALK"] = "1" if sys.argv[1:] else "0"
|
|
7
|
+
main()
|
Machine/Spacewalk.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Change only this value to adjust Spacewalk's proposal and doorway pace.
|
|
2
|
+
INTERVAL = 3.0
|
|
3
|
+
|
|
4
|
+
POSITIVE = ('000', '100', '110', '111')
|
|
5
|
+
NEGATIVE = ('000', '001', '011', '111')
|
|
6
|
+
LATERAL = frozenset(('010', '101'))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Spacewalk:
|
|
10
|
+
def __init__(self, inverted=False):
|
|
11
|
+
self.inverted = inverted
|
|
12
|
+
self.state = self.previous = '000'
|
|
13
|
+
self.doorway_at = None
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def lane(self):
|
|
17
|
+
return NEGATIVE if self.inverted else POSITIVE
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def shell(self):
|
|
21
|
+
return self.state.count('1')
|
|
22
|
+
|
|
23
|
+
def propose(self):
|
|
24
|
+
position = self.lane.index(self.state)
|
|
25
|
+
return self.lane[position + 1] if position < 3 else None
|
|
26
|
+
|
|
27
|
+
def accept(self, incoming, now=0):
|
|
28
|
+
successor = self.propose()
|
|
29
|
+
# ================= LINCHPIN ================= #
|
|
30
|
+
if incoming != successor or successor is None:
|
|
31
|
+
return None
|
|
32
|
+
# ============================================ #
|
|
33
|
+
assert sum(a != b for a, b in zip(self.state, successor)) == 1
|
|
34
|
+
self.previous, self.state = self.state, successor
|
|
35
|
+
if successor == '111': self.doorway_at = now + INTERVAL
|
|
36
|
+
return successor
|
|
37
|
+
|
|
38
|
+
def doorway(self):
|
|
39
|
+
assert self.state == '111'
|
|
40
|
+
self.previous = self.state
|
|
41
|
+
self.inverted = not self.inverted
|
|
42
|
+
self.state, self.doorway_at = '000', None
|
|
43
|
+
return self.state
|
|
44
|
+
|
|
45
|
+
def display(self):
|
|
46
|
+
emblems = ('🪨', '✂️', '📜', '🪨') if self.inverted else ('🪨', '📜', '✂️', '🪨')
|
|
47
|
+
icons = ''.join(glyph + (' ' if glyph == '✂️' else '') for glyph in emblems)
|
|
48
|
+
pointer = ' ' * (4 + 2 * self.shell)
|
|
49
|
+
left, right = (self.state, self.previous) if self.state == '000' and self.previous == '111' else (self.previous, self.state)
|
|
50
|
+
return pointer + '🔻', f'{left} {icons} {right}', pointer + '🔺'
|
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, 3)
|
|
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,134 @@
|
|
|
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
|
+
def paint():
|
|
12
|
+
sys.stdout.write("\033[40m\033[97m\033[2J\033[H\033[?25l")
|
|
13
|
+
|
|
14
|
+
def clear():
|
|
15
|
+
sys.stdout.write("\033[?25h\033[0m\033[2J\033[H"); sys.stdout.flush()
|
|
16
|
+
|
|
17
|
+
def center(text, width=None):
|
|
18
|
+
width = wcswidth(text) if width is None else width
|
|
19
|
+
return " " * max(0, (os.get_terminal_size().columns - width) // 2) + text
|
|
20
|
+
|
|
21
|
+
def draw(identity, mode, state, inverted=False, genesis=None, cube=None):
|
|
22
|
+
paint(); print("\n\n\n\n" + center(f"{identity} HaltingMachine {identity}"), "\n")
|
|
23
|
+
if genesis is not None:
|
|
24
|
+
print(center("GENESIS"), "\n", center(genesis), flush=True); return
|
|
25
|
+
face = "🙃" if inverted else "🙂"
|
|
26
|
+
halt = f"{face}HALT{face}" if mode == 0 else "HALT"
|
|
27
|
+
repeat = f"{face}REPEAT{face}" if mode else "REPEAT"
|
|
28
|
+
visuals = cube.display() if cube else (ART[state],)
|
|
29
|
+
positions = [center(text, 16 if cube else 8) for text in visuals]
|
|
30
|
+
if cube:
|
|
31
|
+
print(center(halt), center(repeat), *positions, ' ' + center("SPACE TO EXIT"), sep="\n", flush=True)
|
|
32
|
+
else:
|
|
33
|
+
print(center(halt), center(repeat), '', *positions, '', ' ' + center("SPACE TO EXIT"), sep="\n", flush=True)
|
|
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
|
+
def run(index, total, step=FORWARD, invert_controls=False, invert_mode=False):
|
|
43
|
+
cube = None
|
|
44
|
+
if os.environ.get("HALTING_SPACEWALK") == "1":
|
|
45
|
+
from .Spacewalk import Spacewalk, INTERVAL
|
|
46
|
+
cube = Spacewalk(invert_mode)
|
|
47
|
+
interval = INTERVAL if cube else 1.0
|
|
48
|
+
identity = GREEK[index]
|
|
49
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
50
|
+
sock.setblocking(False); sock.bind(("127.0.0.1", BASE + index))
|
|
51
|
+
seen, state, mode = {index}, ROCK, 0
|
|
52
|
+
def show(genesis=None):
|
|
53
|
+
draw(identity, mode, state, cube.inverted if cube else invert_controls, genesis, cube)
|
|
54
|
+
old = termios.tcgetattr(sys.stdin); tty.setcbreak(sys.stdin.fileno())
|
|
55
|
+
ignored = (signal.SIGTSTP, signal.SIGQUIT)
|
|
56
|
+
handlers = {s: signal.getsignal(s) for s in ignored}
|
|
57
|
+
for s in ignored: signal.signal(s, signal.SIG_IGN)
|
|
58
|
+
hello_at = ready_at = 0.0; emit_at = None if cube else 0.0; shown_seen = -1
|
|
59
|
+
try:
|
|
60
|
+
while True:
|
|
61
|
+
now = time.monotonic()
|
|
62
|
+
if cube and cube.doorway_at is not None and now >= cube.doorway_at:
|
|
63
|
+
cube.doorway()
|
|
64
|
+
mode = int(cube.inverted)
|
|
65
|
+
emit_at = None # Doorway resets to an effectively HALTed relay.
|
|
66
|
+
show()
|
|
67
|
+
if now >= hello_at:
|
|
68
|
+
send(sock, total, f"H{index}", index); hello_at = now + .25
|
|
69
|
+
while True:
|
|
70
|
+
try: msg = sock.recvfrom(64)[0].decode("utf-8")
|
|
71
|
+
except BlockingIOError: break
|
|
72
|
+
if msg.startswith("H"):
|
|
73
|
+
seen.add(int(msg[1:]))
|
|
74
|
+
elif cube or now >= ready_at:
|
|
75
|
+
if cube:
|
|
76
|
+
admitted = cube.accept(msg, now)
|
|
77
|
+
active = (1 - mode) if cube.inverted else mode
|
|
78
|
+
if admitted is not None and active:
|
|
79
|
+
emit_at = now + interval # Admission rephases the repeater.
|
|
80
|
+
elif (admitted is None and active and emit_at is None
|
|
81
|
+
and len(msg) == 3 and set(msg) <= {'0', '1'}):
|
|
82
|
+
emit_at = now + interval # One delayed reply to a rejected Trinity.
|
|
83
|
+
else:
|
|
84
|
+
# ================= LINCHPIN ================= #
|
|
85
|
+
admissible = msg == step[state]
|
|
86
|
+
# ============================================ #
|
|
87
|
+
admitted = msg if admissible else None
|
|
88
|
+
if admitted is not None:
|
|
89
|
+
if not cube:
|
|
90
|
+
state = admitted
|
|
91
|
+
ready_at = emit_at = now + 1.0
|
|
92
|
+
send(sock, total, admitted, index)
|
|
93
|
+
show()
|
|
94
|
+
if len(seen) < total:
|
|
95
|
+
if len(seen) != shown_seen:
|
|
96
|
+
show(f"{len(seen)} / {total}"); shown_seen = len(seen)
|
|
97
|
+
time.sleep(.05); continue
|
|
98
|
+
if ready_at == 0.0:
|
|
99
|
+
show(); ready_at = now + interval
|
|
100
|
+
if not cube: emit_at = ready_at
|
|
101
|
+
elif cube and cube.inverted: emit_at = emit_at or ready_at
|
|
102
|
+
active = (1 - mode) if (cube.inverted if cube else invert_mode) else mode
|
|
103
|
+
if cube:
|
|
104
|
+
if active and emit_at is not None and now >= emit_at:
|
|
105
|
+
proposal = cube.propose()
|
|
106
|
+
if proposal is not None: send(sock, total, proposal, index)
|
|
107
|
+
emit_at = None # One proposal per encounter; listen for another.
|
|
108
|
+
elif active and now >= ready_at and now >= emit_at:
|
|
109
|
+
send(sock, total, step[state], index)
|
|
110
|
+
emit_at = now + 1.0
|
|
111
|
+
ready, _, _ = select.select([sys.stdin], [], [], .05)
|
|
112
|
+
if ready:
|
|
113
|
+
key = os.read(sys.stdin.fileno(), 3)
|
|
114
|
+
if key == b" ": break
|
|
115
|
+
if key in (b"\x1b[A", b"\x1b[B"):
|
|
116
|
+
mode = 1 - mode
|
|
117
|
+
if cube:
|
|
118
|
+
active = (1 - mode) if cube.inverted else mode
|
|
119
|
+
emit_at = time.monotonic() + interval if active else None
|
|
120
|
+
else:
|
|
121
|
+
emit_at = time.monotonic() + 1.0
|
|
122
|
+
show()
|
|
123
|
+
except (KeyboardInterrupt, EOFError):
|
|
124
|
+
pass
|
|
125
|
+
finally:
|
|
126
|
+
for s, h in handlers.items(): signal.signal(s, h)
|
|
127
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
|
|
128
|
+
clear(); sock.close()
|
|
129
|
+
|
|
130
|
+
def main():
|
|
131
|
+
index, total = map(int, sys.argv[1:3])
|
|
132
|
+
run(index, total)
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__": main()
|