Covers system goals, component design, directory structure, testing strategies, and key design decisions for future agents.
10 KiB
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:
- Cardinal plugin mode — A plugin for Cardinal (a Python IRC bot) watches game messages in an IRC channel and writes
game-state.jsonon every state change. - Standalone bridge mode —
monop_bridge.pyspawns themonopbinary as a subprocess and bridges its stdin/stdout to IRC, whilemonop_players.pyprovides autopilot player bots. - Web viewer —
site/index.htmlreadsgame-state.jsonand 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. Callparse_line(log_line)andget_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:
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:
.monopor.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:
# 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:
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 logicrent.c— Rent calculation and messageshouses.c— House/hotel buyingjail.c— Jail mechanicscards.c/cards.inp— Chance and Community Chest cardsprint.c— Board/player printing (checkpoint format)brd.dat— Board square definitionsprop.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.
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.
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.
python3 plugins/test_plugin.py
Integration Tests (test_integration.py)
Runs a real game against a live monop-irc setup. Requires:
- An IRC server (e.g., InspIRCd) running on localhost:6667
- The monop bridge running (
monop_bridge.py)
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:
- Start an IRC server
- Start the bridge:
python3 monop_bridge.py - Start player bots:
python3 monop_players.py --players alice,bob,charlie - Start the web viewer:
cd site && python3 -m http.server 9998 - 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:
- Check
docs/OUTPUT_CATALOG.md— it catalogs every possible output line from the C source - Check the relevant C file in
reference/for exact format strings - Add regex patterns to
monop_parser.py - Capture the failing game log and use it as test data
- Important: If you modify the root
monop_parser.py, also updateplugins/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.pyviaimportlib.utilfrom 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.