TUI OpenSoundControl finite state machine in Python
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

118 lignes
3.2KB

  1. import curses
  2. import random
  3. import sys
  4. import re
  5. ESCAPE = 27
  6. LETTERS = range(97, 122)
  7. NUMBERS = range(48, 57)
  8. class CheekyFSM(object):
  9. def __init__(self):
  10. self.lastkey = -1
  11. self.buffer = ""
  12. self.message = ""
  13. self.edges = {}
  14. self.state = "A"
  15. self.history = " "*30
  16. def show_edges(self):
  17. """ Print the help menu """
  18. y, x = self.w.getmaxyx()
  19. for index, ((start, end), prob) in enumerate(self.edges.items()):
  20. self.w.addstr(10 + index, 2, f"{start} -> {end} : {prob}%")
  21. def update(self):
  22. """ Update the FSM """
  23. edges = list(self.edges.items())
  24. random.shuffle(edges)
  25. for ((start, end), prob) in edges:
  26. if start == self.state:
  27. if random.randint(0, 100) < prob:
  28. self.state = end
  29. self.history = self.history[1:] + self.state
  30. def show_state(self):
  31. """ Show the state of the machine """
  32. self.w.addstr(2, 2, f"Tempo: 120bpm")
  33. self.w.addstr(4, 2, f"State: {self.state}")
  34. self.w.addstr(5, 2, f"History: {self.history}")
  35. offset = 6
  36. for ((start, end), prob) in self.edges.items():
  37. if start == self.state:
  38. self.w.addstr(offset, 2,
  39. f"Going to {end} with probability {prob}%")
  40. offset += 1
  41. def show_status(self):
  42. """ Print the status bar """
  43. y, x = self.w.getmaxyx()
  44. self.w.addstr(y - 2, 2, f"> {self.buffer}", curses.A_BOLD)
  45. def quit(self):
  46. """ Quit the program """
  47. curses.nocbreak()
  48. self.w.keypad(False)
  49. curses.echo()
  50. curses.endwin()
  51. sys.exit(0)
  52. def process_key(self):
  53. """ Process a key """
  54. key = self.w.getch()
  55. self.lastkey = key
  56. if key == ESCAPE:
  57. quit(self.w)
  58. elif key in LETTERS:
  59. self.buffer += chr(key).upper()
  60. elif key in NUMBERS:
  61. self.buffer += chr(key)
  62. elif key == 10:
  63. self.accept()
  64. elif key == 263 and len(self.buffer) > 0:
  65. self.buffer = self.buffer[:-1]
  66. def accept(self):
  67. """ Accept the string """
  68. match = re.match(r"([A-Z])([A-Z])(\d+)", self.buffer)
  69. if not match:
  70. self.message = " Invalid command :("
  71. else:
  72. groups = match.groups()
  73. start = groups[0]
  74. end = groups[1]
  75. prob = int(groups[2])
  76. self.modify(start, end, prob)
  77. self.buffer = ""
  78. def modify(self, start, end, prob):
  79. """ Make a modification """
  80. key = (start, end)
  81. self.edges[key] = prob
  82. self.message = f" {start} -> {end} : {prob}%"
  83. if prob == 0 and key in self.edges:
  84. self.message = f" {start} -> {end} : deleted"
  85. del self.edges[key]
  86. def loop(self, w):
  87. """ Run the loop """
  88. self.w = w
  89. curses.curs_set(0) # invisible
  90. w.timeout(100)
  91. while True:
  92. w.clear()
  93. self.show_edges()
  94. self.show_status()
  95. self.show_state()
  96. self.update()
  97. w.refresh()
  98. self.process_key()
  99. if __name__ == "__main__":
  100. fsm = CheekyFSM()
  101. curses.wrapper(fsm.loop)