def purge() -> None: # Debug Debug.print("Purging stored images", DebugChannel.GRAPHICS) # Clear Images ImageLoader.data = {}
def state_update(self, state: str, store: bool = False, data: Dict = None) -> None: # Debug Debug.print("Updating to %s state" % state, DebugChannel.STATE) # Purge Images ImageLoader.purge() # Existing State if self.state_active is not None: # Store Existing if store is True: self.state_active.on_store() self.state_stored = self.state_active # Terminate Existing else: self.state_active.on_terminate() # Initialise State self.state_active = self.state_loaded[state](self) # NOTE: put the above into a method to gracefully handle this self.state_active.on_start(data) # Bind Events self.state_bind()
def state_load(directory: str) -> Dict[str, State]: # Debug Debug.print("Loading states from directory %s" % directory, DebugChannel.STATE) # Directory Path directory_path = os.path.join(os.getcwd(), directory) # List Files file_list = ArrayList(os.listdir(directory_path)).reject(lambda it: it.startswith("_")).map(lambda it: it.split(".")[0]) # NOTE: current reject is not going to ignore directories # Module Logic # def load_module(module: module) -> List[module]: # type is <class 'module'> but hasn't been imported def load_module(module): # List Attributes result = ArrayList(list(module.__dict__.keys())).reject(lambda it: it == "State") # Map Classes result = result.map(lambda it: (it, getattr(module, it))) # Return States return result.filter(lambda _, v: inspect.isclass(v) and issubclass(v, State)) # Return States result = {} for module in file_list.map(lambda it: load_module(importlib.import_module("%s.%s" % (directory.split("/")[-1], it)))): for name, state in module: result[name] = state Debug.print(" - %s" % name, DebugChannel.STATE) return result
def play(sound: str) -> None: # Debug Debug.print("Playing sound %s" % sound, DebugChannel.AUDIO) # Thread Logic def execute(sound: str, _) -> None: playsound("resources/sounds/%s.mp3" % sound) # Spawn Thread Thread(target=execute, args=(sound, None), daemon=False).start()
def add_event(self, time_ms: int, logic: Callable) -> None: # Create Event event_new = { "logic": logic, "timer": (time.time() * 1000) + time_ms } # Debug Debug.print("Creating new event %d to fire in %d ms" % (id(event_new), time_ms), DebugChannel.STATE) # Append Event self.event = self.event.add(event_new)
def tick_event(self) -> None: # Check Events time_ms: int = time.time() * 1000 for event in self.event.filter(lambda it: time_ms >= it["timer"]): # Debug Debug.print("Invoking event %d" % id(event), DebugChannel.STATE) # Invoke Logic event["logic"]() # Remove Event self.event = self.event.remove(event)
def _store(image_file: str, image_name: str, size: Dimensions, point: Point) -> None: # Load Image image_data = Image.open("resources/images/%s.png" % image_file) # Crop Image if size is not None and point is not None: posX: int = point.x * size.width posY: int = point.y * size.height image_data = image_data.crop((posX, posY, posX + size.width, posY + size.height)) # Store Image ImageLoader.data[image_name] = ImageTk.PhotoImage(image_data) # Debug Debug.print("Stored image %s (%d)" % (image_name, len(ImageLoader.data)), DebugChannel.GRAPHICS)
def terminate(self) -> None: # Already Terminating if self.running is False: return # Debug Debug.print("Terminating application") # Application Status self.running = False # Terminate Controller self.controller.terminate() # System Exit sys.exit()
def state_revert(self, data: Dict = None) -> None: # Debug Debug.print("Reverting to stored state", DebugChannel.STATE) # Purge Images ImageLoader.purge() # Nothing Stored if self.state_stored is None: raise Exception("No stored state to revert to!") # Terminate Existing self.state_active.on_terminate() # Revert State self.state_active = self.state_stored self.state_active.on_revert(data) self.state_stored = None # Bind Events self.state_bind()
def on_key_pressed(self, event: Any) -> None: # NOTE: event should be specifically typed here if event.keycode in Keyboard.action: Debug.print(event, DebugChannel.INPUT) self.action(Keyboard.action[event.keycode])
def action(self, action: Action) -> None: Debug.print(action, DebugChannel.STATE) self.state_active.on_action(action)
def _debug_parse(value: str, title: str, size: Dimensions, tick_ms: int) -> None: # Invalid Value if not isinstance(value, str) or re.match(r"^[\+\-][A-Z]*$", value) is False: raise Exception("Invalid debug string!") # Disable Channels if value[0] == "-": # Disable All if len(value) == 1: return # Disable Specific if "R" not in value: Debug.debug_channels[DebugChannel.RIEM] = True if "S" not in value: Debug.debug_channels[DebugChannel.STATE] = True if "A" not in value: Debug.debug_channels[DebugChannel.AUDIO] = True if "G" not in value: Debug.debug_channels[DebugChannel.GRAPHICS] = True if "I" not in value: Debug.debug_channels[DebugChannel.INPUT] = True # Enable Channels if value[0] == "+": # Enable All if len(value) == 1: Debug.debug_channels = { DebugChannel.RIEM: True, DebugChannel.STATE: True, DebugChannel.AUDIO: True, DebugChannel.GRAPHICS: True, DebugChannel.INPUT: True } # Enable Specific if "R" in value: Debug.debug_channels[DebugChannel.RIEM] = True if "S" in value: Debug.debug_channels[DebugChannel.STATE] = True if "A" in value: Debug.debug_channels[DebugChannel.AUDIO] = True if "G" in value: Debug.debug_channels[DebugChannel.GRAPHICS] = True if "I" in value: Debug.debug_channels[DebugChannel.INPUT] = True # Print Info print("") Debug.print("Application Debug Mode") Debug.print("======================") Debug.print("Version: %s" % __version__) Debug.print("Project: %s" % title) Debug.print("Window: %d x %d" % (size.width, size.height)) Debug.print("Tick: %d ms" % tick_ms) Debug.print()
def __init__(self, title: str, state_initial: str, state_directory: str, default_text: Dict[str, str] = None, icon: str = None, size: Dimensions = Dimensions(960, 720), tick_ms: int = 250, **kwargs) -> None: # Default Properties maximise: bool = False # Parse Kwargs for k, v in kwargs.items(): # Option: Debug if k == "debug": Application._debug_parse(v, title, size, tick_ms) # Option: Maximise if k == "fullscreen" and v == True: maximise = True # Public Properties self.size: Dimensions = size # State Logic def state_load(directory: str) -> Dict[str, State]: # Debug Debug.print("Loading states from directory %s" % directory, DebugChannel.STATE) # Directory Path directory_path = os.path.join(os.getcwd(), directory) # List Files file_list = ArrayList(os.listdir(directory_path)).reject(lambda it: it.startswith("_")).map(lambda it: it.split(".")[0]) # NOTE: current reject is not going to ignore directories # Module Logic # def load_module(module: module) -> List[module]: # type is <class 'module'> but hasn't been imported def load_module(module): # List Attributes result = ArrayList(list(module.__dict__.keys())).reject(lambda it: it == "State") # Map Classes result = result.map(lambda it: (it, getattr(module, it))) # Return States return result.filter(lambda _, v: inspect.isclass(v) and issubclass(v, State)) # Return States result = {} for module in file_list.map(lambda it: load_module(importlib.import_module("%s.%s" % (directory.split("/")[-1], it)))): for name, state in module: result[name] = state Debug.print(" - %s" % name, DebugChannel.STATE) return result # State Management self.state_active = None self.state_stored = None self.state_loaded = state_load(state_directory) self.state_bind = lambda: self.app.bind("<Key>", self.on_key_pressed) # NOTE: these shouldn't be public # Create Application Debug.print("Creating application", DebugChannel.RIEM) self.app = Tk() self.app.title(title) self.app.geometry("%dx%d" % (self.size.width, self.size.height)) self.app.resizable(False, False) if maximise is True: self.app.attributes("-fullscreen", True) if icon is not None: Debug.print(" - locating custom icon %s" % icon, DebugChannel.RIEM) self.app.iconbitmap(r'%s' % os.path.join(os.getcwd(), "resources", "icons", "%s.ico" % icon)) # NOTE: self.app shouldn't be public # Create Canvas canvas = Canvas(self.app, bg = "black", width = self.size.width, height = self.size.height, highlightthickness = 0) canvas.pack() # Create Graphics gfx: Graphics = Graphics(canvas, default_text) # Intro State self.state_active = StateIntro(self, state_initial) # Initialise Controller Debug.print("Initialising controller", DebugChannel.INPUT) self.controller = Controller(self) # Application Status self.running = True # Create Loop def loop() -> None: # Not Running if self.running is not True: return # Timer Start loop_time: int = (time.time() * 1000) # Controller Actions self.controller.get_actions().each(lambda it: self.action(it)) # Application Tick self.state_active.tick() self.state_active.tick_event() # Application Render gfx.draw_rect(Point(0, 0), self.size, "black", True) self.state_active.render(gfx) # Schedule Loop loop_time = (time.time() * 1000) - loop_time loop_wait: int = 0 if loop_time < tick_ms: loop_wait = tick_ms - loop_time self.app.after(int(loop_wait), loop) # Invoke Loop loop() # Start Application Debug.print("Initialising application loop", DebugChannel.RIEM) self.app.mainloop()