// handles negative index and index bigger than its array length function guard(index, array) { if (index > array.length - 1) return 0; if (index < 0) return array.length - 1; return index; } // get the next evolution of a 1D CA initial state function evolve1d(state, rules) { function getCell(index) { const safeIndex = guard(index, state); return state[safeIndex]; } const newState = state.map((_, x) => { const cells = [getCell(x - 1), getCell(x), getCell(x + 1)]; return rules[cells.join("")]; }); return newState.map(Number); } // create a 2D board from a 1D CA initial state // function createBoard(state, rules, height) { // function createBoardAcc(s, h, acc) { // if (h === 0) return acc; // const newState = evolve1d(s, rules); // const newAcc = acc.concat([s]); // return createBoardAcc(newState, h - 1, newAcc); // } // return createBoardAcc(state, height, []); // } // performance "choke point" in full imperative function createBoard(state, rules, height) { var board = []; let prevState = []; for (let i = 0; i < height; i++) { let nextState = []; if (i == 0) { nextState = evolve1d(state, rules); } else { nextState = evolve1d(prevState, rules); } board = board.concat([nextState]); prevState = nextState; } return board; } // Find the neighbor of a given cell in a 2D CA board function getCellNeighbors(board, cellCoordinates) { const [x, y] = cellCoordinates; const rowLength = board[0].length; // caca? // handles board edges where the cell is missing neighbors function getCell(xx, yy) { const safeX = guard(xx, board); const safeY = guard(yy, rowLength); return board[safeX][safeY]; } // the current cell is not included in the result return [ getCell(x - 1, y - 1), getCell(x, y - 1), getCell(x + 1, y - 1), getCell(x - 1, y), getCell(x + 1, y), getCell(x - 1, y + 1), getCell(x, y + 1), getCell(x + 1, y - 1), ]; } // Sums the value of a cell's neighbors function getNeighborsSum(cells) { return cells.reduce((cell, acc) => cell + acc, 0); } // Get the next evolution of a cell according to // Conway's game of life rules function conwayRules(cell, neighbors) { // loneliness rule if (cell === 1 && neighbors < 2) return 0; // overpopulation rule if (cell === 1 && neighbors > 3) return 0; // born when three live neighbors rule if (cell === 0 && neighbors === 3) return 1; // the cell remains the same if none apply return cell; } // get the next evolution of a 2D CA initial state // Rules : Moore neighborhood function evolve2d(board, rulesFn) { return board.map((row, x) => row.map((cell, y) => { const neighbors = getCellNeighbors(board, [x, y]); const sum = getNeighborsSum(neighbors); return rulesFn(cell, sum); }) ); } function getDrawingValues(state, acc, cell) { const d = cell.dimension; return Object.keys(state).map((key) => { const fillStyle = (() => { if (state[key] === "1") return cell.liveColor; return cell.deadColor; })(); return { move: [key * d, acc * d], fill: [key * d, acc * d, d, d], fillStyle, }; }); } // Populates the first state with a single living cell in the center function create1dStateOneCell(width) { return [...Array(width)].map((cell, index) => { if (index === width / 2 || index === width + 1 / 2) return 1; return 0; }); } // Populates the first state of a 1D CA with cells returned // by initFn function create1dState(width, initFn, args) { return [...Array(width)].map(() => initFn(...args)); } // Populates the first state of a 2D CA with cells returned // by initFn function create2dState(width, height, initFn, args) { return [...Array(height)].map(() => [...Array(width)].map(() => initFn(...args)) ); } export { getDrawingValues, create1dState, create2dState, createBoard, create1dStateOneCell, conwayRules, evolve1d, evolve2d, };