Show players during setup: empty slots and registration progress
Parser changes:
- Track num_players_expected from user input after 'How many players?'
- Create placeholder players ('Player N') on 'say me please' prompts
- Replace placeholder names when real names appear in roll lines
- Emit state during setup phase (was returning None)
- Include phase and numPlayersExpected in state JSON
UI changes:
- Empty slots shown as dashed/dimmed panels with '?' token
- Placeholder players shown as 'Registering...'
- Registered players shown with checkmark and ,500
- Status bar shows 'Setting up · 2/3 players registered'
This commit is contained in:
parent
435b24bfb8
commit
72d996cb4b
5 changed files with 310 additions and 179 deletions
Binary file not shown.
|
|
@ -107,6 +107,7 @@ class GameState:
|
|||
self.card_lines = []
|
||||
self.setup_names = []
|
||||
self.setup_rolls = []
|
||||
self.num_players_expected = 0 # from "How many players?"
|
||||
self.game_active = False
|
||||
# Track spec flag (chance card: nearest RR/utility)
|
||||
self.spec = False
|
||||
|
|
@ -238,6 +239,23 @@ class MonopParser:
|
|||
def _new_game(self):
|
||||
self.game = GameState()
|
||||
self.games.append(self.game)
|
||||
self._awaiting_player_count = True
|
||||
|
||||
def _handle_setup_input(self, sender, msg, timestamp):
|
||||
"""Handle user input during setup phase."""
|
||||
g = self.game
|
||||
if not g:
|
||||
return
|
||||
# Capture player count (first numeric input after "How many players?")
|
||||
if hasattr(self, '_awaiting_player_count') and self._awaiting_player_count:
|
||||
m = re.match(r'^(\d+)$', msg.strip())
|
||||
if m:
|
||||
count = int(m.group(1))
|
||||
if 1 <= count <= 9:
|
||||
g.num_players_expected = count
|
||||
self._awaiting_player_count = False
|
||||
g.add_log(f"Game for {count} players", timestamp=timestamp)
|
||||
return
|
||||
|
||||
def parse_line(self, line):
|
||||
"""Parse a single IRC log line. Returns any events generated."""
|
||||
|
|
@ -254,12 +272,14 @@ class MonopParser:
|
|||
message_raw = message_full.rstrip()
|
||||
message = message_full.strip()
|
||||
|
||||
# Track user input for resign target detection
|
||||
# Track user input
|
||||
if sender != "monop":
|
||||
# Store last user input (strip bot prefix '.')
|
||||
user_msg = message.lstrip('.')
|
||||
if user_msg:
|
||||
self._last_user_input = user_msg
|
||||
# During setup, capture player count and registrations
|
||||
if self.game and self.game.phase == "setup":
|
||||
self._handle_setup_input(sender, user_msg, timestamp)
|
||||
return
|
||||
|
||||
self._process_bot_line(message, timestamp, message_raw)
|
||||
|
|
@ -414,10 +434,16 @@ class MonopParser:
|
|||
m = self.PLAYER_ROLLS_RE.match(msg)
|
||||
if m:
|
||||
name, num, roll_val = m.group(1), int(m.group(2)), int(m.group(3))
|
||||
# Ensure player exists
|
||||
if not g.get_player(name=name):
|
||||
existing = g.get_player(number=num)
|
||||
if existing:
|
||||
# Update placeholder name with real name
|
||||
if existing.name.startswith("Player "):
|
||||
existing.name = name
|
||||
g.add_log(f"{name} registered!", player=name, timestamp=timestamp)
|
||||
elif not g.get_player(name=name):
|
||||
p = Player(name, num)
|
||||
g.players.append(p)
|
||||
g.add_log(f"{name} registered!", player=name, timestamp=timestamp)
|
||||
return
|
||||
|
||||
m = self.GOES_FIRST_RE.match(msg)
|
||||
|
|
@ -437,9 +463,14 @@ class MonopParser:
|
|||
g.add_log(f"Game started! {name} goes first", timestamp=timestamp)
|
||||
return
|
||||
|
||||
# "Player N, say 'me' please" - just note it
|
||||
# "Player N, say 'me' please" - create placeholder
|
||||
m = self.SAY_ME_RE.match(msg)
|
||||
if m:
|
||||
num = int(m.group(1))
|
||||
if not g.get_player(number=num):
|
||||
p = Player(f"Player {num}", num)
|
||||
g.players.append(p)
|
||||
g.add_log(f"Waiting for Player {num} to register...", timestamp=timestamp)
|
||||
return
|
||||
return
|
||||
|
||||
|
|
@ -1209,6 +1240,17 @@ class MonopParser:
|
|||
if not self.game:
|
||||
return None
|
||||
g = self.game
|
||||
|
||||
# During setup, emit partial state so the UI can show registering players
|
||||
if g.phase == "setup":
|
||||
return {
|
||||
"players": [p.to_dict() for p in g.players],
|
||||
"currentPlayer": None,
|
||||
"squares": [{"id": sq["id"], "name": sq["name"], "type": sq["type"]} for sq in g.squares],
|
||||
"log": g.log[-30:],
|
||||
"phase": "setup",
|
||||
"numPlayersExpected": g.num_players_expected,
|
||||
}
|
||||
squares = []
|
||||
for sq in g.squares:
|
||||
sq_out = {"id": sq["id"], "name": sq["name"], "type": sq["type"]}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ class GameState:
|
|||
self.card_lines = []
|
||||
self.setup_names = []
|
||||
self.setup_rolls = []
|
||||
self.num_players_expected = 0 # from "How many players?"
|
||||
self.game_active = False
|
||||
# Track spec flag (chance card: nearest RR/utility)
|
||||
self.spec = False
|
||||
|
|
@ -238,6 +239,23 @@ class MonopParser:
|
|||
def _new_game(self):
|
||||
self.game = GameState()
|
||||
self.games.append(self.game)
|
||||
self._awaiting_player_count = True
|
||||
|
||||
def _handle_setup_input(self, sender, msg, timestamp):
|
||||
"""Handle user input during setup phase."""
|
||||
g = self.game
|
||||
if not g:
|
||||
return
|
||||
# Capture player count (first numeric input after "How many players?")
|
||||
if hasattr(self, '_awaiting_player_count') and self._awaiting_player_count:
|
||||
m = re.match(r'^(\d+)$', msg.strip())
|
||||
if m:
|
||||
count = int(m.group(1))
|
||||
if 1 <= count <= 9:
|
||||
g.num_players_expected = count
|
||||
self._awaiting_player_count = False
|
||||
g.add_log(f"Game for {count} players", timestamp=timestamp)
|
||||
return
|
||||
|
||||
def parse_line(self, line):
|
||||
"""Parse a single IRC log line. Returns any events generated."""
|
||||
|
|
@ -254,12 +272,14 @@ class MonopParser:
|
|||
message_raw = message_full.rstrip()
|
||||
message = message_full.strip()
|
||||
|
||||
# Track user input for resign target detection
|
||||
# Track user input
|
||||
if sender != "monop":
|
||||
# Store last user input (strip bot prefix '.')
|
||||
user_msg = message.lstrip('.')
|
||||
if user_msg:
|
||||
self._last_user_input = user_msg
|
||||
# During setup, capture player count and registrations
|
||||
if self.game and self.game.phase == "setup":
|
||||
self._handle_setup_input(sender, user_msg, timestamp)
|
||||
return
|
||||
|
||||
self._process_bot_line(message, timestamp, message_raw)
|
||||
|
|
@ -414,10 +434,16 @@ class MonopParser:
|
|||
m = self.PLAYER_ROLLS_RE.match(msg)
|
||||
if m:
|
||||
name, num, roll_val = m.group(1), int(m.group(2)), int(m.group(3))
|
||||
# Ensure player exists
|
||||
if not g.get_player(name=name):
|
||||
existing = g.get_player(number=num)
|
||||
if existing:
|
||||
# Update placeholder name with real name
|
||||
if existing.name.startswith("Player "):
|
||||
existing.name = name
|
||||
g.add_log(f"{name} registered!", player=name, timestamp=timestamp)
|
||||
elif not g.get_player(name=name):
|
||||
p = Player(name, num)
|
||||
g.players.append(p)
|
||||
g.add_log(f"{name} registered!", player=name, timestamp=timestamp)
|
||||
return
|
||||
|
||||
m = self.GOES_FIRST_RE.match(msg)
|
||||
|
|
@ -437,9 +463,14 @@ class MonopParser:
|
|||
g.add_log(f"Game started! {name} goes first", timestamp=timestamp)
|
||||
return
|
||||
|
||||
# "Player N, say 'me' please" - just note it
|
||||
# "Player N, say 'me' please" - create placeholder
|
||||
m = self.SAY_ME_RE.match(msg)
|
||||
if m:
|
||||
num = int(m.group(1))
|
||||
if not g.get_player(number=num):
|
||||
p = Player(f"Player {num}", num)
|
||||
g.players.append(p)
|
||||
g.add_log(f"Waiting for Player {num} to register...", timestamp=timestamp)
|
||||
return
|
||||
return
|
||||
|
||||
|
|
@ -1209,6 +1240,17 @@ class MonopParser:
|
|||
if not self.game:
|
||||
return None
|
||||
g = self.game
|
||||
|
||||
# During setup, emit partial state so the UI can show registering players
|
||||
if g.phase == "setup":
|
||||
return {
|
||||
"players": [p.to_dict() for p in g.players],
|
||||
"currentPlayer": None,
|
||||
"squares": [{"id": sq["id"], "name": sq["name"], "type": sq["type"]} for sq in g.squares],
|
||||
"log": g.log[-30:],
|
||||
"phase": "setup",
|
||||
"numPlayersExpected": g.num_players_expected,
|
||||
}
|
||||
squares = []
|
||||
for sq in g.squares:
|
||||
sq_out = {"id": sq["id"], "name": sq["name"], "type": sq["type"]}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,10 @@
|
|||
{
|
||||
"players": [
|
||||
{
|
||||
"name": "alice",
|
||||
"number": 1,
|
||||
"money": 919,
|
||||
"location": 25,
|
||||
"inJail": false,
|
||||
"jailTurns": 0,
|
||||
"doublesCount": 0,
|
||||
"getOutOfJailFreeCards": 0
|
||||
},
|
||||
{
|
||||
"name": "bob",
|
||||
"number": 2,
|
||||
"money": 1292,
|
||||
"location": 37,
|
||||
"money": 966,
|
||||
"location": 2,
|
||||
"inJail": false,
|
||||
"jailTurns": 0,
|
||||
"doublesCount": 0,
|
||||
|
|
@ -23,9 +13,19 @@
|
|||
{
|
||||
"name": "charlie",
|
||||
"number": 3,
|
||||
"money": 320,
|
||||
"location": 40,
|
||||
"inJail": true,
|
||||
"money": 349,
|
||||
"location": 0,
|
||||
"inJail": false,
|
||||
"jailTurns": 0,
|
||||
"doublesCount": 0,
|
||||
"getOutOfJailFreeCards": 0
|
||||
},
|
||||
{
|
||||
"name": "alice",
|
||||
"number": 1,
|
||||
"money": 276,
|
||||
"location": 14,
|
||||
"inJail": false,
|
||||
"jailTurns": 0,
|
||||
"doublesCount": 0,
|
||||
"getOutOfJailFreeCards": 1
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"id": 1,
|
||||
"name": "Mediterranean ave. (P)",
|
||||
"type": "property",
|
||||
"owner": 2,
|
||||
"owner": 3,
|
||||
"mortgaged": false,
|
||||
"group": "purple",
|
||||
"cost": 60,
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
"id": 3,
|
||||
"name": "Baltic ave. (P)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": 1,
|
||||
"mortgaged": false,
|
||||
"group": "purple",
|
||||
"cost": 60,
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
"id": 5,
|
||||
"name": "Reading RR",
|
||||
"type": "railroad",
|
||||
"owner": 2,
|
||||
"owner": 1,
|
||||
"mortgaged": false,
|
||||
"group": "railroad",
|
||||
"cost": 200
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
"id": 8,
|
||||
"name": "Vermont ave. (L)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "lightblue",
|
||||
"cost": 100,
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
"id": 9,
|
||||
"name": "Connecticut ave. (L)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "lightblue",
|
||||
"cost": 120,
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
"id": 11,
|
||||
"name": "St. Charles pl. (V)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": 2,
|
||||
"mortgaged": false,
|
||||
"group": "violet",
|
||||
"cost": 140,
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
"id": 12,
|
||||
"name": "Electric Co.",
|
||||
"type": "utility",
|
||||
"owner": 2,
|
||||
"owner": 3,
|
||||
"mortgaged": false,
|
||||
"group": "utility",
|
||||
"cost": 150
|
||||
|
|
@ -140,7 +140,7 @@
|
|||
"id": 13,
|
||||
"name": "States ave. (V)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": 1,
|
||||
"mortgaged": false,
|
||||
"group": "violet",
|
||||
"cost": 140,
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
"id": 14,
|
||||
"name": "Virginia ave. (V)",
|
||||
"type": "property",
|
||||
"owner": 2,
|
||||
"owner": 3,
|
||||
"mortgaged": false,
|
||||
"group": "violet",
|
||||
"cost": 160,
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
"id": 15,
|
||||
"name": "Pennsylvania RR",
|
||||
"type": "railroad",
|
||||
"owner": 1,
|
||||
"owner": 3,
|
||||
"mortgaged": false,
|
||||
"group": "railroad",
|
||||
"cost": 200
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
"id": 16,
|
||||
"name": "St. James pl. (O)",
|
||||
"type": "property",
|
||||
"owner": 2,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "orange",
|
||||
"cost": 180,
|
||||
|
|
@ -209,7 +209,7 @@
|
|||
"id": 21,
|
||||
"name": "Kentucky ave. (R)",
|
||||
"type": "property",
|
||||
"owner": 1,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "red",
|
||||
"cost": 220,
|
||||
|
|
@ -224,7 +224,7 @@
|
|||
"id": 23,
|
||||
"name": "Indiana ave. (R)",
|
||||
"type": "property",
|
||||
"owner": 1,
|
||||
"owner": 3,
|
||||
"mortgaged": false,
|
||||
"group": "red",
|
||||
"cost": 220,
|
||||
|
|
@ -234,7 +234,7 @@
|
|||
"id": 24,
|
||||
"name": "Illinois ave. (R)",
|
||||
"type": "property",
|
||||
"owner": 1,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "red",
|
||||
"cost": 240,
|
||||
|
|
@ -263,7 +263,7 @@
|
|||
"id": 27,
|
||||
"name": "Ventnor ave. (Y)",
|
||||
"type": "property",
|
||||
"owner": 1,
|
||||
"owner": 3,
|
||||
"mortgaged": false,
|
||||
"group": "yellow",
|
||||
"cost": 260,
|
||||
|
|
@ -273,7 +273,7 @@
|
|||
"id": 28,
|
||||
"name": "Water Works",
|
||||
"type": "utility",
|
||||
"owner": 2,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "utility",
|
||||
"cost": 150
|
||||
|
|
@ -282,7 +282,7 @@
|
|||
"id": 29,
|
||||
"name": "Marvin Gardens (Y)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": null,
|
||||
"mortgaged": false,
|
||||
"group": "yellow",
|
||||
"cost": 280,
|
||||
|
|
@ -307,7 +307,7 @@
|
|||
"id": 32,
|
||||
"name": "N. Carolina ave. (G)",
|
||||
"type": "property",
|
||||
"owner": 2,
|
||||
"owner": 1,
|
||||
"mortgaged": false,
|
||||
"group": "green",
|
||||
"cost": 300,
|
||||
|
|
@ -322,7 +322,7 @@
|
|||
"id": 34,
|
||||
"name": "Pennsylvania ave. (G)",
|
||||
"type": "property",
|
||||
"owner": 3,
|
||||
"owner": 1,
|
||||
"mortgaged": false,
|
||||
"group": "green",
|
||||
"cost": 320,
|
||||
|
|
@ -346,7 +346,7 @@
|
|||
"id": 37,
|
||||
"name": "Park place (D)",
|
||||
"type": "property",
|
||||
"owner": 1,
|
||||
"owner": 2,
|
||||
"mortgaged": false,
|
||||
"group": "darkblue",
|
||||
"cost": 350,
|
||||
|
|
@ -370,149 +370,152 @@
|
|||
],
|
||||
"log": [
|
||||
{
|
||||
"text": "charlie's turn \u2014 $320 on Electric Co.",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:37"
|
||||
"text": "bob's turn \u2014 $1138 on New York ave. (O)",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:24:23"
|
||||
},
|
||||
{
|
||||
"text": "roll is 4, 6",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:38"
|
||||
"text": "roll is 6, 2",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:24:24"
|
||||
},
|
||||
{
|
||||
"text": "Landed on Chance ii",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:39"
|
||||
"text": "Landed on Ventnor ave. (Y)",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:24:25"
|
||||
},
|
||||
{
|
||||
"text": "Go Back 3 Spaces",
|
||||
"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": "Landed on New York ave. (O)",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:41"
|
||||
},
|
||||
{
|
||||
"text": "alice's turn \u2014 $912 on States ave. (V)",
|
||||
"text": "alice's turn \u2014 $88 on Pennsylvania ave. (G)",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:03:42"
|
||||
"timestamp": "2026-02-21 11:24:29"
|
||||
},
|
||||
{
|
||||
"text": "roll is 1, 2",
|
||||
"text": "roll is 3, 3",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:03:43"
|
||||
"timestamp": "2026-02-21 11:24:31"
|
||||
},
|
||||
{
|
||||
"text": "Landed on St. James pl. (O)",
|
||||
"text": "Passed GO \u2014 collected $200",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:03:43"
|
||||
"timestamp": "2026-02-21 11:24:31"
|
||||
},
|
||||
{
|
||||
"text": "Paid $14 rent to bob",
|
||||
"player": "alice"
|
||||
},
|
||||
{
|
||||
"text": "bob's turn \u2014 $1113 on Pennsylvania RR",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:03:45"
|
||||
},
|
||||
{
|
||||
"text": "roll is 5, 1",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:03:46"
|
||||
},
|
||||
{
|
||||
"text": "Landed on Kentucky ave. (R)",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:03:47"
|
||||
},
|
||||
{
|
||||
"text": "Paid $36 rent to alice",
|
||||
"player": "bob"
|
||||
},
|
||||
{
|
||||
"text": "charlie's turn \u2014 $320 on New York ave. (O)",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:48"
|
||||
},
|
||||
{
|
||||
"text": "roll is 6, 5",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:50"
|
||||
},
|
||||
{
|
||||
"text": "Landed on GO TO JAIL!",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:03:50"
|
||||
},
|
||||
{
|
||||
"text": "alice's turn \u2014 $934 on St. James pl. (O)",
|
||||
"text": "Landed on === GO ===",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:03:51"
|
||||
"timestamp": "2026-02-21 11:24:32"
|
||||
},
|
||||
{
|
||||
"text": "roll is 5, 4",
|
||||
"text": "alice's turn \u2014 $288 on === GO ===",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:03:52"
|
||||
"timestamp": "2026-02-21 11:24:33"
|
||||
},
|
||||
{
|
||||
"text": "Landed on B&O RR",
|
||||
"text": "roll is 2, 1",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:03:52"
|
||||
"timestamp": "2026-02-21 11:24:34"
|
||||
},
|
||||
{
|
||||
"text": "Paid $50 rent to bob",
|
||||
"player": "alice"
|
||||
"text": "Landed on Baltic ave. (P)",
|
||||
"player": "alice",
|
||||
"timestamp": "2026-02-21 11:24:35"
|
||||
},
|
||||
{
|
||||
"text": "bob's turn \u2014 $1127 on Kentucky ave. (R)",
|
||||
"text": "bob's turn \u2014 $1116 on Ventnor ave. (Y)",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:03:54"
|
||||
"timestamp": "2026-02-21 11:24:36"
|
||||
},
|
||||
{
|
||||
"text": "bob's turn \u2014 $1127 on Kentucky ave. (R)",
|
||||
"text": "roll is 4, 6",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:04:04"
|
||||
},
|
||||
{
|
||||
"text": "roll is 6, 6",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:04:05"
|
||||
},
|
||||
{
|
||||
"text": "Landed on Community Chest iii",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:04:06"
|
||||
},
|
||||
{
|
||||
"text": "Bank Error in Your Favor.",
|
||||
"player": "bob"
|
||||
},
|
||||
{
|
||||
"text": "bob's turn \u2014 $1327 on Community Chest iii",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:04:09"
|
||||
},
|
||||
{
|
||||
"text": "roll is 2, 2",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:04:10"
|
||||
"timestamp": "2026-02-21 11:24:37"
|
||||
},
|
||||
{
|
||||
"text": "Landed on Park place (D)",
|
||||
"player": "bob",
|
||||
"timestamp": "2026-02-21 11:04:10"
|
||||
"timestamp": "2026-02-21 11:24:38"
|
||||
},
|
||||
{
|
||||
"text": "Paid $35 rent to alice",
|
||||
"player": "bob"
|
||||
"text": "charlie's turn \u2014 $137 on N. Carolina ave. (G)",
|
||||
"player": "charlie",
|
||||
"timestamp": "2026-02-21 11:24:40"
|
||||
},
|
||||
{
|
||||
"text": "bob's turn \u2014 $1292 on Park place (D)",
|
||||
"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:04:12"
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"lastUpdated": "2026-02-21T11:04:14.993563+00:00"
|
||||
"lastUpdated": "2026-02-21T11:24:48.953583+00:00"
|
||||
}
|
||||
|
|
@ -406,12 +406,40 @@ function renderPlayers(state) {
|
|||
container.innerHTML = '';
|
||||
const players = state.players || [];
|
||||
const squares = state.squares || [];
|
||||
const isSetup = state.phase === 'setup';
|
||||
const expected = state.numPlayersExpected || 0;
|
||||
|
||||
players.forEach((p, idx) => {
|
||||
// During setup, show slots for all expected players
|
||||
const slotCount = isSetup ? Math.max(players.length, expected) : players.length;
|
||||
|
||||
for (let idx = 0; idx < slotCount; idx++) {
|
||||
const p = players[idx]; // may be undefined for empty slots
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'player-panel' + (p.number === state.currentPlayer ? ' current-turn' : '');
|
||||
|
||||
const color = PLAYER_COLORS[idx % PLAYER_COLORS.length];
|
||||
|
||||
if (!p) {
|
||||
// Empty slot — waiting for registration
|
||||
panel.className = 'player-panel';
|
||||
panel.style.opacity = '0.4';
|
||||
panel.style.borderStyle = 'dashed';
|
||||
panel.innerHTML = `<h3><span class="player-token" style="background:#555;width:20px;height:20px;font-size:11px">?</span> Player ${idx + 1}</h3>
|
||||
<div style="color:#555;font-size:0.9em">Waiting to join...</div>`;
|
||||
} else if (isSetup && p.name.startsWith('Player ')) {
|
||||
// Placeholder — registered slot but no name yet
|
||||
panel.className = 'player-panel';
|
||||
panel.style.opacity = '0.6';
|
||||
panel.style.borderStyle = 'dashed';
|
||||
panel.innerHTML = `<h3><span class="player-token" style="background:${color};width:20px;height:20px;font-size:11px;opacity:0.5">?</span> ${p.name}</h3>
|
||||
<div style="color:#888;font-size:0.9em">Registering...</div>`;
|
||||
} else if (isSetup) {
|
||||
// Registered player during setup
|
||||
panel.className = 'player-panel';
|
||||
panel.innerHTML = `<h3><span class="player-token" style="background:${color};width:20px;height:20px;font-size:11px">${p.name.charAt(0).toUpperCase()}</span> ${p.name} ✓</h3>
|
||||
<div class="money">$1,500</div>
|
||||
<div style="font-size:0.8em;margin-top:4px;color:#888">Ready to play</div>`;
|
||||
} else {
|
||||
// Normal playing state
|
||||
panel.className = 'player-panel' + (p.number === state.currentPlayer ? ' current-turn' : '');
|
||||
let html = `<h3><span class="player-token" style="background:${color};width:20px;height:20px;font-size:11px">${p.name.charAt(0).toUpperCase()}</span> ${p.name}`;
|
||||
if (p.number === state.currentPlayer) html += ' 🎲';
|
||||
html += '</h3>';
|
||||
|
|
@ -430,7 +458,6 @@ function renderPlayers(state) {
|
|||
html += `<div style="font-size:0.8em;margin-top:4px;color:#aaa">📍 ${loc.name}</div>`;
|
||||
}
|
||||
|
||||
// Properties owned by this player
|
||||
const owned = squares.filter(sq => sq.owner === p.number);
|
||||
if (owned.length > 0) {
|
||||
html += '<div class="props">';
|
||||
|
|
@ -446,8 +473,10 @@ function renderPlayers(state) {
|
|||
}
|
||||
|
||||
panel.innerHTML = html;
|
||||
}
|
||||
|
||||
container.appendChild(panel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderLog(state) {
|
||||
|
|
@ -626,14 +655,29 @@ async function update() {
|
|||
checkStale();
|
||||
|
||||
// Determine what to show
|
||||
if (!state.players || state.players.length === 0) {
|
||||
// No players = no game
|
||||
if (!state.players || (state.players.length === 0 && !state.phase)) {
|
||||
// No players and no phase = no game
|
||||
showView('zero');
|
||||
lastUpdate = '';
|
||||
gameOverShown = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup phase — show registration
|
||||
if (state.phase === 'setup') {
|
||||
const updateStr = JSON.stringify(state);
|
||||
if (updateStr !== lastUpdate) {
|
||||
lastUpdate = updateStr;
|
||||
showView('playing');
|
||||
renderGame(state);
|
||||
}
|
||||
const registered = (state.players || []).filter(p => !p.name.startsWith('Player ')).length;
|
||||
const expected = state.numPlayersExpected || '?';
|
||||
document.getElementById('status').textContent =
|
||||
`Setting up · ${registered}/${expected} players registered`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for game over
|
||||
if (isGameOver(state)) {
|
||||
const updateStr = JSON.stringify(state);
|
||||
|
|
|
|||
Loading…
Reference in a new issue