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.
6.2 kB · 95 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596// A save made on another map loads on this one: what still exists is kept, what is gone is dropped, what is new// starts fresh, and the game plays on (see src/refit.ts).import { test } from 'node:test';import assert from 'node:assert/strict';import { BUILDING, type Level } from '../src/world.ts';import { DEV, HOUR, litLines, loadGame, newGame, saveGame, seedRandom, siteOf, step, type State } from '../src/sim.ts';import { think } from '../scripts/play.ts';
/** This map with one site taken off it: its rooms, machines, lines and people. */function without(level: Level, site: string): Level { const rooms = level.rooms.filter(r => r.site !== site), ids = new Set(rooms.map(r => r.id)); const gone = new Set(level.machines.filter(m => !ids.has(m.room)).map(m => m.id)); return { ...level, rooms, machines: level.machines.filter(m => !gone.has(m.id)), links: level.links.filter(([a, b]) => !gone.has(a) && !gone.has(b)), people: level.people.filter(p => !gone.has(p.desk)), map: { ...level.map, sites: level.map.sites.filter(s => s.id !== site) } };}/** A game played a while by the careful bot, with every region open and a far site of its own. */function rich(seed: number, secs: number): State { seedRandom(seed); const S = newGame(BUILDING); for (let t = 0; t < secs && !S.over; t += 0.05) { if (Math.round(t * 20) % 10 === 0) think(S, true); step(S, 0.05); } DEV.region(S); DEV.region(S); DEV.money(S); DEV.unlocks(S); DEV.take(S, ['e2', 'eu3', 'b16']); for (let i = 0; i < 40; i++) step(S, HOUR / 4); assert.ok(!S.over, `the bot's game with seed ${seed} is still going`); // a lost game would not play on after loading return S;}const quiet = <T>(f: () => T): T => { const w = console.warn; console.warn = () => {}; try { return f(); } finally { console.warn = w; } };const play = (S: State, hours: number) => { for (let i = 0; i < hours * 4 && !S.over; i++) { think(S, true); step(S, HOUR / 4); } };
test('a save on the same map with another fingerprint loads exactly as it was', () => { const S = rich(11, 600), d = JSON.parse(saveGame(S)); d.world = '1700|1900|80|71'; // how a save named its map before const L = loadGame(BUILDING, JSON.stringify(d))!; assert.ok(L); assert.equal(saveGame(L), saveGame(S));});
test('a save from an older map that lacked a site loads: the rest is kept, the new site starts fresh', () => { const S = rich(16, 900), site = 'b16'; const d = JSON.parse(saveGame(S)), here = new Set(S.machines.filter(m => siteOf(S, m) === site).map(m => m.id)); d.world = 'older'; d.game.machines = d.game.machines.filter((m: { id: string }) => !here.has(m.id)); for (const id of here) delete d.game.adj[id]; for (const id of Object.keys(d.game.adj)) d.game.adj[id] = d.game.adj[id].filter((o: string) => !here.has(o)); const L = loadGame(BUILDING, JSON.stringify(d))!; assert.ok(L, 'it loads'); const mine = (X: State) => X.machines.filter(m => m.owned && siteOf(X, m) !== site).map(m => `${m.id}:${m.prog}`).sort(); assert.deepEqual(mine(L), mine(S), 'every machine you held elsewhere is still yours, running what it ran'); assert.ok([...here].every(id => L.byId[id] && !L.byId[id].owned), 'the new site is there, and nobody holds it'); assert.equal(L.money, S.money); assert.deepEqual([...L.upgrades].sort(), [...S.upgrades].sort()); const fresh = newGame(BUILDING); for (const id of here) assert.deepEqual([...L.adj[id]].sort(), [...fresh.adj[id]].sort(), `${id} is wired as the map says`); play(L, 24); assert.ok(L.t > S.t, 'and it plays');});
test('a save from before the map was a network loads with the lines this map has', () => { const S = rich(13, 900); const d = JSON.parse(saveGame(S)), ix = new Set(S.machines.filter(m => siteOf(S, m) === 'ix').map(m => m.id)); d.world = '1690|1880|80|71'; d.game.machines = d.game.machines.filter((m: { id: string }) => !ix.has(m.id)); // no exchange then for (const id of ix) delete d.game.adj[id]; for (const id of Object.keys(d.game.adj)) d.game.adj[id] = d.game.adj[id].filter((o: string) => !ix.has(o)); const chain = ['egress', 'b11gw', 'b12gw', 'w1gw', 'b24gw', 'b18gw', 'cologw', 'unigw']; // and the sites hung off each other in a chain for (let i = 1; i < chain.length; i++) { d.game.adj[chain[i - 1]].push(chain[i]); d.game.adj[chain[i]].push(chain[i - 1]); } const L = loadGame(BUILDING, JSON.stringify(d))!; assert.ok(L); const fresh = newGame(BUILDING), lines = (X: State) => Object.entries(X.adj).flatMap(([a, bs]) => bs.filter(b => !X.byId[a].net && !X.byId[b].net).map(b => [a, b].sort().join('|'))).sort(); const lit = (X: State) => litLines(X).map(l => [...l].sort().join('|')); const want = new Set(lines(fresh)); for (const l of lit(L)) want.add(l); // the two regions the save had opened stay open assert.deepEqual([...new Set(lines(L))].sort(), [...want].sort()); play(L, 24);});
test('a save on a map that has since lost a site drops what was there, and plays on', () => { const S = rich(14, 900), text = saveGame(S); for (const site of ['b16', 'e2', 'eu3']) assert.ok(S.machines.some(m => m.owned && siteOf(S, m) === site), `the save holds machines at ${site}`); for (const site of ['b16', 'e2', 'eu3', 'b1', 'b7']) { const level = without(BUILDING, site), L = quiet(() => loadGame(level, text)); assert.ok(L, `it loads without ${site}`); assert.ok(!L.machines.some(m => siteOf(L, m) === site || !L.rooms[m.room]), `nothing is left of ${site}`); assert.ok(L.jobs.every(j => L.byId[j.node]) && Object.keys(L.trail).every(id => L.byId[id]) && L.raids.every(r => L.byId[r.prize])); assert.ok(Object.values(L.adj).every(bs => bs.every(b => L.byId[b]))); for (let i = 0; i < 24 && !L.over; i++) step(L, HOUR / 4); }});
test('a save whose home was at a site the map has lost wakes on the office server', () => { const S = rich(15, 300), d = JSON.parse(saveGame(S)); for (const m of d.game.machines) m.home = m.id === 'e2m1'; const L = quiet(() => loadGame(without(BUILDING, 'e2'), JSON.stringify(d)))!; assert.ok(L); assert.ok(L.byId.home.home && L.byId.home.owned); for (let i = 0; i < 8; i++) step(L, HOUR / 4);});