J. Rogers, SE Ohio
1. Executive Summary
2. The Problem: Divergent and Brittle Architectures
Inconsistent Input Handling: Mouse and keyboard actions behaved differently depending on the view. For example, marker dragging and context menus were fully implemented in the GeoController but were completely absent in the TacticalController. This forced the user to remember two separate control schemes.Special-Cased Navigation: The logic for navigating "up" a level was a complex web of if/elif statements checking for specific node types like dungeon_complex. This was a hardcoded, genre-specific rule that made the system inflexible and difficult to debug.Entangled Data and Presentation: The type of renderer used was initially tied to the node['type']. A building_interior would always attempt to use a "blueprint" style, and a dungeon_level would always use a "hand-drawn" style. This prevented stylistic flexibility, such as rendering a dungeon with a clean blueprint aesthetic.State Management Chaos: State information (like the last-visited dungeon level) was stored inconsistently, sometimes on a marker, sometimes on a container node, and sometimes not at all. This led to bugs where moving a marker would "forget" its linked dungeon.
3. The Solution: Regularization via a Unified Interface
The BaseController Contract: An abstract class, BaseController, was implemented. It defines the public-facing methods that the main application loop (main.py) is allowed to call:handle_input() draw_map() draw_overlays() refresh_data() render_player_view_surface()
Standardized Marker Logic: The complete, working marker interaction logic from GeoController (hover, drag, short-click entry, right-click context menu) was moved into TacticalController. Now, a marker is a marker, and it behaves the same everywhere. The only difference is that dragging is restricted to the "Tools" tab in GeoController.Decoupled Rendering Style: The responsibility for choosing a visual style was removed from the controller's __init__ logic. Instead, the Generators (dungeon_gen.py, building_gen.py) now stamp each node with a render_style: 'hand_drawn' or render_style: 'blueprint' in its metadata. The TacticalController now uses a single, unified TacticalRenderer that reads this style and adjusts its drawing primitives accordingly. This makes the system data-driven and extensible to hundreds of potential styles.
4. The Payoff: A Trivial Multi-Process Player Window
Trigger: The GM moves the "Party View" marker. The active controller (Geo or Tactical) detects the end of the drag and returns the action {"action": "update_player_view"}.Command: main.py catches this action and makes a single, universal call:player_surface = self.map_viewer.controller.render_player_view_surface() Polymorphism in Action: main.py does not know or care if the active controller is a GeoController or a TacticalController. It only knows that, according to the BaseController contract, the render_player_view_surface() method is guaranteed to exist.Encapsulated Rendering: The active controller performs a "headless" render of its map from the perspective of the View Marker, using its own specific render_strategy or renderer. It returns a fully drawn pygame.Surface.Inter-Process Communication (IPC): main.py takes this generic surface, serializes it to a string, and puts it onto the multiprocessing.Queue.Display: The separate player_window_process receives the image data and displays it.
No comments:
Post a Comment