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