/* Smoke test: create room w/ bot, 2 human clients via SSE, play until end. */ const BASE = "http://localhost:3000"; function sse(uid) { return new Promise((resolve) => { const { request } = require("http"); const res = request({ host: "localhost", port: 3000, path: `/api/events?uid=${uid}`, method: "GET" }, (r) => { let buf = ""; const states = []; const chats = []; r.on("data", (c) => { buf += c; let idx; while ((idx = buf.indexOf("\n\n")) >= 0) { const chunk = buf.slice(0, idx); buf = buf.slice(idx + 2); const lines = chunk.split("\n").filter(l => l.startsWith("data: ")); for (const l of lines) { const m = JSON.parse(l.slice(6)); if (m.type === "state") states.push(m.snap); else if (m.type === "chat") chats.push(m); } } }); resolve({ states, chats, res }); }); res.on("error", () => {}); res.end(); }); } const api = async (action, uid, name, extra = {}) => { const r = await fetch(BASE + "/api", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, uid, name, ...extra }), }); const d = await r.json(); if (!r.ok) throw new Error(`${action}: ${d.error}`); return d; }; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); (async () => { // create room (host + auto bot) const c1 = await api("create", null, "Alice", { roomName: "Testbord", addBot: true }); const uid1 = c1.uid; // second player const c2 = await api("join", null, "Bob", { roomId: c1.room }); const uid2 = c2.uid; const [s1, s2] = await Promise.all([sse(uid1), sse(uid2)]); // host starts await api("start", uid1, "Alice"); console.log("started. waiting for bot rounds..."); const startT = Date.now(); let turns = 0; while (Date.now() - startT < 240000) { await sleep(1500); const snap = s1.states[s1.states.length - 1]; if (!snap) continue; if (snap.phase === "waiting") { console.log("GAME ENDED:", snap.log.slice(-3).map(l => l.t)); break; } // alice acts if it's her turn/actor const actor = snap.phase === "challenging" && snap.pending ? snap.pending.challenger : snap.turnUid; if (actor === uid1) { if (snap.phase === "acting") { await api("roll", uid1, "Alice"); } else if (snap.phase === "peeking") { await api("peek", uid1, "Alice", { revealed: true }); await sleep(200); const fresh = s1.states[s1.states.length - 1]; const myDice = fresh.myDice; if (myDice) { const truth = rankOf(myDice[0], myDice[1]); const minR = fresh.lastClaim ? fresh.lastClaim.rank + 1 : 0; let r = Math.max(minR, truth); if (Math.random() < 0.3) r = Math.min(r + 1, 17); // occasional bluff if (r < minR || r > 17) { await api("sendForward", uid1, "Alice"); } else await api("declare", uid1, "Alice", { rank: r }); } } else if (snap.phase === "challenging" && snap.pending && snap.pending.challenger === uid1) { // bob's claim is on the table... but alice is the actor? (challenger) - decide randomly await (Math.random() < 0.4 ? api("challenge", uid1, "Alice") : api("pass", uid1, "Alice")); } turns++; } const actor2 = snap.phase === "challenging" && snap.pending ? snap.pending.challenger : snap.turnUid; if (actor2 === uid2) { if (snap.phase === "acting") await api("roll", uid2, "Bob"); else if (snap.phase === "peeking") { await api("peek", uid2, "Bob", { revealed: true }); await sleep(200); const fresh = s2.states[s2.states.length - 1]; const myDice = fresh.myDice; if (myDice) { const truth = rankOf(myDice[0], myDice[1]); const minR = fresh.lastClaim ? fresh.lastClaim.rank + 1 : 0; let r = Math.max(minR, truth); if (Math.random() < 0.3) r = Math.min(r + 1, 17); if (r < minR || r > 17) await api("sendForward", uid2, "Bob"); else await api("declare", uid2, "Bob", { rank: r }); } } else if (snap.phase === "challenging" && snap.pending && snap.pending.challenger === uid2) { await (Math.random() < 0.4 ? api("challenge", uid2, "Bob") : api("pass", uid2, "Bob")); } turns++; } // progress report if (turns % 10 === 0) console.log(`...turns=${turns} phase=${snap.phase} scores=${JSON.stringify(snap.players.map(p => [p.name, p.score, p.eliminated]))}`); } // also test chat + leave await api("chat", uid1, "Alice", { text: "hej med jer alle sammen! 🍺" }); await sleep(300); console.log("chats received by bob:", s2.chats.length); console.log("total turns driven:", turns); const rooms = await (await fetch(BASE + "/api/rooms")).json(); console.log("rooms after:", JSON.stringify(rooms.rooms.map(r => ({ id: r.id, phase: r.phase, n: r.players.length })))); process.exit(0); })().catch(e => { console.error("SMOKE FAIL:", e); process.exit(1); }); /* local mirror of ranking for the test driver */ const COMBOS = [[1,2],[1,3],[6,6],[5,5],[4,4],[3,3],[2,2],[1,1],[6,5],[6,4]]; const DYN = [[6,3],[6,2],[5,4],[5,3],[5,2],[4,3],[4,2],[3,2]]; function rankOf(a, b) { const x = Math.min(a, b), y = Math.max(a, b); for (let i = 0; i < COMBOS.length; i++) if (COMBOS[i][0] === y && COMBOS[i][1] === x) return i; const i = DYN.findIndex(c => c[0] === y && c[1] === x); return i >= 0 ? 10 + i : -1; }