monop-state/run_game.py

115 lines
3.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Run a simulated game: player bots + parser writing game-state.json to site/."""
import sys
import os
import json
import socket
import threading
import time
sys.path.insert(0, os.path.dirname(__file__))
from monop_parser import MonopParser
from monop_players import PlayerBot
STATE_PATH = os.path.join(os.path.dirname(__file__), "site", "game-state.json")
HOST = "127.0.0.1"
PORT = 6667
CHANNEL = "#monop"
BOT_NICK = "monop"
class ParserClient:
"""IRC client that watches the game and writes game-state.json."""
def __init__(self):
self.parser = MonopParser()
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.buffer = ""
self.running = True
def connect(self):
self.sock.connect((HOST, PORT))
self.sock.sendall(b"NICK observer\r\nUSER observer 0 * :observer\r\n")
time.sleep(1)
self.sock.sendall(f"JOIN {CHANNEL}\r\n".encode())
def run(self):
self.connect()
while self.running:
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)
self._handle(line)
except Exception as e:
print(f"[parser] Error: {e}")
break
def _handle(self, line):
if line.startswith("PING"):
self.sock.sendall(f"PONG {line[5:]}\r\n".encode())
return
# Parse PRIVMSG from bot
if "PRIVMSG" in line and CHANNEL in line:
# :nick!user@host PRIVMSG #chan :message
parts = line.split(f"PRIVMSG {CHANNEL} :", 1)
if len(parts) == 2:
prefix = parts[0].strip()
sender = prefix.split("!")[0].lstrip(":")
message = parts[1]
ts = time.strftime("%Y-%m-%d %H:%M:%S")
log_line = f"{ts}\t{sender}\t{message}"
self.parser.parse_line(log_line)
state = self.parser.get_state()
if state:
from datetime import datetime, timezone
state["lastUpdated"] = datetime.now(timezone.utc).isoformat()
try:
with open(STATE_PATH, "w") as f:
json.dump(state, f, indent=2)
except Exception as e:
print(f"[parser] Write error: {e}")
def stop(self):
self.running = False
try:
self.sock.close()
except:
pass
def main():
player_names = ["alice", "bob", "charlie"]
# 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] Observer connected, writing to {STATE_PATH}")
# 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(3.0) # stagger joins so setup is visible in the web UI
print(f"[game] {len(bots)} player bots started")
# Run until killed
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("[game] Shutting down...")
pc.stop()
if __name__ == "__main__":
main()