# Statechart exercises

Five problems, hardest last. Each directory holds:

- `machine.py` — the skeleton you fill in. Its docstring is the contract.
- `test_machine.py` — the acceptance tests. Do not edit them.
- `_solution.py` — a reference solution. Open it only once your own is green.

## Setup

```bash
uv venv && uv pip install python-statemachine pytest
```

## Working a problem

```bash
cd p1_microwave
pytest -q
```

Draw the chart before you type. Every problem is set up so that the drawing is
the hard part and the code is a transcription of it — if the code is fighting
you, the drawing is wrong.

## Order

| | Problem | What it is for |
|---|---|---|
| P1 | Microwave oven | Hierarchy, entry/exit actions, guards, context |
| P2 | Media player | History — shallow against deep — and a first parallel region |
| P3 | Elevator controller | Two real regions, a queue in context, eventless transitions |
| P4 | TCP (RFC 9293) | A flat machine you have to live with, then refactor |
| P5 | Harel's digital watch | All of it, plus scope |

## Library notes

Written against `python-statemachine` 3.2.x, using the `StateChart` base class
(SCXML semantics), not the legacy `StateMachine`. Three things that will cost
you an hour if nobody tells you:

1. **`sm.<id>` is bound, `sm.parent.child` is not.** Every state is published
   on the instance under its own id, and only that accessor reflects the live
   configuration. `self.car.doors.closed.is_active` reaches the class
   attribute and is always `False`. Use `self.closed.is_active`.
2. **`internal=True` needs `enable_self_transition_entries = False`.** With
   the `StateChart` default (`True`) an internal self-transition still runs
   the state's entry actions.
3. **Do not use `internal=True` inside a parallel region** (3.2.1): it
   re-enters the sibling regions' default states and corrupts the
   configuration. An external self-transition is safe.
