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