|
|
@@ -0,0 +1,117 @@ |
|
|
|
import curses |
|
|
|
import random |
|
|
|
import sys |
|
|
|
import re |
|
|
|
|
|
|
|
ESCAPE = 27 |
|
|
|
LETTERS = range(97, 122) |
|
|
|
NUMBERS = range(48, 57) |
|
|
|
|
|
|
|
|
|
|
|
class CheekyFSM(object): |
|
|
|
def __init__(self): |
|
|
|
self.lastkey = -1 |
|
|
|
self.buffer = "" |
|
|
|
self.message = "" |
|
|
|
self.edges = {} |
|
|
|
self.state = "A" |
|
|
|
self.history = " "*30 |
|
|
|
|
|
|
|
def show_edges(self): |
|
|
|
""" Print the help menu """ |
|
|
|
y, x = self.w.getmaxyx() |
|
|
|
for index, ((start, end), prob) in enumerate(self.edges.items()): |
|
|
|
self.w.addstr(10 + index, 2, f"{start} -> {end} : {prob}%") |
|
|
|
|
|
|
|
def update(self): |
|
|
|
""" Update the FSM """ |
|
|
|
edges = list(self.edges.items()) |
|
|
|
random.shuffle(edges) |
|
|
|
for ((start, end), prob) in edges: |
|
|
|
if start == self.state: |
|
|
|
if random.randint(0, 100) < prob: |
|
|
|
self.state = end |
|
|
|
|
|
|
|
self.history = self.history[1:] + self.state |
|
|
|
|
|
|
|
def show_state(self): |
|
|
|
""" Show the state of the machine """ |
|
|
|
self.w.addstr(2, 2, f"Tempo: 120bpm") |
|
|
|
self.w.addstr(4, 2, f"State: {self.state}") |
|
|
|
self.w.addstr(5, 2, f"History: {self.history}") |
|
|
|
|
|
|
|
offset = 6 |
|
|
|
for ((start, end), prob) in self.edges.items(): |
|
|
|
if start == self.state: |
|
|
|
self.w.addstr(offset, 2, |
|
|
|
f"Going to {end} with probability {prob}%") |
|
|
|
offset += 1 |
|
|
|
|
|
|
|
def show_status(self): |
|
|
|
""" Print the status bar """ |
|
|
|
y, x = self.w.getmaxyx() |
|
|
|
self.w.addstr(y - 2, 2, f"> {self.buffer}", curses.A_BOLD) |
|
|
|
|
|
|
|
def quit(self): |
|
|
|
""" Quit the program """ |
|
|
|
curses.nocbreak() |
|
|
|
self.w.keypad(False) |
|
|
|
curses.echo() |
|
|
|
curses.endwin() |
|
|
|
sys.exit(0) |
|
|
|
|
|
|
|
def process_key(self): |
|
|
|
""" Process a key """ |
|
|
|
key = self.w.getch() |
|
|
|
self.lastkey = key |
|
|
|
if key == ESCAPE: |
|
|
|
quit(self.w) |
|
|
|
elif key in LETTERS: |
|
|
|
self.buffer += chr(key).upper() |
|
|
|
elif key in NUMBERS: |
|
|
|
self.buffer += chr(key) |
|
|
|
elif key == 10: |
|
|
|
self.accept() |
|
|
|
elif key == 263 and len(self.buffer) > 0: |
|
|
|
self.buffer = self.buffer[:-1] |
|
|
|
|
|
|
|
def accept(self): |
|
|
|
""" Accept the string """ |
|
|
|
match = re.match(r"([A-Z])([A-Z])(\d+)", self.buffer) |
|
|
|
if not match: |
|
|
|
self.message = " Invalid command :(" |
|
|
|
else: |
|
|
|
groups = match.groups() |
|
|
|
start = groups[0] |
|
|
|
end = groups[1] |
|
|
|
prob = int(groups[2]) |
|
|
|
self.modify(start, end, prob) |
|
|
|
self.buffer = "" |
|
|
|
|
|
|
|
def modify(self, start, end, prob): |
|
|
|
""" Make a modification """ |
|
|
|
key = (start, end) |
|
|
|
self.edges[key] = prob |
|
|
|
self.message = f" {start} -> {end} : {prob}%" |
|
|
|
if prob == 0 and key in self.edges: |
|
|
|
self.message = f" {start} -> {end} : deleted" |
|
|
|
del self.edges[key] |
|
|
|
|
|
|
|
def loop(self, w): |
|
|
|
""" Run the loop """ |
|
|
|
self.w = w |
|
|
|
curses.curs_set(0) # invisible |
|
|
|
w.timeout(100) |
|
|
|
while True: |
|
|
|
w.clear() |
|
|
|
self.show_edges() |
|
|
|
self.show_status() |
|
|
|
self.show_state() |
|
|
|
self.update() |
|
|
|
w.refresh() |
|
|
|
self.process_key() |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
fsm = CheekyFSM() |
|
|
|
curses.wrapper(fsm.loop) |