# 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.