function* q2(input, cycles) {
const t0 = performance.now();
const timing = [];
const stride = cycles + Math.max(input.length, input[0].length);
const W = stride * stride * stride * 8;
const X = stride * stride * 4;
const Y = stride * 2;
const WXYZ = (w, x, y, z) =>
(w + stride) * W + (x + stride) * X + (y + stride) * Y + (z + stride);
const getWXYZ = wxyz => {
const w = (wxyz / W) | 0;
wxyz -= w * W;
const x = (wxyz / X) | 0;
wxyz -= x * X;
const y = (wxyz / Y) | 0;
wxyz -= y * Y;
const z = wxyz - stride;
return [w - stride, x - stride, y - stride, z];
};
let activeCubes = new Set();
for (let y = 0; y < input.length; y++) {
const row = input[y];
for (let x = 0; x < row.length; x++) {
if (row[x]) activeCubes.add(WXYZ(0, x, y, 0));
}
}
let nextCubes = new Set(),
maybeNextCubes = new Set();
const countNeighbors = new Map();
for (let n = 0; n < cycles; n++) {
// Count neighbors for all cubes adjacent to active cubes
countNeighbors.clear();
maybeNextCubes.clear();
let t1 = performance.now();
for (const wxyz of activeCubes.values()) {
// note that active cubes count themselves as
// neighbors (h = i = j = k = 0) so we have to
// adjust for that later
// const [w, x, y, z] = getWXYZ(wxyz);
// for (let h = -1; h <= 1; h++) {
// for (let i = -1; i <= 1; i++) {
// for (let j = -1; j <= 1; j++) {
// for (let k = -1; k <= 1; k++) {
// const wxyzN = WXYZ(w + h, x + i, y + j, z + k);
// Optimization: we can easily compute wxyzN directly,
// saving a tiny bit of call overhead on getWXYZ and WXYZ
for (let w = -W; w <= W; w += W) {
for (let x = -X; x <= X; x += X) {
for (let y = -Y; y <= Y; y += Y) {
for (let z = -1; z <= 1; z++) {
const wxyzN = wxyz + w + x + y + z;
// abuse Map.get(<not present>) => undefined,
// and undefined | 0 being converted to 0
const neighbors = (countNeighbors.get(wxyzN) | 0) + 1;
// Optimization:
// If active and 2 or 3 neighbors (so totalNeighbors 3 or 4,
// because the count includes the current active cube),
// active cube remains active.
// If inactive and exactly 3 neighbors, become active.
// Therefore we can pre-filter: only add cubes with
// three neighbors to nextCubes, remove cubes
// with five neighbors, and skip
// No need to update if more than five neighbors, since
// the cube will always end up inactive with five or more
// neighbors, and we already mark that when we reach
// five neighbors
if (neighbors > 5) continue;
else if (neighbors === 3) maybeNextCubes.add(wxyzN);
else if (neighbors === 5) maybeNextCubes.delete(wxyzN);
countNeighbors.set(wxyzN, neighbors);
}
}
}
}
let t2 = performance.now();
if (t2 - t1 > 200) {
t1 = t2;
yield { n, timing, activeCubes, time: t2 - t0 };
}
}
// for all cubes in nextCubes, determine if
// 3 + inactive, or (3|4) + active. If so
// they will be active the next round
nextCubes.clear();
for (const wxyz of maybeNextCubes.values()) {
if (countNeighbors.get(wxyz) === 3 || activeCubes.has(wxyz))
nextCubes.add(wxyz);
let t2 = performance.now();
if (t2 - t1 > 200) {
t1 = t2;
yield { n, timing, activeCubes, time: t2 - t0 };
}
}
activeCubes.clear();
[activeCubes, nextCubes] = [nextCubes, activeCubes];
let t2 = performance.now();
timing.push(t2 - t0);
yield { n, timing, activeCubes, time: t2 - t0 };
}
yield { cycles, timing, activeCubes, time: performance.now() - t0 };
}