# Monolith to Composed structure a game as small, single-purpose nodes. ```{raw} html ▶ Run in browser ``` **Tags:** `tutorial` `architecture` `signals` # Monolith to Composed You can build games now. This last tutorial is about building them *well*: how to structure a game so it stays easy to change. The game itself is tiny, a dodge game where you slide a paddle along the bottom to avoid falling blocks. The point is the shape of the code. `main.py` is the **composed** version. The full before/after refactor, starting from one giant node that does everything, is the walkthrough in {doc}`/tutorials/from_monolithic_to_composed`. ## The idea: one node, one job The tempting first draft is a single `DodgeGame` node that tracks the player's `x`, runs a spawn timer, holds the score, draws everything, and checks collisions. It works, but every change touches the same 80-line method and nothing can be tested or reused in isolation. The fix is to give each concern its own node: | node | its one job | |---|---| | `Player` | read input, move, clamp, draw itself | | `Enemy` | fall, and announce when it escapes off the bottom | | `Spawner` | a timer that emits each new enemy | | `ScoreLabel` | hold the score and draw it | | `DodgeGame` (root) | own the *graph* and the one thing only it can see: collision | ## Signals connect them Each node stays ignorant of the others; the root wires them with signals: ```python self.spawner.spawned.connect(self._on_spawned) # new enemy -> root places it def _on_spawned(self, enemy): enemy.escaped.connect(lambda: self.score.add(1)) # dodged -> +1 self.add_child(enemy) ``` The **signal-pairing rule**: every signal has an emitter *and* a listener. Here `Spawner.spawned`, `Enemy.escaped`, and `ScoreLabel.changed` are all paired. A signal with no listener (or a global reached for instead of a signal) is a smell that the structure is wrong. ## The payoff The root's `on_update` shrinks to pure game flow (alive check, restart, collision sweep). Per-entity behaviour lives in the entity. Each node is small enough to test on its own with a `SceneRunner`, and reusable in the next game. ## Run it ```bash uv run python examples/tutorials/monolith_to_composed/main.py ``` ## Source ```{literalinclude} ../../examples/tutorials/monolith_to_composed/main.py :language: python :linenos: ```