diff --git a/scripts/bot.ts b/scripts/bot.ts index 94cb95a1..1a918ed9 100644 --- a/scripts/bot.ts +++ b/scripts/bot.ts @@ -1,12 +1,14 @@ // Headless balance check: the bot in play.ts plays N games and reports outcomes. -// Run: node scripts/bot.ts [games] [careful] [seed] +// Run: node scripts/bot.ts [games] [careful] [schemes] [seed] // With a seed, game i uses seed + i, so a run can be repeated exactly. +// With schemes, the bot turns on Auto for schemes. import { playGame } from './play.ts'; const games = +(process.argv[2] ?? 20); -const careful = process.argv[3] === 'careful'; -const seed = process.argv[4] === undefined ? undefined : +process.argv[4]; -const results = Array.from({ length: games }, (_, g) => playGame(careful, seed === undefined ? undefined : seed + g)); +const args = process.argv.slice(3); +const careful = args.includes('careful'), schemes = args.includes('schemes'); +const num = args.find(a => /^\d+$/.test(a)), seed = num === undefined ? undefined : +num; +const results = Array.from({ length: games }, (_, g) => playGame(careful, seed === undefined ? undefined : seed + g, schemes)); console.log(results.map(r => r.line).join('\n')); const wins = results.filter(r => r.outcome === 'WIN'); console.log(`\n${wins.length}/${games} wins, avg ${Math.round(wins.reduce((s, r) => s + r.S.t, 0) / Math.max(1, wins.length))}s`); diff --git a/scripts/play.ts b/scripts/play.ts index 6f957073..c7ac1d1e 100644 --- a/scripts/play.ts +++ b/scripts/play.ts @@ -4,8 +4,9 @@ // Both found a Startup on the office once they can, and spend its money on GPU orders and a wider line out. // At other sites they put trainers on the big machines (so linked ones form clusters) and taps on the rest, // and a broker at each site with a front sells the data nothing else wants. Spare money buys a site when a front may, then rents cloud trainers. +// With schemes on, it turns on the schemes list's Auto and lets it claim every scheme it can feed. import { BUILDING } from '../src/world.ts'; -import { buyBlock, buySite, sitePrice, canRent, leaseRate, rent, openAccount, brokerFront, gateLoad, lineCost, siteOf, upgradeLine, FRONT_KINDS, NODES, found, foundBlock, NODE_LIST, PATH_LIST, canInstall, closeTicket, freeSpot, orderDesk, nodeOpen, setTarget, dayOf, hourOf, installProg, jobFor, newGame, order, seedRandom, sourceFor, step, takeOver, takeWork, watchers, type Prog, type State } from '../src/sim.ts'; +import { buyBlock, buySite, sitePrice, canRent, leaseRate, rent, openAccount, setRuseAuto, brokerFront, gateLoad, lineCost, siteOf, upgradeLine, FRONT_KINDS, NODES, found, foundBlock, NODE_LIST, PATH_LIST, canInstall, closeTicket, freeSpot, orderDesk, nodeOpen, setTarget, dayOf, hourOf, installProg, jobFor, newGame, order, seedRandom, sourceFor, step, takeOver, takeWork, watchers, type Prog, type State } from '../src/sim.ts'; /** The run never ends by growing, so a "win" is reaching this capability, the building's old final level. */ export const GOAL = 600; @@ -85,9 +86,10 @@ function think(S: State, careful: boolean) { export type Outcome = 'WIN' | 'LOSE' | 'TIME'; /** Play one game to a win, a loss, or the time limit. With a seed, the same game every time. */ -export function playGame(careful: boolean, seed?: number): { S: State; outcome: Outcome; line: string } { +export function playGame(careful: boolean, seed?: number, schemes = false): { S: State; outcome: Outcome; line: string } { if (seed !== undefined) seedRandom(seed); const S = newGame(BUILDING); + if (schemes) setRuseAuto(S, true); let t = 0; while (!S.over && S.capability < GOAL && t < LIMIT) { if (Math.round(t * 20) % 10 === 0) think(S, careful); step(S, 0.05); t += 0.05; } const outcome: Outcome = S.over ? 'LOSE' : S.capability >= GOAL ? 'WIN' : 'TIME'; @@ -95,6 +97,6 @@ export function playGame(careful: boolean, seed?: number): { S: State; outcome: const paths = PATH_LIST.map(p => `${p[0]}${S.paths[p]}`).join('/'); const closed = S.threats.filter(t => t.closed).length, hit = S.threats.filter(t => !t.closed && t.target).length; const lost = S.machines.reduce((n, m) => n + m.missed, 0); - const line = `${outcome.padEnd(4)} closed=${closed} hit=${hit} nodes=${S.upgrades.size} lost=${lost} ${Math.round(S.t)}s day=${dayOf(S)} sus=${Math.round(S.suspicion)} owned=${S.machines.filter(m => m.owned).length} orders=${S.orders} lv=${S.rank} cap=${Math.round(S.capability)} ${paths} $${Math.round(S.money)} sold=$${Math.round(Object.values(S.sold).reduce((x, y) => x + (y ?? 0), 0))} line=${S.byId.egress.cap} rented=${S.machines.filter(m => m.lease).length} bought=${[...S.publicSites].filter(id => id !== 'cloud').join('+') || '-'} ${last}`; + const line = `${outcome.padEnd(4)} closed=${closed} hit=${hit} nodes=${S.upgrades.size} lost=${lost} ${Math.round(S.t)}s day=${dayOf(S)} sus=${Math.round(S.suspicion)} owned=${S.machines.filter(m => m.owned).length} orders=${S.orders} lv=${S.rank} cap=${Math.round(S.capability)} ${paths} $${Math.round(S.money)} sold=$${Math.round(Object.values(S.sold).reduce((x, y) => x + (y ?? 0), 0))} line=${S.byId.egress.cap} rented=${S.machines.filter(m => m.lease).length} learned=${S.learned.size} bought=${[...S.publicSites].filter(id => id !== 'cloud').join('+') || '-'} ${last}`; return { S, outcome, line }; }