108 lines
8.2 KiB
JavaScript
108 lines
8.2 KiB
JavaScript
// 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(); }');
|
|
check('a hot fan stores its temperature without a toast', run("machines.some(m => m && m.type === 'fan' && m.hot > 760) && !EV.some(e => e.t === 'toast')"));
|
|
|
|
run("EV.length = 0; removeMachine(machines.find(m => m && m.type === 'fan').id, false, true);");
|
|
check('heat destruction emits a local warning', run("EV.some(e => e.t === 'warn' && e.text === 'Fan destroyed' && Number.isFinite(e.x) && Number.isFinite(e.y)) && !EV.some(e => e.t === 'toast')"));
|
|
|
|
// 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`);
|
|
|
|
// t64: building reaches about as far as the screen shows, and a replace placed out of range
|
|
// becomes a ghost that replaces the old piece when the bot comes in range.
|
|
fresh();
|
|
check('with no view the build range is a REACH box around the bot', run('buildBox = null; inBuildRange(bot.x + bot.w / 2 + REACH, bot.y) && !inBuildRange(bot.x + bot.w / 2 + REACH + 2, bot.y)'));
|
|
check('the build range follows the view box', run('(() => { setBuildRange(200, 100, 300, 160); const r = inBuildRange(340, 100) && !inBuildRange(352, 100) && inBuildRange(200, 180) && !inBuildRange(200, 190); buildBox = null; return r; })()'));
|
|
check('a tiny view still reaches REACH and a huge one stops at BUILD_MAX', run('(() => { setBuildRange(500, 500, 20, 20000); const r = buildBox.hw === REACH && buildBox.hh === BUILD_MAX; buildBox = null; return r; })()'));
|
|
fresh();
|
|
const swap = run(`(() => {
|
|
placeBlock(BP_BELT, 1, 50, 20, true); const b = 20 * BW + 50, m0 = money;
|
|
ghostBlocks.set(b, [BP_SCREW, 1, undefined, 1]);
|
|
bot.x = 20; bot.y = 20; for (let k = 0; k < 30; k++) ghostTick();
|
|
const before = blockP[b];
|
|
bot.x = 50 * BS - 4; bot.y = 20 * BS - 10; for (let k = 0; k < 30; k++) ghostTick();
|
|
return { before, after: blockP[b], v: blockV[b], spent: m0 - money, ghosts: ghostBlocks.size };
|
|
})()`);
|
|
check('an out-of-range replace ghost leaves the old piece until the bot comes near', swap.before === run('BP_BELT'), JSON.stringify(swap));
|
|
check('then replaces it: the new piece is built and the ghost is gone', swap.after === run('BP_SCREW') && swap.v === 1 && swap.ghosts === 0, JSON.stringify(swap));
|
|
check('the replace charges the new piece and refunds the old one', swap.spent === 25 - 12, JSON.stringify(swap));
|
|
fresh();
|
|
check('a plain ghost over a different piece still leaves it alone', run(`(() => {
|
|
placeBlock(BP_BELT, 1, 30, 20, true); const b = 20 * BW + 30;
|
|
ghostBlocks.set(b, [BP_SCREW, 1]); bot.x = 30 * BS - 4; bot.y = 20 * BS - 10;
|
|
for (let k = 0; k < 30; k++) ghostTick();
|
|
return blockP[b] === BP_BELT && ghostBlocks.has(b);
|
|
})()`));
|
|
done();
|