// Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning. const { load, suite, flatWorld, count } = require('./harness'); const { run } = load(); const { check, done } = suite(); const fresh = () => { flatWorld(run, 240, 160, 100); run('money = 1000; bag[RSTONE] = 400; bagTotal = 400; bot.x = 100; bot.y = 60; undoStack = []; clearAllGhosts();'); }; const belts = () => run('blockP.reduce((a, v) => a + (v === BP_BELT), 0)'); const fans = () => run("machines.filter(m => m && m.type === 'fan').length"); // placing a line of belts, then undoing it: they come down and the money comes back fresh(); const m0 = run('money'); run("undoRecord('placing belts', () => placeBlocks(BP_BELT, 1, blockLine(22, 20, 27, 20), undefined, false))"); check('a line of 6 belts is placed', belts() === 6 && run('money') === m0 - 72, `${belts()} belts, $${run('money')}`); check('Ctrl+Z takes them back and refunds them', run('undoLast()') === 'Undid placing belts.' && belts() === 0 && run('money') === m0, `${belts()} belts, $${run('money')}`); check('with nothing left, it says so', run('undoLast()') === 'Nothing to undo.'); // taking a piece back, then undoing: it is built again fresh(); run('placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false); placeBlock(BP_WALL, RSTONE, 26, 20, false);'); const st0 = run('bag[RSTONE]'); run("undoRecord('taking back', () => { removePieceAt(23 * 4 + 1, 20 * 4 + 1); removePieceAt(26 * 4 + 1, 20 * 4 + 1); })"); check('taking back a belt and a wall block', belts() === 3 && run('blockP[20 * BW + 26]') === 0 && run('bag[RSTONE]') === st0 + 16, `${belts()} belts, ${run('bag[RSTONE]')} rubble`); run('undoLast()'); check('undo builds them again from the bag', belts() === 4 && run('blockP[20 * BW + 26]') === run('BP_WALL') && run('bag[RSTONE]') === st0, `${belts()} belts, ${run('bag[RSTONE]')} rubble`); // replacing belts with filters, then undoing: the belts come back fresh(); run('placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false);'); run("undoRecord('replacing', () => placeBlocks(BP_FILTER, 128, blockLine(23, 20, 24, 20), { m: [SAND], inv: 0 }, true))"); check('Ctrl-dragging filters over belts replaces them', run('blockP[20 * BW + 23]') === run('BP_FILTER') && belts() === 2); run('undoLast()'); check('undo puts the belts back', belts() === 4 && run('blockP.reduce((a, v) => a + (v === BP_FILTER), 0)') === 0, belts()); // Ctrl over built blocks with a machine: they are taken down and the fan goes in their place fresh(); run('placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false);'); check('a fan will not go on a belt without Ctrl', /already/.test(run("canPlaceMachine('fan', 23, 20) || ''"))); run("undoRecord('replacing', () => { clearFootprint(23, 20, 1, 1); placeMachineAt('fan', 0, 23, 20); })"); check('with Ctrl the belt under it is taken down and the fan placed', fans() === 1 && belts() === 3); run('undoLast()'); check('undo takes the fan back and rebuilds the belt', fans() === 0 && belts() === 4); run('for (let y = 76; y < 80; y++) for (let x = 112; x < 116; x++) setCell(y * W + x, STONE, AMB[y], F_NAT);'); check('natural rock still stops a replace', /rock/.test(run('clearFootprint(28, 19, 1, 1)'))); // cut: everything in the box comes down into the bag and onto the clipboard; undo puts it back fresh(); run("placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false); placeMachineAt('fan', 0, 22, 18);"); const mc = run('money'); const bp = JSON.parse(run("JSON.stringify(undoRecord('cutting', () => cutRegion(22 * 4, 18 * 4, 26 * 4 - 1, 21 * 4 - 1)))")); check('cut picks up the belts and the fan', bp && bp.blocks.length === 4 && bp.machines.length === 1, bp && `${bp.blocks.length} blocks, ${bp.machines.length} machines`); check('and takes them down, refunded', belts() === 0 && fans() === 0 && run('money') === mc + 48 + 60, `${belts()} belts, ${fans()} fans, $${run('money')}`); run('undoLast()'); check('undo after a cut puts it all back', belts() === 4 && fans() === 1 && run('money') === mc, `${belts()} belts, ${fans()} fans, $${run('money')}`); // paste: ghosts appear; undo clears them, and takes back any the bot already built fresh(); run("placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false); window_bp = copyRegion(22 * 4, 20 * 4, 26 * 4 - 1, 21 * 4 - 1);"); run("undoRecord('pasting', () => pasteGhost(window_bp, 22, 17))"); check('pasting places ghosts', run('ghostBlocks.size') === 4); run('for (let k = 0; k < 400; k++) simTick();'); const built = belts(); check('the bot builds some or all of them', built > 4, built); check('building from ghosts is never an undo step of its own', run('undoStack.length') === 1, run('undoStack.length')); run('undoLast()'); check('undo clears the ghosts and takes back what was built from them', belts() === 4 && run('ghostBlocks.size') === 0, `${belts()} belts, ${run('ghostBlocks.size')} ghosts`); // a fan getting near its melting point warns you fresh(); run("placeMachineAt('fan', 0, 22, 20); EV.length = 0;"); run('for (let y = 80; y < 84; y++) for (let x = 88; x < 92; x++) temp[y * W + x] = 900; for (let k = 0; k < 40; k++) { for (let y = 80; y < 84; y++) for (let x = 88; x < 92; x++) temp[y * W + x] = 900; simTick(); }'); const warn = run("EV.filter(e => e.t === 'toast' && /fan/i.test(e.text)).map(e => e.text).join(' | ')"); check('a fan at 900°C warns that it is about to melt', /1085/.test(warn) && fans() === 1, warn); // old saves: tiny specks of too-hard rock become the rock around them, once fresh(); run('for (let y = 120; y < 122; y++) for (let x = 60; x < 62; x++) setCell(y * W + x, FEV, AMB[y], F_NAT); for (let y = 120; y < 130; y++) for (let x = 140; x < 150; x++) setCell(y * W + x, FEV, AMB[y], F_NAT);'); const n = run('clearHardSpecks(true)'); check('a 4-grain speck of iron ore in stone becomes stone; a 100-grain vein stays', n === 1 && count(run, run('FEV')) === 100 && run('mat[120 * W + 60]') === run('STONE'), `${n} specks, ${count(run, run('FEV'))} vein`); done();