SpaceCities / tools /selfplay-cli.js
Claude
Phase 0: split tools/selfplay.js into a browser-safe core + tools/selfplay-cli.js
022fcd2 unverified
Raw History Blame Contribute Delete
3.24 kB
/* ============================================================
SELF-PLAY CLI β€” the Node entry point for tools/selfplay.js's pure self-play core. Split out (see
docs/competitions-and-elo.md Phase 0) so tools/selfplay.js itself stays free of `process`/
`document` and can be imported from a browser main thread or a Worker, not just Node. This file
is Node-only by design β€” it's the thing that CAN'T be imported from a browser β€” so all of that
lives here instead.
Usage
node tools/selfplay-cli.js run [--world ferros] [--seed 1] [--minutes 40]
[--ai-strategy default] [--ai-difficulty medium]
[--player-strategy default] [--player-difficulty medium]
============================================================ */
"use strict";
import { createSelfPlayState, runSelfPlayMatch } from "./selfplay.js";
function parseArgs(argv) {
const out = { _: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith("--")) out[a.slice(2)] = (argv[i + 1] && !argv[i + 1].startsWith("--")) ? argv[++i] : "true";
else out._.push(a);
}
return out;
}
function runCmd(args) {
const state = createSelfPlayState({
planetId: args.world || "ferros",
seed: args.seed !== undefined ? Number(args.seed) : 1,
matchTimeLimit: args.minutes !== undefined ? Number(args.minutes) * 60 : undefined,
ai: { strategy: args["ai-strategy"], difficulty: args["ai-difficulty"] },
playerAi: { strategy: args["player-strategy"], difficulty: args["player-difficulty"] },
});
const result = runSelfPlayMatch(state);
const unitsOf = o => [...state.units.values()].filter(u => u.owner === o).length;
const buildingsOf = o => [...state.buildings.values()].filter(b => b.owner === o).length;
console.log(`world=${state.planetId} seed=${state.seed}`);
console.log(`ai: strategy=${state.ai.strategy} difficulty=${state.ai.difficulty}`);
console.log(`player: strategy=${state.playerAi.strategy} difficulty=${state.playerAi.difficulty}`);
console.log(`--`);
console.log(`over=${result.over} winner=${result.winner} reason=${result.winReason} `
+ `time=${result.time.toFixed(1)}s tick=${result.tick}`);
console.log(`ai: units=${unitsOf("ai")} buildings=${buildingsOf("ai")} `
+ `resources=${JSON.stringify(state.players.ai.resources)}`);
console.log(`player: units=${unitsOf("player")} buildings=${buildingsOf("player")} `
+ `resources=${JSON.stringify(state.players.player.resources)}`);
}
const USAGE = `SELF-PLAY β€” drive BOTH sides of a skirmish with the real AI (Tier 1).
node tools/selfplay-cli.js run [--world ferros] [--seed 1] [--minutes 40]
[--ai-strategy default] [--ai-difficulty medium]
[--player-strategy default] [--player-difficulty medium]`;
function main(argv) {
const args = parseArgs(argv);
const cmd = args._[0];
if (cmd === "run") { runCmd(args); return; }
console.log(USAGE);
}
// Only run the CLI when invoked directly, so a test can import the functions above.
if (process.argv[1] && new URL(import.meta.url).pathname === process.argv[1]) main(process.argv.slice(2));