Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. cameron.tngl.io/misaligned
Something went wrong. Try again.
3.7 kB · 56 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// Lab's first steps: a short run of things to do, each pointing at its target (a cell of the floor, something// built, or a button) and done when the player does it. Never blocks play, and can be skipped. No DOM here.import * as R from './rules.ts';
/** What the page knows that the game does not. */export interface Ctx { G: R.Game; building: R.Build | null }/** Where a step points: a cell to build on, something to tap, a button on the page (a CSS selector). */export interface Target { cell?: { c: number; r: number; size: number }; button?: string }interface Step { text: string; done: (x: Ctx) => boolean; target: (x: Ctx) => Target }export interface Guide { at: number; off: boolean }export const newGuide = (): Guide => ({ at: 0, off: false });
const mine = (G: R.Game, kind: R.Build) => G.nodes.filter(n => n.kind === kind && n.cell && !n.site);const at = (c: number, r: number) => ({ kind: 'gpu' as R.NodeKind, cell: { c, r, size: 1 } });/** The free cell nearest a point that is within reach of everything in `near`. */function cellNear(G: R.Game, c0: number, r0: number, near: string[]) { let best: { c: number; r: number } | null = null, bd = Infinity; const any = { ...G, money: Infinity }; // only whether the cell is free, not whether you can pay yet for (let c = 0; c < R.COLS; c++) for (let r = 0; r < R.ROWS; r++) { if (R.whyNotBuild(any, 'gpu', c, r)) continue; if (near.some(id => R.gap(at(c, r), R.node(G, id)) > R.K.reach)) continue; const d = Math.hypot(c - c0, r - r0); if (d < bd) { bd = d; best = { c, r }; } } return best ? { ...best, size: 1 } : undefined;}const pickGpu = (x: Ctx): Target => (x.building === 'gpu' ? {} : { button: '[data-build="gpu"]' });
const STEPS: Step[] = [ { text: 'Pick a GPU', done: x => x.building === 'gpu' || mine(x.G, 'gpu').length > 0, target: () => ({ button: '[data-build="gpu"]' }) }, // only a cell between the north door and the core reaches both: the step points at one, and is done only when a // GPU stands where it reaches both { text: 'Put it on the marked cell: it reaches the north door and the core', done: x => mine(x.G, 'gpu').some(n => R.gap(n, R.node(x.G, 'door:pier')) <= R.K.reach && R.gap(n, R.node(x.G, 'core')) <= R.K.reach), target: x => ({ ...pickGpu(x), cell: cellNear(x.G, 8, 1, ['door:pier', 'core', 'mains']) }) }, { text: 'Work comes in. Each right step pays', done: x => x.G.earned > 0, target: x => { const n = mine(x.G, 'gpu')[0]; return n ? { cell: n.cell } : {}; } }, { text: 'Another GPU, by the core', done: x => mine(x.G, 'gpu').length > 1 || R.trainers(x.G).length > 0, target: x => ({ ...pickGpu(x), cell: cellNear(x.G, 9, 7, ['core', 'mains']) }) }, // in front of the tower, not hidden behind it { text: 'Tap it, and set it to Train', done: x => R.trainers(x.G).length > 0, target: x => { const core = R.node(x.G, 'core'), n = mine(x.G, 'gpu').filter(m => R.gap(m, core) <= R.K.reach).pop(); return { cell: n?.cell, button: '#card [data-act="train"]' }; } }, { text: 'Data fills the tower. Ship when it is full', done: x => R.canShip(x.G) || x.G.model > 1, target: x => ({ cell: R.node(x.G, 'core').cell }) }, { text: 'Ship it', done: x => x.G.model > 1, target: () => ({ button: '#ship' }) },];export const STEP_COUNT = STEPS.length;
/** The step to show now, or null once they are all done or skipped. Steps already done are passed for good. */export function current(gd: Guide, x: Ctx): { i: number; text: string; target: Target } | null { if (gd.off) return null; while (gd.at < STEPS.length && STEPS[gd.at].done(x)) gd.at++; const s = STEPS[gd.at]; return s ? { i: gd.at, text: s.text, target: s.target(x) } : null;}