From 9c47bac33a5e2108718d4b99d7df8f578765db5f Mon Sep 17 00:00:00 2001 From: Jarvis Date: Sat, 21 Feb 2026 11:54:53 +0000 Subject: [PATCH] Fix setup registration: names appear as each player joins Parser: detect name registration from user input during setup (sender == name constraint prevents false matches on gameplay messages). Bridge: extract _process_buffer() to handle leftover lines after _wait_for_players returns, preventing messages from being stuck. Player bots: proactively send name on join (not just first player sending count) so they respond even if they missed the 'say me' prompt. All tests pass (1551/1553 parser checkpoints, 38 player unit tests). --- __pycache__/monop_parser.cpython-310.pyc | Bin 26467 -> 26619 bytes __pycache__/monop_players.cpython-310.pyc | Bin 12887 -> 12968 bytes monop_bridge.py | 38 ++- monop_parser.py | 11 + monop_players.py | 5 + plugins/monop/monop_parser.py | 11 + site/game-state.json | 361 +++++++++++++++++++--- 7 files changed, 367 insertions(+), 59 deletions(-) diff --git a/__pycache__/monop_parser.cpython-310.pyc b/__pycache__/monop_parser.cpython-310.pyc index ee65d839749bb05129f51e98adcd666eb0c82a86..15a3eeea77ed285542d1daea9df7258c16e71df0 100644 GIT binary patch delta 1406 zcmZA0ZERCj7zgnC+}@Y2Z_I7LCdyjoAZ283Dk12SWz4w_mbF{A%sEF_yd8Ec-8k(; z=A9!#W=zHhb2J2r$@&TTWaJ8{QIUNRF$U)cW2})F!573Ad_~v`p8p*&#y0ntbDrm% zr|0yXd+|KYeN4V#uh%7sPtTFV$$hi0`Hq+S3Pe{EPosD;m-zYe+Z*alX;5bMS;w?u zI81q3%}F_VTs2h@(@b?*H8+s%a-z&mnPXFL=GMz92 z$7Hr&Y?U1l1buwQzd4ZClNlqqGnr*W$l%||yE550%Vzc@vyTT0_=3NZH16#$<+Xvb znhj!rCM%>+p`Y6-c>WA`+Y7d(p!S*gG%(VP71H5D|&wTc>ghi|HA zrDfKEiqA_`_PVIY2kIV{*$L}x-K18163f^N-Ug?@8{h*$-rE>zYTOnHx3qP}I-1y9 z)-#Q(6;VFdTv_riMqz7teOG2@jK%k`(>&k&1ii=GLsN8yUkG)m=R|(JwXx-dN}us7 zZSC%jBxaq))D65jyjJ~HG#{{7xY{loT}M6qTr@=WR#E39QQkV-l_mOt`?|Mkw`Ohc z-lU2%lG?17y5~uhJ=8NCKrSzj)QaaG){62r5wv__@T=CkM+^*}>Rr9EUIc<}b_mH= zz!W&i&-AXj`!?hhwjGhB9~1#r2mV*qD`M|kMURH5`g@G-29qEq2x?0;>p{ zqt)?m*N)j6MTs(1Bs)bzY{I@`UHp@H^BwjvU&i950M;7RZ|#!bx1)}(^4&Y0o51ns z^~Z*HjU-bE_7#R+0Y8GD!By}Z_#ONK{sg~(pTJ+>8u%7`2QGncz)CD!yt)z-ubPxE z+S1h%3x&I)P0?86L^x{SGX-fCY4_HKmeyFLp$FIRZ_K|AZh$#34;H}!xCsc;HLwg6 zf+A1`JirMGfD5=m33UoDqQY|Yi@^$@gBTLW!3>xMxZVs`nBhj+H;v(5FdQ%I1+Dz~ zzG5CTo~Bj&g7InD77=U{hx4MCl~;yG5@~i81!nn2iB4^NE5gzc|QDiO6WO mhn)6{E-$)nDoWwJqA){L33}NJn0boV4BSr!9~!tu#s2^W*b2OYR&$7Cc@S^zu;WE6i7BpByhM4KL0qFREfm)-$Bp zo}4s=#(6+ ztfVpdR%JE4BhOYglIF~t{mt~kt=`xMYfD^&daso^6P}v#?iFhX2lZxA+Y&zs>-Bzks)bTCTFJ48~5ikf&$kiPc^Mkmj ziM5#dZ%v*MwV)Amd18osJU8kb?T8Y6FK?|Wt3tZ#N!l-oDx~kZmwvwn-M@6|*X~y7 zs{E|$upZ-yYjTs7qNn9~Yqq}{m5+l9va4&xLh&5pe1j%L(R;)DP0=IB=T)rYfSwd`+Nc8LD0&W-n(B;p6;&WCof#LCxwl7z3b8&`;r^t;#;Kt04{;c;3x1i zxCVX!zk(~^NAMf?9h?K_!CCMfD8_^TgGkwLPk)&(R2kR5qQ0TAGgiMEXY4u>Zh$|) zP4FkU4Q_!u;BUbHD@9BJ0q_7Q0fj&Z2JnIc@F1Av+K8Bnun0T^G|-8z$H5pl4RD49 z&Z)q$6F6k~ofp_j(I(H>2dPN*$G@t1h9{c%=YNIsTrJ%fPl*pvf;Oh+79{w$?X$A$ uU8BzunYeH%AwH4Y6EkU=d_6HUU~#<}5C>2*B0o(mB#XO0EK7SPso-A@nli8e diff --git a/__pycache__/monop_players.cpython-310.pyc b/__pycache__/monop_players.cpython-310.pyc index 1933d65c0256b5a612b6720428f0ab8cf0ad4ad9..f47e279433a7b0e748359ef949c79a3bae87dc81 100644 GIT binary patch delta 220 zcmcbfvLcl)pO=@50SLa&nwhyWc_ZITHpU;Df3RI=R4d_HAd(`zkdcw0hG7A3iY$~R zR>P3R0b-{J1~X{NZNAD8&B#}!9h{n%l9`vTke8U7x>=A*fRQm^vJ$s18y7PdqX6UN zLhfyhb(1xCo-sCU=I3o?WbB$em2Vki`(zvbKE_F#PxGH-WSq8ns^A4i#^sySg!l6> zW^PtfC}Co}x_P!zKQrUr&Dv^0tc+2U?X*8J8f`Yz*~-K?b@OvQZbrrpn?LIZ0S(;j QX2`|Jp3kAj#mMp>051YV-T(jq delta 168 zcmV;Z09XI0W!Gd5Zw(C!00000+>V)P%wn+)=>`GVv-k$r0S06dQvfL)v-JpD0h4_S z@c~SeZwpNV1pSl33$_7ylX(p00e!Pz4TS*#g_GtErU8PJjSr3ijk6pO$pHb8v*r=d z0Rf`3w-dh%0c5jxAanu&(6j6!j{^a?vwS8J1p!r)k1O&4Ewh3wwE_Wk~<^- diff --git a/monop_bridge.py b/monop_bridge.py index 10a8085..58fa8c4 100644 --- a/monop_bridge.py +++ b/monop_bridge.py @@ -108,34 +108,38 @@ class IRCBridge: print(f"[bridge] {m.group(1)} joined — starting game") return + def _process_buffer(self): + """Process any complete lines in the buffer.""" + 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:]) + continue + m = re.match(r":(\S+?)!(\S+?) PRIVMSG (\S+) :(.+)", line) + if m: + sender_nick = m.group(1) + target = m.group(3) + msg = m.group(4) + if target == CHANNEL and sender_nick != NICK: + if msg.startswith(PREFIX): + cmd = msg[len(PREFIX):] + self._feed_monop(sender_nick, cmd) + def run(self): self.connect_irc() self._wait_for_players() self.start_monop() + # Process any lines buffered during _wait_for_players + self._process_buffer() + while True: try: data = self.sock.recv(4096) if not data: break 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:]) - continue - # Parse PRIVMSG - m = re.match( - r":(\S+?)!(\S+?) PRIVMSG (\S+) :(.+)", line - ) - if m: - sender_nick = m.group(1) - target = m.group(3) - msg = m.group(4) - if target == CHANNEL and sender_nick != NICK: - if msg.startswith(PREFIX): - cmd = msg[len(PREFIX):] - self._feed_monop(sender_nick, cmd) + self._process_buffer() except Exception as e: print(f"[bridge] error: {e}") break diff --git a/monop_parser.py b/monop_parser.py index 9a610fe..a6a9f1e 100644 --- a/monop_parser.py +++ b/monop_parser.py @@ -257,6 +257,17 @@ class MonopParser: g.add_log(f"Game for {count} players", timestamp=timestamp) return + # Name registration — user sends their IRC nick as their name. + # In monop-irc, the bridge sends "nick name" to monop stdin, + # so the player name matches the IRC sender. + name = msg.strip() + if name and not name.isdigit() and name == sender: + for p in g.players: + if p.name.startswith("Player "): + p.name = name + g.add_log(f"{name} joined!", player=name, timestamp=timestamp) + return + def parse_line(self, line): """Parse a single IRC log line. Returns any events generated.""" self.line_num += 1 diff --git a/monop_players.py b/monop_players.py index 2de5acc..7a21512 100644 --- a/monop_players.py +++ b/monop_players.py @@ -117,6 +117,11 @@ class PlayerBot: time.sleep(1.0) self.log("Sending player count (in case we missed the prompt)") self.say(str(self.num_players)) + else: + # Send our name proactively in case we missed the "say me" prompt + time.sleep(1.0) + self.log(f"Sending name (in case we missed the prompt)") + self.say(self.nick) def _send(self, line): with self.lock: diff --git a/plugins/monop/monop_parser.py b/plugins/monop/monop_parser.py index 9a610fe..a6a9f1e 100644 --- a/plugins/monop/monop_parser.py +++ b/plugins/monop/monop_parser.py @@ -257,6 +257,17 @@ class MonopParser: g.add_log(f"Game for {count} players", timestamp=timestamp) return + # Name registration — user sends their IRC nick as their name. + # In monop-irc, the bridge sends "nick name" to monop stdin, + # so the player name matches the IRC sender. + name = msg.strip() + if name and not name.isdigit() and name == sender: + for p in g.players: + if p.name.startswith("Player "): + p.name = name + g.add_log(f"{name} joined!", player=name, timestamp=timestamp) + return + def parse_line(self, line): """Parse a single IRC log line. Returns any events generated.""" self.line_num += 1 diff --git a/site/game-state.json b/site/game-state.json index ad1321a..4fab5e2 100644 --- a/site/game-state.json +++ b/site/game-state.json @@ -1,27 +1,37 @@ { "players": [ { - "name": "Player 1", + "name": "alice", "number": 1, - "money": 1500, - "location": 0, + "money": 1190, + "location": 40, + "inJail": true, + "jailTurns": 1, + "doublesCount": 0, + "getOutOfJailFreeCards": 0 + }, + { + "name": "bob", + "number": 2, + "money": 1300, + "location": 11, "inJail": false, "jailTurns": 0, "doublesCount": 0, "getOutOfJailFreeCards": 0 }, { - "name": "Player 2", - "number": 2, - "money": 1500, - "location": 0, + "name": "charlie", + "number": 3, + "money": 1400, + "location": 8, "inJail": false, "jailTurns": 0, "doublesCount": 0, "getOutOfJailFreeCards": 0 } ], - "currentPlayer": null, + "currentPlayer": 3, "squares": [ { "id": 0, @@ -31,7 +41,12 @@ { "id": 1, "name": "Mediterranean ave. (P)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "purple", + "cost": 60, + "houses": 0 }, { "id": 2, @@ -41,7 +56,12 @@ { "id": 3, "name": "Baltic ave. (P)", - "type": "property" + "type": "property", + "owner": 2, + "mortgaged": false, + "group": "purple", + "cost": 60, + "houses": 0 }, { "id": 4, @@ -51,12 +71,21 @@ { "id": 5, "name": "Reading RR", - "type": "railroad" + "type": "railroad", + "owner": null, + "mortgaged": false, + "group": "railroad", + "cost": 200 }, { "id": 6, "name": "Oriental ave. (L)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "lightblue", + "cost": 100, + "houses": 0 }, { "id": 7, @@ -66,12 +95,22 @@ { "id": 8, "name": "Vermont ave. (L)", - "type": "property" + "type": "property", + "owner": 3, + "mortgaged": false, + "group": "lightblue", + "cost": 100, + "houses": 0 }, { "id": 9, "name": "Connecticut ave. (L)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "lightblue", + "cost": 120, + "houses": 0 }, { "id": 10, @@ -81,32 +120,60 @@ { "id": 11, "name": "St. Charles pl. (V)", - "type": "property" + "type": "property", + "owner": 2, + "mortgaged": false, + "group": "violet", + "cost": 140, + "houses": 0 }, { "id": 12, "name": "Electric Co.", - "type": "utility" + "type": "utility", + "owner": null, + "mortgaged": false, + "group": "utility", + "cost": 150 }, { "id": 13, "name": "States ave. (V)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "violet", + "cost": 140, + "houses": 0 }, { "id": 14, "name": "Virginia ave. (V)", - "type": "property" + "type": "property", + "owner": 1, + "mortgaged": false, + "group": "violet", + "cost": 160, + "houses": 0 }, { "id": 15, "name": "Pennsylvania RR", - "type": "railroad" + "type": "railroad", + "owner": null, + "mortgaged": false, + "group": "railroad", + "cost": 200 }, { "id": 16, "name": "St. James pl. (O)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "orange", + "cost": 180, + "houses": 0 }, { "id": 17, @@ -116,12 +183,22 @@ { "id": 18, "name": "Tennessee ave. (O)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "orange", + "cost": 180, + "houses": 0 }, { "id": 19, "name": "New York ave. (O)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "orange", + "cost": 200, + "houses": 0 }, { "id": 20, @@ -131,7 +208,12 @@ { "id": 21, "name": "Kentucky ave. (R)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "red", + "cost": 220, + "houses": 0 }, { "id": 22, @@ -141,37 +223,70 @@ { "id": 23, "name": "Indiana ave. (R)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "red", + "cost": 220, + "houses": 0 }, { "id": 24, "name": "Illinois ave. (R)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "red", + "cost": 240, + "houses": 0 }, { "id": 25, "name": "B&O RR", - "type": "railroad" + "type": "railroad", + "owner": null, + "mortgaged": false, + "group": "railroad", + "cost": 200 }, { "id": 26, "name": "Atlantic ave. (Y)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "yellow", + "cost": 260, + "houses": 0 }, { "id": 27, "name": "Ventnor ave. (Y)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "yellow", + "cost": 260, + "houses": 0 }, { "id": 28, "name": "Water Works", - "type": "utility" + "type": "utility", + "owner": null, + "mortgaged": false, + "group": "utility", + "cost": 150 }, { "id": 29, "name": "Marvin Gardens (Y)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "yellow", + "cost": 280, + "houses": 0 }, { "id": 30, @@ -181,12 +296,22 @@ { "id": 31, "name": "Pacific ave. (G)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "green", + "cost": 300, + "houses": 0 }, { "id": 32, "name": "N. Carolina ave. (G)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "green", + "cost": 300, + "houses": 0 }, { "id": 33, @@ -196,12 +321,21 @@ { "id": 34, "name": "Pennsylvania ave. (G)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "green", + "cost": 320, + "houses": 0 }, { "id": 35, "name": "Short Line RR", - "type": "railroad" + "type": "railroad", + "owner": null, + "mortgaged": false, + "group": "railroad", + "cost": 200 }, { "id": 36, @@ -211,7 +345,12 @@ { "id": 37, "name": "Park place (D)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "darkblue", + "cost": 350, + "houses": 0 }, { "id": 38, @@ -221,27 +360,165 @@ { "id": 39, "name": "Boardwalk (D)", - "type": "property" + "type": "property", + "owner": null, + "mortgaged": false, + "group": "darkblue", + "cost": 400, + "houses": 0 } ], "log": [ { "text": "Game for 3 players", "player": null, - "timestamp": "2026-02-21 11:33:22" + "timestamp": "2026-02-21 11:53:09" }, { "text": "Waiting for Player 1 to register...", "player": null, - "timestamp": "2026-02-21 11:33:22" + "timestamp": "2026-02-21 11:53:09" + }, + { + "text": "alice joined!", + "player": "alice", + "timestamp": "2026-02-21 11:53:10" }, { "text": "Waiting for Player 2 to register...", "player": null, - "timestamp": "2026-02-21 11:33:23" + "timestamp": "2026-02-21 11:53:10" + }, + { + "text": "bob joined!", + "player": "bob", + "timestamp": "2026-02-21 11:53:12" + }, + { + "text": "Waiting for Player 3 to register...", + "player": null, + "timestamp": "2026-02-21 11:53:12" + }, + { + "text": "charlie joined!", + "player": "charlie", + "timestamp": "2026-02-21 11:53:15" + }, + { + "text": "Game started! alice goes first", + "player": null, + "timestamp": "2026-02-21 11:53:15" + }, + { + "text": "alice's turn \u2014 $1500 on === GO ===", + "player": "alice", + "timestamp": "2026-02-21 11:53:15" + }, + { + "text": "roll is 2, 2", + "player": "alice", + "timestamp": "2026-02-21 11:53:16" + }, + { + "text": "Landed on Income Tax", + "player": "alice", + "timestamp": "2026-02-21 11:53:17" + }, + { + "text": "alice's turn \u2014 $1350 on Income Tax", + "player": "alice", + "timestamp": "2026-02-21 11:53:19" + }, + { + "text": "roll is 5, 5", + "player": "alice", + "timestamp": "2026-02-21 11:53:20" + }, + { + "text": "Landed on Virginia ave. (V)", + "player": "alice", + "timestamp": "2026-02-21 11:53:21" + }, + { + "text": "alice's turn \u2014 $1190 on Virginia ave. (V)", + "player": "alice", + "timestamp": "2026-02-21 11:53:23" + }, + { + "text": "roll is 3, 3", + "player": "alice", + "timestamp": "2026-02-21 11:53:24" + }, + { + "text": "3 doubles \u2014 go to jail!", + "player": "alice", + "timestamp": "2026-02-21 11:53:25" + }, + { + "text": "bob's turn \u2014 $1500 on === GO ===", + "player": "bob", + "timestamp": "2026-02-21 11:53:25" + }, + { + "text": "roll is 1, 2", + "player": "bob", + "timestamp": "2026-02-21 11:53:27" + }, + { + "text": "Landed on Baltic ave. (P)", + "player": "bob", + "timestamp": "2026-02-21 11:53:27" + }, + { + "text": "charlie's turn \u2014 $1500 on === GO ===", + "player": "charlie", + "timestamp": "2026-02-21 11:53:29" + }, + { + "text": "roll is 2, 6", + "player": "charlie", + "timestamp": "2026-02-21 11:53:31" + }, + { + "text": "Landed on Vermont ave. (L)", + "player": "charlie", + "timestamp": "2026-02-21 11:53:31" + }, + { + "text": "alice's turn \u2014 $1190 on JAIL", + "player": "alice", + "timestamp": "2026-02-21 11:53:33" + }, + { + "text": "roll is 2, 6", + "player": "alice", + "timestamp": "2026-02-21 11:53:34" + }, + { + "text": "bob's turn \u2014 $1440 on Baltic ave. (P)", + "player": "bob", + "timestamp": "2026-02-21 11:53:35" + }, + { + "text": "roll is 3, 5", + "player": "bob", + "timestamp": "2026-02-21 11:53:37" + }, + { + "text": "Landed on St. Charles pl. (V)", + "player": "bob", + "timestamp": "2026-02-21 11:53:37" + }, + { + "text": "charlie's turn \u2014 $1400 on Vermont ave. (L)", + "player": "charlie", + "timestamp": "2026-02-21 11:53:39" + }, + { + "text": "roll is 3, 2", + "player": "charlie", + "timestamp": "2026-02-21 11:53:40" } ], - "phase": "setup", - "numPlayersExpected": 3, - "lastUpdated": "2026-02-21T11:33:23.055895+00:00" + "lastUpdated": "2026-02-21T11:53:40.970003+00:00" } \ No newline at end of file