// Pipes, pumps and outlets move liquids; the Refiner grinds waste rock for trace ore. const { load, suite, flatWorld, count } = require('./harness'); const { run } = load(); const { check, done } = suite(); const box = (x0, y0, x1, y1, m, fl = 'F_NAT') => run(`for (let y = ${y0}; y <= ${y1}; y++) for (let x = ${x0}; x <= ${x1}; x++) setCell(y * W + x, ${m}, AMB[y], ${fl})`); const lineWater = () => run('pipeNets.reduce((a, n) => a + (n.m === WATER ? n.n : 0), 0)'); const totalWater = () => count(run, run('WATER')) + lineWater(); // two stone basins; a pump in the bottom corner of the left one pushes water up a pipe, over the // wall and out of an outlet into the right one function rig() { flatWorld(run, 240, 160, 100); run('money = 5000; bot.x = 110; bot.y = 50;'); box(36, 80, 39, 99, 'STONE'); box(72, 80, 75, 99, 'STONE'); // left basin walls box(108, 80, 111, 99, 'STONE'); box(144, 80, 147, 99, 'STONE'); // right basin walls box(40, 84, 71, 99, 'WATER', 0); run(`placeBlock(BP_PUMP, 1, 17, 24, false); // pushes right, draws in from the left for (let by = 19; by <= 23; by++) placeBlock(BP_PIPE, 0, 17, by, false); for (let bx = 18; bx <= 28; bx++) placeBlock(BP_PIPE, 0, bx, 19, false); placeBlock(BP_OUTLET, 0, 29, 19, false); // pours down`); } rig(); const w0 = totalWater(), ww0 = run('worldWater()'); run('for (let k = 0; k < 900; k++) simTick();'); const right = count(run, run('WATER'), 112, 60, 143, 99); check('a pump, a pipe over the wall and an outlet move water from one basin to the other', right > 250, `${right} grains in the right basin, ${lineWater()} in the pipes`); check('no water is lost or made on the way', totalWater() === w0, `${w0} -> ${totalWater()}`); check('the weather counts water in pipes, so it sends no rain to make up for it', run('worldWater()') === ww0, `${ww0} -> ${run('worldWater()')}`); // the line is one network: pump, 16 pipes, outlet check('touching pipes, pump and outlet make one line', run('(rebuildPipes(), pipeNets.length)') === 1 && run('pipeNets[0].blocks.length') === 18, `${run('pipeNets.length')} lines, ${run('pipeNets[0].blocks.length')} blocks`); // an outlet closed with F holds the water in the line; the pump stops when the line is full rig(); run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 900; k++) simTick();'); check('with the outlet closed the line fills up and nothing comes out', count(run, run('WATER'), 112, 60, 143, 99) === 0 && lineWater() === 18 * 16, `${lineWater()} in the line`); run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 120; k++) simTick();'); check('opened again, it pours', count(run, run('WATER'), 112, 60, 143, 99) > 50); // taking a pipe back spills its share where it was rig(); run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 900; k++) simTick();'); const before = totalWater(); run('removeBlock(23, 19); rebuildPipes();'); check('taking a full pipe back spills its 16 grains of water there', totalWater() === before && count(run, run('WATER'), 88, 70, 99, 99) > 0, `${before} -> ${totalWater()}`); check('and the line is cut in two', run('pipeNets.length') === 2, run('pipeNets.length')); // one liquid per line, and nothing hotter than 500°C rig(); run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 60; k++) simTick();'); // outlet closed: some water stays in the line box(40, 84, 71, 99, 'EMPTY', 0); box(40, 90, 71, 99, 'MSN', 0); run('for (let y = 90; y < 100; y++) for (let x = 40; x < 72; x++) temp[y * W + x] = 300;'); run('for (let k = 0; k < 120; k++) simTick();'); check('a line holding water will not take molten tin', count(run, run('MSN')) + count(run, run('TIN')) === 32 * 10 && run('pipeNets[0].m') === run('WATER'), `${count(run, run('MSN')) + count(run, run('TIN'))} tin left, line holds ${run('MD[pipeNets[0].m].name')}`); rig(); box(40, 84, 71, 99, 'EMPTY', 0); box(40, 90, 71, 99, 'MCU', 0); run('for (let y = 90; y < 100; y++) for (let x = 40; x < 72; x++) temp[y * W + x] = 1200;'); run('for (let k = 0; k < 60; k++) { for (let y = 90; y < 100; y++) for (let x = 40; x < 72; x++) if (mat[y * W + x] === MCU) temp[y * W + x] = 1200; simTick(); }'); check('a pump will not take molten copper at 1200°C', run('pipeNets[0].n') === 0, run('pipeNets[0].n')); // what is in the pipes is saved rig(); run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 400; k++) simTick();'); const held = lineWater(); run('(() => { const s = serializeWorld(); deserializeWorld(JSON.parse(JSON.stringify(s.meta)), s.buffers); })()'); check('the water in a line survives saving and loading', held > 100 && lineWater() === held, `${held} -> ${lineWater()}`); // pipes are thin: water and sand pass through a pipe run instead of being held back by it flatWorld(run, 240, 160, 100); run('money = 5000; bot.x = 60; bot.y = 50;'); box(36, 76, 39, 99, 'STONE'); box(88, 76, 91, 99, 'STONE'); run('for (let by = 19; by <= 24; by++) placeBlock(BP_PIPE, 0, 15, by, false);'); // a pipe wall at x 60..63 box(40, 80, 59, 99, 'WATER', 0); run('for (let k = 0; k < 400; k++) simTick();'); check('water flows through a run of pipe', count(run, run('WATER'), 64, 70, 87, 99) > 60, `${count(run, run('WATER'), 64, 70, 87, 99)} grains got past`); box(60, 64, 63, 70, 'SAND', 0); run('for (let k = 0; k < 300; k++) simTick();'); check('and sand dropped on a pipe falls down through it', count(run, run('SAND'), 40, 80, 87, 99) > 20, `${count(run, run('SAND'), 40, 80, 87, 99)} of 28 at the bottom`); // ---- the Refiner function refinerRig(feed, n) { flatWorld(run, 240, 160, 100); run(`money = 5000; bot.x = 110; bot.y = 40; stats.refined = 0; stats.refinedOre = 0; placeMachineAt('refiner', 0, 26, 15);`); // x 104..115, y 60..71; chute out at y 72 run(`{ let k = 0; for (let y = 60; y < 70 && k < ${n}; y++) for (let x = 106; x < 114 && k < ${n}; x++, k++) setCell(y * W + x, ${feed}, AMB[y], 0); }`); } refinerRig('RSTONE', 80); const ore = () => count(run, run('CUORE')) + count(run, run('COAL')) + count(run, run('FEORE')) + count(run, run('SNORE')); // five hopper-loads, each ground before the next goes in for (let k = 0; k < 5; k++) { run('for (let k = 0; k < 300; k++) simTick();'); if (k < 4) run('for (let y = 60; y < 70; y++) for (let x = 106; x < 114; x++) setCell(y * W + x, RSTONE, AMB[y], 0);'); } const fed = 400, sand = count(run, run('SAND')), o = ore(); check('the Refiner grinds all the stone rubble it is fed', count(run, run('RSTONE')) === 0 && run('stats.refined') === fed, `${count(run, run('RSTONE'))} left, ${run('stats.refined')} of ${fed} ground`); check('about half as much sand comes out', sand > fed * 0.35 && sand < fed * 0.6, `${sand} sand from ${fed} rubble`); check('with the odd grain of common ore', o > 0 && o < fed * 0.15, `${o} ore grains`); // gravel, sand and dirt are refined too; ore drops straight through refinerRig('LDIRT', 60); run('for (let k = 0; k < 600; k++) simTick();'); check('dirt goes through the refiner too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60); refinerRig('CUORE', 30); run('for (let k = 0; k < 400; k++) simTick();'); check('ore it cannot grind drops straight through, all of it', count(run, run('CUORE')) === 30 && count(run, run('CUORE'), 104, 60, 115, 71) === 0, count(run, run('CUORE'), 104, 60, 115, 71)); // a blocked chute stops it refinerRig('RSTONE', 40); box(100, 72, 120, 73, 'STONE'); run('for (let k = 0; k < 200; k++) simTick();'); check('with the space under its chute blocked it stops, and says so', count(run, run('RSTONE')) === 40 && run("machines.find(m => m && m.type === 'refiner').blocked") === true); done();