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)