From cceec64a7c789b93e9123efdd8f83902a843d123 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Sat, 21 Feb 2026 10:20:01 +0000 Subject: [PATCH] Add ARCHITECTURE.md developer guide Covers system goals, component design, directory structure, testing strategies, and key design decisions for future agents. --- docs/ARCHITECTURE.md | 230 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 docs/ARCHITECTURE.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..bc6e2dd --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,230 @@ +# Monop-State Architecture & Developer Guide + +## Goals + +This project tracks a game of BSD `monop` (terminal Monopoly) being played over IRC and produces a live visual board that can be viewed in a browser. There are three modes of operation: + +1. **Cardinal plugin mode** — A plugin for [Cardinal](https://github.com/JohnMaguire/Cardinal) (a Python IRC bot) watches game messages in an IRC channel and writes `game-state.json` on every state change. +2. **Standalone bridge mode** — `monop_bridge.py` spawns the `monop` binary as a subprocess and bridges its stdin/stdout to IRC, while `monop_players.py` provides autopilot player bots. +3. **Web viewer** — `site/index.html` reads `game-state.json` and renders a visual Monopoly board with player tokens, property ownership, houses/hotels, and a game log. Auto-refreshes every 2 seconds. + +The core challenge is **parsing unstructured terminal output** from a 1980s C program and reconstructing full game state from it. + +--- + +## Directory Structure + +``` +monop-state/ +├── monop_parser.py # Core: parses monop output → game state +├── monop_players.py # Autopilot player bots (connect via IRC) +├── monop_bridge.py # Bridges monop binary stdin/stdout to IRC +├── irc_client.py # Minimal IRC client (used by integration tests) +├── test_parser.py # Log replay test for the parser +├── test_players.py # Unit tests for player bot logic +├── test_integration.py # Full integration test (needs live IRC + monop) +├── test_data/ # Recorded game logs for replay testing +│ ├── monop.log # Real game log +│ ├── autopilot_bridge.log # Bridge-side log from an autopilot game +│ └── autopilot_players.log# Player-side log from an autopilot game +├── plugins/ # Cardinal IRC bot plugins +│ ├── monop/ # Game observer plugin +│ │ ├── plugin.py # Cardinal plugin entry point +│ │ ├── monop_parser.py # Bundled copy of the parser +│ │ ├── config.example.json +│ │ └── __init__.py +│ ├── monop-player/ # Autopilot player as Cardinal plugin +│ │ ├── plugin.py +│ │ ├── config.example.json +│ │ └── __init__.py +│ └── test_plugin.py # Smoke test for the Cardinal plugin +├── site/ # Web board viewer +│ ├── index.html # Self-contained HTML/CSS/JS board +│ └── game-state.json # State file (written by plugin or bridge) +├── reference/ # BSD monop C source code +├── docs/ +│ ├── ARCHITECTURE.md # This file +│ └── OUTPUT_CATALOG.md # Every possible monop output line +└── README.md +``` + +--- + +## Component Details + +### `monop_parser.py` — The Parser (~1200 lines) + +The heart of the system. Parses raw monop output lines and maintains full game state including: + +- Player positions, money, jail status, doubles tracking +- Property ownership, houses, hotels, mortgages +- Get Out of Jail Free cards +- Current player turn +- Game log + +**Key concepts:** + +- **Checkpoint lines** — Every turn starts with a line like `alice (1) (cash $1500) on === GO ===`. These are ground truth and the parser reconciles its tracked state against them. +- **Incremental parsing** — Between checkpoints, the parser tracks state changes (buys, rent payments, card draws, etc.) line by line using regex matching. +- **Input format** — Expects tab-separated log lines: `TIMESTAMP\tSENDER\tMESSAGE`. The parser only processes lines from the monop bot nick. + +**Important exports:** +- `MonopParser` — Main class. Call `parse_line(log_line)` and `get_state()`. +- `BOARD` — List of all 40 board squares with names, types, groups, and costs. +- `SQUARE_BY_NAME` — Dict mapping square name → square ID. + +### `monop_players.py` — Autopilot Bots (~685 lines) + +Connects to IRC as individual players and responds to monop prompts automatically. Each bot: + +- Rolls when it's their turn +- Buys properties when affordable +- Handles jail (pays to get out) +- Responds to auction prompts +- Handles debt resolution (mortgaging, selling houses) + +**Usage:** +```bash +python3 monop_players.py --host 127.0.0.1 --port 6667 --channel '#monop' --players alice,bob,charlie +``` + +Useful for testing the parser against a full game without manual play. + +### `monop_bridge.py` — IRC Bridge (~127 lines) + +Spawns the actual `monop` binary and bridges it to IRC: +- Reads monop's stdout and sends lines to IRC channel +- Reads IRC messages prefixed with `.` and pipes them to monop's stdin +- Expects the monop binary at `/tmp/monop-irc/monop/monop` + +### `irc_client.py` — Test IRC Client (~85 lines) + +Minimal IRC client used by integration tests. Connects, joins channels, sends messages, and collects responses. Not used in production. + +### Cardinal Plugins (`plugins/`) + +**`plugins/monop/`** — Watches IRC messages, feeds them to MonopParser, writes `game-state.json`. Provides IRC commands: +- `.monop` or `.monop status` — Current game summary +- `.monop players` — Detailed player info with properties +- `.monop owned` — Properties grouped by owner + +**`plugins/monop-player/`** — The autopilot player logic packaged as a Cardinal plugin (alternative to standalone `monop_players.py`). + +**Installing plugins into Cardinal:** +```bash +# Symlink from this repo into your Cardinal instance +ln -s /path/to/monop-state/plugins/monop /path/to/cardinal/plugins/monop +ln -s /path/to/monop-state/plugins/monop-player /path/to/cardinal/plugins/monop-player +``` + +The `plugins/monop/` directory contains its own copy of `monop_parser.py` (loaded via `importlib.util` from the same directory). This means the plugin is self-contained — it doesn't need the root-level parser on `sys.path`. + +### Web Viewer (`site/`) + +A single `index.html` that fetches `game-state.json` every 2 seconds and renders: +- Classic Monopoly board layout with color-coded property groups +- Player tokens with colors and initials +- Property ownership indicators, houses (green), hotels (red) +- Player info panels with money, properties, cards +- Game log with recent events +- Mobile-responsive dark theme +- Demo mode when no live game data exists + +**Serving:** +```bash +cd site/ +python3 -m http.server 9998 +``` + +The `game-state.json` must be in the same directory as `index.html` (it fetches it with a relative path). + +### Reference Source (`reference/`) + +The original BSD monop C source code. Use this to understand what output the binary produces — essential when adding new parser patterns. Key files: + +- `execute.c` — Movement, buying, rolling logic +- `rent.c` — Rent calculation and messages +- `houses.c` — House/hotel buying +- `jail.c` — Jail mechanics +- `cards.c` / `cards.inp` — Chance and Community Chest cards +- `print.c` — Board/player printing (checkpoint format) +- `brd.dat` — Board square definitions +- `prop.dat` — Property data (costs, rents) + +--- + +## Testing + +### Unit Tests — Parser (`test_parser.py`) + +Replays a recorded game log through the parser and validates that checkpoint lines match the parser's tracked state. + +```bash +python3 test_parser.py +``` + +Uses `test_data/monop.log` by default. This is the fastest way to verify parser correctness. If you fix a parsing bug, capture the failing log lines and add them to the test data. + +### Unit Tests — Player Bots (`test_players.py`) + +Tests the `PlayerBot` response logic in isolation using mocked IRC connections. + +```bash +python3 -m pytest test_players.py -v +``` + +Uses `FakePlayerBot` — a subclass that captures outgoing messages instead of sending to IRC. Tests verify that bots respond correctly to prompts (rolling, buying, jail, auctions, debt). + +### Plugin Smoke Test (`plugins/test_plugin.py`) + +Tests the Cardinal plugin without a running Cardinal instance by mocking the `cardinal` module. + +```bash +python3 plugins/test_plugin.py +``` + +### Integration Tests (`test_integration.py`) + +Runs a real game against a live monop-irc setup. Requires: +1. An IRC server (e.g., InspIRCd) running on localhost:6667 +2. The monop bridge running (`monop_bridge.py`) + +```bash +python3 test_integration.py +``` + +Plays a game using `irc_client.py`, periodically dumps state via `.print` and `.own` commands, and compares against the parser's tracked state. This is the most thorough test but requires infrastructure. + +### Manual Testing with Autopilot + +To watch a full automated game and observe the parser + web viewer working together: + +1. Start an IRC server +2. Start the bridge: `python3 monop_bridge.py` +3. Start player bots: `python3 monop_players.py --players alice,bob,charlie` +4. Start the web viewer: `cd site && python3 -m http.server 9998` +5. Watch the board update in your browser + +The `test_data/autopilot_bridge.log` and `test_data/autopilot_players.log` are recorded outputs from such a session — useful for debugging. + +--- + +## Adding New Parser Patterns + +When the parser fails to handle a monop output line: + +1. Check `docs/OUTPUT_CATALOG.md` — it catalogs every possible output line from the C source +2. Check the relevant C file in `reference/` for exact format strings +3. Add regex patterns to `monop_parser.py` +4. Capture the failing game log and use it as test data +5. **Important:** If you modify the root `monop_parser.py`, also update `plugins/monop/monop_parser.py` (it's a separate copy) + +--- + +## Key Design Decisions + +- **Flat Python layout** — Core files live at the repo root so imports work without packaging. Tests use `sys.path.insert(0, '.')` which is pragmatic for a project of this scope. +- **Parser has its own copy in the plugin** — The Cardinal plugin loads `monop_parser.py` via `importlib.util` from its own directory, making it self-contained and deployable via symlink. The tradeoff is keeping two copies in sync. +- **Tab-separated log format** — The parser expects `TIMESTAMP\tSENDER\tMESSAGE`. This matches how Cardinal and the bridge produce log lines. +- **game-state.json as the interface** — The parser writes JSON, the web viewer reads JSON. They're decoupled — you could replace either side independently. +- **Checkpoint reconciliation** — Rather than trusting incremental state tracking alone, the parser validates against checkpoint lines every turn. This makes it self-correcting.