| @@ -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) | |||