// Pipes, pumps and outlets: each joined line holds one liquid as a count, not as grains. // ---------- Pipes ---------- // Pipes, pumps and outlets that touch join into a line. A line holds one liquid at a time, // PIPE_CAP grains per block, kept as a count (it is not simulated grain by grain inside). // Pumps draw liquid in at their intake side; outlets let it out where there is room. const PIPE_CAP = 16, PUMP_RATE = 1, OUTLET_RATE = 2, PUMP_MAX_T = 500; const SIDE_V = [[0, -1], [1, 0], [0, 1], [-1, 0]]; // up, right, down, left const PUMP_V = SIDE_V; // pump variants: the way it pushes (its intake is the opposite side) const OUTLET_V = [[0, 1], [-1, 0], [0, -1], [1, 0]]; // outlet variants: down, left, up, right let pipeNets = [], pipeNetAt = null, pipesDirty = true; function sideOf(dx, dy) { for (let s = 0; s < 4; s++) if (SIDE_V[s][0] === dx && SIDE_V[s][1] === dy) return s; return -1; } // the one side of a block that does not join the line: a pump's intake, an outlet's mouth function pipeClosedSide(b) { const c = blockP[b], d = blockV[b] & 3; if (c === BP_PUMP) return sideOf(-PUMP_V[d][0], -PUMP_V[d][1]); if (c === BP_OUTLET) return sideOf(OUTLET_V[d][0], OUTLET_V[d][1]); return -1; } function pipeJoins(b, s) { const bx = b % BW, by = (b / BW) | 0, nx = bx + SIDE_V[s][0], ny = by + SIDE_V[s][1]; if (nx < 0 || ny < 0 || nx >= BW || ny >= BH || s === pipeClosedSide(b)) return -1; const d = ny * BW + nx; return isPipeCode(blockP[d]) && pipeClosedSide(d) !== (s + 2) % 4 ? d : -1; } function rebuildPipes() { pipesDirty = false; const old = pipeNets, oldAt = pipeNetAt; pipeNetAt = new Int32Array(NB).fill(-1); pipeNets = []; for (let b0 = 0; b0 < NB; b0++) { if (!isPipeCode(blockP[b0]) || pipeNetAt[b0] >= 0) continue; const id = pipeNets.length, net = { blocks: [], pumps: [], outlets: [], m: 0, n: 0, T: 20, cap: 0, flowT: -999 }; pipeNets.push(net); pipeNetAt[b0] = id; const st = [b0]; while (st.length) { const b = st.pop(); net.blocks.push(b); if (blockP[b] === BP_PUMP) net.pumps.push(b); else if (blockP[b] === BP_OUTLET) net.outlets.push(b); for (let s = 0; s < 4; s++) { const d = pipeJoins(b, s); if (d >= 0 && pipeNetAt[d] < 0) { pipeNetAt[d] = id; st.push(d); } } } net.cap = net.blocks.length * PIPE_CAP; } // what the old lines held goes with their blocks into the new lines if (oldAt) for (const o of old) { if (!o.n) continue; const share = new Map(); let alive = 0; for (const b of o.blocks) { const id = pipeNetAt[b]; if (id >= 0) { share.set(id, (share.get(id) || 0) + 1); alive++; } } let left = o.n; const parts = [...share]; parts.forEach(([id, k], idx) => { const amt = idx === parts.length - 1 ? left : Math.min(left, Math.round(o.n * k / alive)); left -= amt; const net = pipeNets[id]; if (!amt) return; if (net.n && net.m !== o.m) { if (net.n >= amt) return; net.n = 0; } // two liquids met: the smaller lot is lost net.T = (net.T * net.n + o.T * amt) / (net.n + amt); net.m = o.m; net.n = Math.min(net.cap, net.n + amt); }); } } function pipeNetOf(b) { if (pipesDirty) rebuildPipes(); const id = pipeNetAt ? pipeNetAt[b] : -1; return id >= 0 ? pipeNets[id] : null; } // take block b's share of its line's liquid out (it is about to be removed) function pipeTakeShare(b) { const net = pipeNetOf(b); if (!net || !net.n) return null; const k = Math.min(net.n, PIPE_CAP, Math.round(net.n / net.blocks.length)); net.n -= k; const r = { m: net.m, n: k, T: net.T }; if (!net.n) net.m = 0; return r; } // the cells just outside side s of block b, lane by lane; depth grains further out function faceCell(b, s, lane, depth) { const bx = b % BW, by = (b / BW) | 0; let x, y; if (s === 0) { x = bx * BS + lane; y = by * BS - 1 - depth; } else if (s === 2) { x = bx * BS + lane; y = by * BS + BS + depth; } else if (s === 1) { x = bx * BS + BS + depth; y = by * BS + lane; } else { x = bx * BS - 1 - depth; y = by * BS + lane; } return x < 0 || y < 0 || x >= W || y >= H ? -1 : y * W + x; } function pumpDraw(net, b) { const s = sideOf(-PUMP_V[blockV[b] & 3][0], -PUMP_V[blockV[b] & 3][1]); let got = 0; for (let k = 0; k < 4 && got < PUMP_RATE && net.n < net.cap; k++) { const lane = (k + tick) & 3; for (let depth = 0; depth < 3; depth++) { const j = faceCell(b, s, lane, depth); if (j < 0) break; const m = mat[j]; if (PH[m] !== PH_LIQUID) { if (PH[m] === PH_SOLID && !part[j]) break; continue; } if (temp[j] > PUMP_MAX_T || occ[j] || (net.n && m !== net.m)) break; net.T = (net.T * net.n + temp[j]) / (net.n + 1); net.m = m; net.lm = m; net.n++; setCell(j, EMPTY, AMB[(j / W) | 0], 0); got++; net.flowT = tick; break; } } } function outletPour(net, b) { const v = OUTLET_V[blockV[b] & 3], s = sideOf(v[0], v[1]); let put = 0; for (let k = 0; k < 4 && put < OUTLET_RATE && net.n > 0; k++) { const j = faceCell(b, s, (k + tick) & 3, 0); if (j < 0 || mat[j] !== EMPTY || occ[j] || (part[j] && part[j] !== P_GRATE && part[j] !== P_GOPEN) || mach[j]) continue; setCell(j, net.m, net.T, 0); net.n--; put++; net.flowT = tick; } if (!net.n) net.m = 0; } function pipesTick() { if (pipesDirty) rebuildPipes(); for (const net of pipeNets) { for (const b of net.pumps) if (!(blockV[b] & 4)) pumpDraw(net, b); if (net.n) for (const b of net.outlets) if (!(blockV[b] & 4)) outletPour(net, b); } }