实时对局选你的对手
加入
c391413
66 行 · 2.6 KB 原始文件 永久链接 历史
1// A bot in its own process (referee/bot.mjs loads <dir>/bot.mjs), spoken to one JSON line at a time, like kit/host.py:
2// each message is numbered so a late answer to an earlier ask is dropped; a move later than the limit, or from a bot
3// that crashed, is no move (the caller passes for it). A bot sees no job token: SS_* and SLURM_* are not passed on.
4import { spawn, type ChildProcess } from 'node:child_process';
5import { createInterface } from 'node:readline';
6import { existsSync } from 'node:fs';
7import path from 'node:path';
8import { fileURLToPath } from 'node:url';
9
10export const REFEREE = path.dirname(fileURLToPath(import.meta.url));
11
12export class Bot {
13 errors = 0;
14 dead = false;
15 private p: ChildProcess;
16 private n = 0;
17 private waiting = new Map<number, (a: unknown) => void>();
18 ready: Promise<unknown>;
19
20 constructor(readonly dir: string, readonly limitMs = 2000) {
21 if (!existsSync(path.join(dir, 'bot.mjs'))) throw new Error(`No bot at ${path.join(dir, 'bot.mjs')}`);
22 const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !/^(SS_|SLURM_)/.test(k)));
23 this.p = spawn(process.execPath, [path.join(REFEREE, 'bot.mjs'), path.resolve(dir)], {
24 stdio: ['pipe', 'pipe', process.env.SGS_BOT_STDERR ? 'inherit' : 'ignore'],
25 env,
26 });
27 createInterface({ input: this.p.stdout! }).on('line', (line) => {
28 try {
29 const m = JSON.parse(line);
30 if (m && typeof m === 'object' && 'answer' in m) this.waiting.get(m.n)?.(m.answer);
31 } catch {
32 // a line that is not an answer
33 }
34 });
35 this.p.on('exit', () => {
36 this.dead = true;
37 for (const r of this.waiting.values()) r(undefined);
38 });
39 this.p.stdin!.on('error', () => {});
40 this.ready = this.ask({ hello: true }, 10000);
41 }
42
43 /** The bot's answer, or undefined when it is late, broken or gone. */
44 async ask(msg: object, limitMs = this.limitMs): Promise<unknown> {
45 if (this.dead) return undefined;
46 const n = ++this.n;
47 let timer: NodeJS.Timeout | undefined;
48 const answer = await new Promise<unknown>((resolve) => {
49 this.waiting.set(n, resolve);
50 timer = setTimeout(() => resolve(undefined), limitMs);
51 this.p.stdin!.write(`${JSON.stringify({ ...msg, n })}\n`);
52 });
53 clearTimeout(timer);
54 this.waiting.delete(n);
55 if (answer === undefined || answer === null) {
56 if (++this.errors >= 20) this.close(); // ponytail: a bot that misses 20 asks is stopped for the rest of the run
57 return undefined;
58 }
59 return answer;
60 }
61
62 close(): void {
63 this.dead = true;
64 this.p.kill();
65 }
66}