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