SuperHirn uses a lightweight ports-and-adapters architecture. The game rules are independent of PySide6, HTTP, and concrete algorithms.
UI -> ApplicationControl -> CoreDomainModel <- Implementation / Networking
^
|
main.py
main.py is the composition root: it selects concrete adapters and injects
them into the domain module. Dependencies point inward; the core domain never
imports the UI, networking client, or a concrete algorithm.
| Module | Responsibility | Seam |
|---|---|---|
CoreDomainModel |
Mastermind rules, game state, GameSession, value objects, and provider interfaces |
IGame, IGuessProvider, IEvaluationProvider, ISecretCodeProvider, IProviderFactory |
ApplicationControl |
Coordinates UI requests and domain events | IBoundary |
Implementation |
Concrete algorithms and local/UI providers | Domain provider interfaces |
Networking |
HTTP adapter for remote evaluation | NetworkService |
UI |
PySide6 rendering and user interaction | IBoundary |
- Ports and adapters: the domain owns the interfaces; UI, network, and providers are adapters at those seams.
- Strategy: Consistency and Knuth algorithms share the
IGuessProviderinterface and can be selected per game or per spectator board. - Factory:
ProviderFactoryassembles the provider strategy appropriate for the selected game mode. - Observer:
Gamepublishes game events to the controller, which forwards them to registered UI callbacks. - Composition root:
main.pyowns concrete dependency construction.
GameSession is the domain aggregate for one game board. It groups the secret
provider, guess provider, evaluation provider, round history, and state. The
spectator mode creates two sessions with one shared secret and independent
guess strategies. Code Maker routes the player-entered secret through
UISecretCodeProvider, while the domain deliberately does not expose that
secret through its game view; only the player UI retains it for display.
- Single responsibility: UI rendering, game rules, algorithm selection, persistence, and HTTP communication stay in separate modules.
- Open/closed: add an algorithm or an evaluator by adding an adapter that satisfies its domain interface; callers do not need mode-specific changes.
- Liskov substitution: all guess-provider adapters support generating, updating, checking, and pre-validating feedback through the same interface.
- Interface segregation: provider interfaces are narrow and model only a provider's responsibility. Optional input methods explicitly fail when an adapter cannot support them.
- Dependency inversion: the domain depends on provider interfaces, while
adapters depend on the domain;
main.pyperforms the wiring.
- Keep framework, HTTP, and filesystem imports out of
CoreDomainModel. - Keep adapter internals private. Callers must use the provider interface,
including
would_remain_consistent, rather than inspect candidate lists. - Add a regression test for every behavioural change at the domain interface or adapter seam.