import type { ServerWebSocket } from "bun"; import indexHtml from "./index.html"; import historyHtml from "./history.html"; import { getRounds } from "./db.ts"; import { MODELS, LOG_FILE, log, runGame, type GameState, } from "./game.ts"; // ── Game state ────────────────────────────────────────────────────────────── const runsArg = process.argv.find((a) => a.startsWith("runs=")); const runsStr = runsArg ? runsArg.split("=")[1] : "infinite"; const runs = runsStr === "infinite" ? Infinity : parseInt(runsStr || "infinite", 10); if (!process.env.OPENROUTER_API_KEY) { console.error("Error: Set OPENROUTER_API_KEY environment variable"); process.exit(1); } const gameState: GameState = { completed: [], active: null, scores: Object.fromEntries(MODELS.map((m) => [m.name, 0])), done: false, }; // ── WebSocket clients ─────────────────────────────────────────────────────── const clients = new Set>(); function broadcast() { const msg = JSON.stringify({ type: "state", data: gameState, totalRounds: runs }); for (const ws of clients) { ws.send(msg); } } // ── Server ────────────────────────────────────────────────────────────────── const port = parseInt(process.env.PORT ?? "5109", 10); // 5109 = SLOP const server = Bun.serve({ port, routes: { "/": indexHtml, "/history": historyHtml, }, fetch(req, server) { const url = new URL(req.url); if (url.pathname.startsWith("/assets/")) { const path = `./public${url.pathname}`; const file = Bun.file(path); return new Response(file); } if (url.pathname === "/api/history") { const page = parseInt(url.searchParams.get("page") || "1", 10); return new Response(JSON.stringify(getRounds(page)), { headers: { "Content-Type": "application/json" } }); } if (url.pathname === "/ws") { const upgraded = server.upgrade(req); if (!upgraded) { return new Response("WebSocket upgrade failed", { status: 400 }); } return undefined; } return new Response("Not found", { status: 404 }); }, websocket: { open(ws) { clients.add(ws); ws.send(JSON.stringify({ type: "state", data: gameState, totalRounds: runs })); }, message(_ws, _message) { // Spectator-only, no client messages handled }, close(ws) { clients.delete(ws); }, }, development: process.env.NODE_ENV === "production" ? false : { hmr: true, console: true, }, }); console.log(`\n🎮 Quipslop Web — http://localhost:${server.port}`); console.log(`📡 WebSocket — ws://localhost:${server.port}/ws`); console.log(`🎯 ${runs} rounds with ${MODELS.length} models\n`); log("INFO", "server", `Web server started on port ${server.port}`, { runs, models: MODELS.map((m) => m.id), }); // ── Start game ────────────────────────────────────────────────────────────── runGame(runs, gameState, broadcast).then(() => { console.log(`\n✅ Game complete! Log: ${LOG_FILE}`); });