Fix setup visibility: bridge waits for players, observer sees registration
Bridge changes: - Wait for at least one user to JOIN before starting monop - Ensures observer is in channel to see all setup messages Parser changes: - Handle 'Player N, say me' even without prior 'How many players?' - Infer num_players_expected from highest player number seen - Emit state during setup phase run_game.py changes: - 3s stagger between bot joins so setup is visible in web UI - Observer connects before bots to catch all registration messages
This commit is contained in:
parent
72d996cb4b
commit
6d055c68a2
6 changed files with 107 additions and 330 deletions
Binary file not shown.
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
12
run_game.py
12
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in a new issue