diff --git a/__pycache__/monop_parser.cpython-310.pyc b/__pycache__/monop_parser.cpython-310.pyc index d5bad55..ee65d83 100644 Binary files a/__pycache__/monop_parser.cpython-310.pyc and b/__pycache__/monop_parser.cpython-310.pyc differ diff --git a/monop_bridge.py b/monop_bridge.py index d995bf3..10a8085 100644 --- a/monop_bridge.py +++ b/monop_bridge.py @@ -90,8 +90,27 @@ class IRCBridge: print("[bridge] monop process died, restarting...") self.start_monop() + def _wait_for_players(self): + """Wait until at least one other user joins the channel before starting.""" + print(f"[bridge] Waiting for players to join {CHANNEL}...") + while True: + data = self.sock.recv(4096) + if not data: + raise ConnectionError("IRC connection lost while waiting") + self.buffer += data.decode("utf-8", errors="replace") + while "\r\n" in self.buffer: + line, self.buffer = self.buffer.split("\r\n", 1) + if line.startswith("PING"): + self.irc_send("PONG" + line[4:]) + # Detect JOIN from someone other than us + m = re.match(r":(\S+?)!\S+ JOIN", line) + if m and m.group(1) != NICK: + print(f"[bridge] {m.group(1)} joined — starting game") + return + def run(self): self.connect_irc() + self._wait_for_players() self.start_monop() while True: diff --git a/monop_parser.py b/monop_parser.py index afa4255..9a610fe 100644 --- a/monop_parser.py +++ b/monop_parser.py @@ -408,7 +408,19 @@ class MonopParser: return if g is None: - # No game yet - try to pick up from checkpoint + # No game yet - try to pick up from setup or checkpoint + m = self.SAY_ME_RE.match(msg) + if m: + self._new_game() + g = self.game + g.phase = "setup" + num = int(m.group(1)) + g.num_players_expected = num + p = Player(f"Player {num}", num) + g.players.append(p) + g.add_log(f"Waiting for Player {num} to register...", timestamp=timestamp) + return + m = self.CHECKPOINT_RE.match(msg) if m: self._new_game() @@ -471,6 +483,9 @@ class MonopParser: p = Player(f"Player {num}", num) g.players.append(p) g.add_log(f"Waiting for Player {num} to register...", timestamp=timestamp) + # Update expected count (in case we missed "How many players?") + if num > g.num_players_expected: + g.num_players_expected = num return return diff --git a/plugins/monop/monop_parser.py b/plugins/monop/monop_parser.py index afa4255..9a610fe 100644 --- a/plugins/monop/monop_parser.py +++ b/plugins/monop/monop_parser.py @@ -408,7 +408,19 @@ class MonopParser: return if g is None: - # No game yet - try to pick up from checkpoint + # No game yet - try to pick up from setup or checkpoint + m = self.SAY_ME_RE.match(msg) + if m: + self._new_game() + g = self.game + g.phase = "setup" + num = int(m.group(1)) + g.num_players_expected = num + p = Player(f"Player {num}", num) + g.players.append(p) + g.add_log(f"Waiting for Player {num} to register...", timestamp=timestamp) + return + m = self.CHECKPOINT_RE.match(msg) if m: self._new_game() @@ -471,6 +483,9 @@ class MonopParser: p = Player(f"Player {num}", num) g.players.append(p) g.add_log(f"Waiting for Player {num} to register...", timestamp=timestamp) + # Update expected count (in case we missed "How many players?") + if num > g.num_players_expected: + g.num_players_expected = num return return diff --git a/run_game.py b/run_game.py index 93b5ee6..7cbcbd1 100644 --- a/run_game.py +++ b/run_game.py @@ -82,21 +82,23 @@ class ParserClient: def main(): player_names = ["alice", "bob", "charlie"] - # Start parser client + # 1. Start parser/observer client FIRST so it sees setup messages pc = ParserClient() parser_thread = threading.Thread(target=pc.run, daemon=True) parser_thread.start() - print(f"[game] Parser writing to {STATE_PATH}") - time.sleep(2) + print(f"[game] Observer connected, writing to {STATE_PATH}") - # Start player bots + # 2. Wait for observer to be fully joined before bots trigger setup + time.sleep(3) + + # 3. Start player bots (they'll trigger setup by sending player count) bots = [] for i, name in enumerate(player_names): bot = PlayerBot(name, CHANNEL, HOST, PORT, player_names, i) t = threading.Thread(target=bot.run, daemon=True) t.start() bots.append(bot) - time.sleep(0.5) + time.sleep(3.0) # stagger joins so setup is visible in the web UI print(f"[game] {len(bots)} player bots started") diff --git a/site/game-state.json b/site/game-state.json index 08e36d1..ad1321a 100644 --- a/site/game-state.json +++ b/site/game-state.json @@ -1,19 +1,9 @@ { "players": [ { - "name": "bob", - "number": 2, - "money": 966, - "location": 2, - "inJail": false, - "jailTurns": 0, - "doublesCount": 0, - "getOutOfJailFreeCards": 0 - }, - { - "name": "charlie", - "number": 3, - "money": 349, + "name": "Player 1", + "number": 1, + "money": 1500, "location": 0, "inJail": false, "jailTurns": 0, @@ -21,17 +11,17 @@ "getOutOfJailFreeCards": 0 }, { - "name": "alice", - "number": 1, - "money": 276, - "location": 14, + "name": "Player 2", + "number": 2, + "money": 1500, + "location": 0, "inJail": false, "jailTurns": 0, "doublesCount": 0, - "getOutOfJailFreeCards": 1 + "getOutOfJailFreeCards": 0 } ], - "currentPlayer": 2, + "currentPlayer": null, "squares": [ { "id": 0, @@ -41,12 +31,7 @@ { "id": 1, "name": "Mediterranean ave. (P)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "purple", - "cost": 60, - "houses": 0 + "type": "property" }, { "id": 2, @@ -56,12 +41,7 @@ { "id": 3, "name": "Baltic ave. (P)", - "type": "property", - "owner": 1, - "mortgaged": false, - "group": "purple", - "cost": 60, - "houses": 0 + "type": "property" }, { "id": 4, @@ -71,21 +51,12 @@ { "id": 5, "name": "Reading RR", - "type": "railroad", - "owner": 1, - "mortgaged": false, - "group": "railroad", - "cost": 200 + "type": "railroad" }, { "id": 6, "name": "Oriental ave. (L)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "lightblue", - "cost": 100, - "houses": 0 + "type": "property" }, { "id": 7, @@ -95,22 +66,12 @@ { "id": 8, "name": "Vermont ave. (L)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "lightblue", - "cost": 100, - "houses": 0 + "type": "property" }, { "id": 9, "name": "Connecticut ave. (L)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "lightblue", - "cost": 120, - "houses": 0 + "type": "property" }, { "id": 10, @@ -120,60 +81,32 @@ { "id": 11, "name": "St. Charles pl. (V)", - "type": "property", - "owner": 2, - "mortgaged": false, - "group": "violet", - "cost": 140, - "houses": 0 + "type": "property" }, { "id": 12, "name": "Electric Co.", - "type": "utility", - "owner": 3, - "mortgaged": false, - "group": "utility", - "cost": 150 + "type": "utility" }, { "id": 13, "name": "States ave. (V)", - "type": "property", - "owner": 1, - "mortgaged": false, - "group": "violet", - "cost": 140, - "houses": 0 + "type": "property" }, { "id": 14, "name": "Virginia ave. (V)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "violet", - "cost": 160, - "houses": 0 + "type": "property" }, { "id": 15, "name": "Pennsylvania RR", - "type": "railroad", - "owner": 3, - "mortgaged": false, - "group": "railroad", - "cost": 200 + "type": "railroad" }, { "id": 16, "name": "St. James pl. (O)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "orange", - "cost": 180, - "houses": 0 + "type": "property" }, { "id": 17, @@ -183,22 +116,12 @@ { "id": 18, "name": "Tennessee ave. (O)", - "type": "property", - "owner": 1, - "mortgaged": false, - "group": "orange", - "cost": 180, - "houses": 0 + "type": "property" }, { "id": 19, "name": "New York ave. (O)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "orange", - "cost": 200, - "houses": 0 + "type": "property" }, { "id": 20, @@ -208,12 +131,7 @@ { "id": 21, "name": "Kentucky ave. (R)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "red", - "cost": 220, - "houses": 0 + "type": "property" }, { "id": 22, @@ -223,70 +141,37 @@ { "id": 23, "name": "Indiana ave. (R)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "red", - "cost": 220, - "houses": 0 + "type": "property" }, { "id": 24, "name": "Illinois ave. (R)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "red", - "cost": 240, - "houses": 0 + "type": "property" }, { "id": 25, "name": "B&O RR", - "type": "railroad", - "owner": 2, - "mortgaged": false, - "group": "railroad", - "cost": 200 + "type": "railroad" }, { "id": 26, "name": "Atlantic ave. (Y)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "yellow", - "cost": 260, - "houses": 0 + "type": "property" }, { "id": 27, "name": "Ventnor ave. (Y)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "yellow", - "cost": 260, - "houses": 0 + "type": "property" }, { "id": 28, "name": "Water Works", - "type": "utility", - "owner": null, - "mortgaged": false, - "group": "utility", - "cost": 150 + "type": "utility" }, { "id": 29, "name": "Marvin Gardens (Y)", - "type": "property", - "owner": null, - "mortgaged": false, - "group": "yellow", - "cost": 280, - "houses": 0 + "type": "property" }, { "id": 30, @@ -296,22 +181,12 @@ { "id": 31, "name": "Pacific ave. (G)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "green", - "cost": 300, - "houses": 0 + "type": "property" }, { "id": 32, "name": "N. Carolina ave. (G)", - "type": "property", - "owner": 1, - "mortgaged": false, - "group": "green", - "cost": 300, - "houses": 0 + "type": "property" }, { "id": 33, @@ -321,21 +196,12 @@ { "id": 34, "name": "Pennsylvania ave. (G)", - "type": "property", - "owner": 1, - "mortgaged": false, - "group": "green", - "cost": 320, - "houses": 0 + "type": "property" }, { "id": 35, "name": "Short Line RR", - "type": "railroad", - "owner": 1, - "mortgaged": false, - "group": "railroad", - "cost": 200 + "type": "railroad" }, { "id": 36, @@ -345,12 +211,7 @@ { "id": 37, "name": "Park place (D)", - "type": "property", - "owner": 2, - "mortgaged": false, - "group": "darkblue", - "cost": 350, - "houses": 0 + "type": "property" }, { "id": 38, @@ -360,162 +221,27 @@ { "id": 39, "name": "Boardwalk (D)", - "type": "property", - "owner": 3, - "mortgaged": false, - "group": "darkblue", - "cost": 400, - "houses": 0 + "type": "property" } ], "log": [ { - "text": "bob's turn \u2014 $1138 on New York ave. (O)", - "player": "bob", - "timestamp": "2026-02-21 11:24:23" + "text": "Game for 3 players", + "player": null, + "timestamp": "2026-02-21 11:33:22" }, { - "text": "roll is 6, 2", - "player": "bob", - "timestamp": "2026-02-21 11:24:24" + "text": "Waiting for Player 1 to register...", + "player": null, + "timestamp": "2026-02-21 11:33:22" }, { - "text": "Landed on Ventnor ave. (Y)", - "player": "bob", - "timestamp": "2026-02-21 11:24:25" - }, - { - "text": "Paid $22 rent to charlie", - "player": "bob" - }, - { - "text": "charlie's turn \u2014 $163 on Ventnor ave. (Y)", - "player": "charlie", - "timestamp": "2026-02-21 11:24:26" - }, - { - "text": "roll is 2, 3", - "player": "charlie", - "timestamp": "2026-02-21 11:24:27" - }, - { - "text": "Landed on N. Carolina ave. (G)", - "player": "charlie", - "timestamp": "2026-02-21 11:24:28" - }, - { - "text": "Paid $26 rent to alice", - "player": "charlie" - }, - { - "text": "alice's turn \u2014 $88 on Pennsylvania ave. (G)", - "player": "alice", - "timestamp": "2026-02-21 11:24:29" - }, - { - "text": "roll is 3, 3", - "player": "alice", - "timestamp": "2026-02-21 11:24:31" - }, - { - "text": "Passed GO \u2014 collected $200", - "player": "alice", - "timestamp": "2026-02-21 11:24:31" - }, - { - "text": "Landed on === GO ===", - "player": "alice", - "timestamp": "2026-02-21 11:24:32" - }, - { - "text": "alice's turn \u2014 $288 on === GO ===", - "player": "alice", - "timestamp": "2026-02-21 11:24:33" - }, - { - "text": "roll is 2, 1", - "player": "alice", - "timestamp": "2026-02-21 11:24:34" - }, - { - "text": "Landed on Baltic ave. (P)", - "player": "alice", - "timestamp": "2026-02-21 11:24:35" - }, - { - "text": "bob's turn \u2014 $1116 on Ventnor ave. (Y)", - "player": "bob", - "timestamp": "2026-02-21 11:24:36" - }, - { - "text": "roll is 4, 6", - "player": "bob", - "timestamp": "2026-02-21 11:24:37" - }, - { - "text": "Landed on Park place (D)", - "player": "bob", - "timestamp": "2026-02-21 11:24:38" - }, - { - "text": "charlie's turn \u2014 $137 on N. Carolina ave. (G)", - "player": "charlie", - "timestamp": "2026-02-21 11:24:40" - }, - { - "text": "roll is 3, 5", - "player": "charlie", - "timestamp": "2026-02-21 11:24:41" - }, - { - "text": "Passed GO \u2014 collected $200", - "player": "charlie", - "timestamp": "2026-02-21 11:24:41" - }, - { - "text": "Landed on === GO ===", - "player": "charlie", - "timestamp": "2026-02-21 11:24:42" - }, - { - "text": "alice's turn \u2014 $288 on Baltic ave. (P)", - "player": "alice", - "timestamp": "2026-02-21 11:24:43" - }, - { - "text": "roll is 6, 5", - "player": "alice", - "timestamp": "2026-02-21 11:24:44" - }, - { - "text": "Landed on Virginia ave. (V)", - "player": "alice", - "timestamp": "2026-02-21 11:24:45" - }, - { - "text": "Paid $12 rent to charlie", - "player": "alice" - }, - { - "text": "bob's turn \u2014 $766 on Park place (D)", - "player": "bob", - "timestamp": "2026-02-21 11:24:46" - }, - { - "text": "roll is 3, 2", - "player": "bob", - "timestamp": "2026-02-21 11:24:47" - }, - { - "text": "Passed GO \u2014 collected $200", - "player": "bob", - "timestamp": "2026-02-21 11:24:48" - }, - { - "text": "Landed on Community Chest i", - "player": "bob", - "timestamp": "2026-02-21 11:24:48" + "text": "Waiting for Player 2 to register...", + "player": null, + "timestamp": "2026-02-21 11:33:23" } ], - "lastUpdated": "2026-02-21T11:24:48.953583+00:00" + "phase": "setup", + "numPlayersExpected": 3, + "lastUpdated": "2026-02-21T11:33:23.055895+00:00" } \ No newline at end of file