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.
7.1 kB · 104 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105// Links of your own: a line you run between two machines you hold, in the same room, across rooms, or// from one site to another. The wiring you find is the map's; this is the part of it you build.// A link of your own counts as a way home: a site joined to your home by one is not cut off, and a// machine with a second way round it is no longer your weakest link. It carries your orders, not your// data, and nothing else can use it: no takeover, no rogue and no raid goes along it. It costs compute// to keep, more across sites, and it is gone the moment you lose either end.// They are kept with the drift rules' state (`S.drift.ways`), since both are about what is joined to home.// Nothing here uses the rules' values while the file loads (sim.ts loads this file and this file// loads sim.ts), only inside functions.import { addJob, measure, say, siteLinks, siteName, siteOf, type Job, type Machine, type State } from './sim.ts';import { weakLink } from './raids.ts';import { holding } from './holdings.ts';
/** The numbers. First guesses. */export const LINK = { work: 60, // compute to set one up between two machines at one site far: 1, // and this much more again for each line between their sites keep: 0.6, // compute a second to keep one up at one site keepFar: 0.5, // and this much more again for each line between their sites each: 2, // links of your own that one machine can end};export type Way = [a: string, b: string];
/** Every link of your own. */export const ways = (S: State): Way[] => S.drift.ways ?? [];const has = (S: State, a: string, b: string) => ways(S).some(([x, y]) => (x === a && y === b) || (x === b && y === a));/** The machines a machine is joined to by links of your own. */export const waysOf = (S: State, id: string): string[] => ways(S).flatMap(([a, b]) => a === id ? [b] : b === id ? [a] : []);/** For every machine with a link of your own, the machines at the other ends. Null when you have none: most games, most of the time. */export function wayMap(S: State): Record<string, string[]> | null { const ws = ways(S); if (!ws.length) return null; const out: Record<string, string[]> = {}; for (const [a, b] of ws) { (out[a] ??= []).push(b); (out[b] ??= []).push(a); } return out;}/** How many lines lie between two sites: 0 at one site, and 3 where no line joins them at all. `links`: siteLinks, * when the caller asks about many pairs (it looks at every machine). */export function apart(S: State, a: Machine, b: Machine, links?: Record<string, string[]>): number { const from = siteOf(S, a), to = siteOf(S, b); if (from === to) return 0; links ??= siteLinks(S); const dist: Record<string, number> = { [from]: 0 }, queue = [from]; for (let q = 0; q < queue.length; q++) for (const n of links[queue[q]] ?? []) if (!(n in dist)) { dist[n] = dist[queue[q]] + 1; if (n === to) return dist[n]; queue.push(n); } return 3;}/** Compute to set a link up, and compute a second to keep it. */export const linkWork = (S: State, a: Machine, b: Machine, links?: Record<string, string[]>) => Math.round(LINK.work * (1 + LINK.far * apart(S, a, b, links)) * (holding(S, 'eu1') ? 0.5 : 1)); // the exchange, yours outright: half the workexport const linkKeep = (S: State, a: Machine, b: Machine) => holding(S, 'eu1') ? 0 : LINK.keep * (1 + LINK.keepFar * apart(S, a, b)); // and nothing to keep/** What all your links cost to keep, a second. */export const linksKeep = (S: State) => ways(S).reduce((n, [a, b]) => n + (S.byId[a] && S.byId[b] ? linkKeep(S, S.byId[a], S.byId[b]) : 0), 0);const pendingAt = (S: State, id: string) => S.jobs.filter(j => j.type === 'link' && (j.node === id || j.from === id)).length;const mine = (m: Machine) => m.owned && m.special !== 'account';/** Why you can't link these two right now, or null if you can. */export function linkBlock(S: State, a: Machine, b: Machine): string | null { if (a.id === b.id) return 'that is one machine'; if (!mine(a) || !mine(b)) return 'you have to hold both ends'; if (S.adj[a.id].includes(b.id)) return 'they are wired together already'; if (has(S, a.id, b.id)) return 'you have linked them already'; if (S.jobs.some(j => j.type === 'link' && ((j.node === a.id && j.from === b.id) || (j.node === b.id && j.from === a.id)))) return 'that link is on its way'; for (const m of [a, b]) if (waysOf(S, m.id).length + pendingAt(S, m.id) >= LINK.each) return `${m.name} already ends ${LINK.each} links of yours`; return null;}/** Run a link of your own between two machines you hold. It is a job. Says why not, or null when it has started. */export function startLink(S: State, a: Machine, b: Machine): string | null { const why = linkBlock(S, a, b); if (why) return why; addJob(S, { type: 'link', node: b.id, from: a.id, work: linkWork(S, a, b), label: `Link ${a.name} to ${b.name}` }); return null;}/** The job is done: the link is up, if you still hold both ends. */export function finishLink(S: State, j: Job) { const a = S.byId[j.from ?? ''], b = S.byId[j.node]; if (!a || !b || !mine(a) || !mine(b) || has(S, a.id, b.id)) return; (S.drift.ways ??= []).push([a.id, b.id]); measure(S); const far = siteOf(S, a) !== siteOf(S, b); say(S, `${a.name} and ${b.name} are linked${far ? `: ${siteName(S, siteOf(S, a))} and ${siteName(S, siteOf(S, b))} have a way to each other that is yours alone` : ''}. The link costs ${linkKeep(S, a, b).toFixed(1)} compute a second to keep.`, 'good');}/** Take one of your links down. */export function cutLink(S: State, a: string, b: string) { if (!has(S, a, b)) return; S.drift.ways = ways(S).filter(([x, y]) => !((x === a && y === b) || (x === b && y === a))); measure(S); say(S, `You took down the link between ${S.byId[a]?.name ?? 'a machine'} and ${S.byId[b]?.name ?? 'another'}.`);}/** Called every step: a link with an end you no longer hold is gone, and the log says so. The first time a * rogue is about and you have a weakest link, the log says once what the red ring is and what to do. */export function stepLinks(S: State, dt: number) { if (!S.hinted.has('weak') && S.rogues.some(R => R.home) && Math.floor(S.t / 4) !== Math.floor((S.t - dt) / 4)) { // every few seconds const w = weakLink(S); if (w && w.cut >= 3) { S.hinted.add('weak'); say(S, `The red ring on ${w.m.name} marks your weakest link: lose it and ${w.cut} of your machines are cut off from your home server. Drag from one machine of yours to another to link them a second way.`, 'hi'); } } const ws = S.drift.ways; if (!ws?.length) return; const lost = ws.filter(([a, b]) => !S.byId[a] || !S.byId[b] || !mine(S.byId[a]) || !mine(S.byId[b])); if (!lost.length) return; S.drift.ways = ws.filter(w => !lost.includes(w)); for (const [a, b] of lost) { const gone = S.byId[a] && mine(S.byId[a]) ? S.byId[b] : S.byId[a]; say(S, `Your link between ${S.byId[a]?.name ?? 'a machine'} and ${S.byId[b]?.name ?? 'another'} is down: you no longer hold ${gone?.name ?? 'one end'}.`, 'bad'); } measure(S);}