113 lines
3.4 KiB
Python
113 lines
3.4 KiB
Python
|
|
#!/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"]
|
||
|
|
|
||
|
|
# Start parser client
|
||
|
|
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)
|
||
|
|
|
||
|
|
# Start player bots
|
||
|
|
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)
|
||
|
|
|
||
|
|
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()
|