diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index 1eedb8f..1de00d2 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -111,19 +111,19 @@ // draw 2D automaton on the canvas in a loop const draw2d = (board) => { - store.board.grid = Object.freeze(evolve2d(board.grid, selectedRules())); drawCanvas(store.board); + const newBoard = Object.freeze(evolve2d(board.grid, selectedRules())); + if (store.board.grid == newBoard) store.toggleStop(); + store.board.grid = newBoard; }; // draw 2d automaton in a loop, starting from passed state const draw2dNext = async (board) => { setTimeout(() => { - requestAnimationFrame(() => { - if (!store.canDraw) return; - draw2d(board); - return draw2dNext(store.board); - }); - }, store.refreshRate); + if (!store.canDraw) return; + draw2d(board); + return draw2dNext(store.board); + }, store.renderer.refreshRate); }; // draw 2d automaton from a new state diff --git a/src/modules/core.js b/src/modules/core.js index 31008c4..e77c072 100644 --- a/src/modules/core.js +++ b/src/modules/core.js @@ -1,23 +1,26 @@ // core functions to generate initial states and evolve them -// handles negative index and index bigger than its array length -function guard(index, arrayLength) { - if (index > arrayLength - 1) return 0; - if (index < 0) return arrayLength - 1; - return index; -} +// get the next evolution of a 1D CA initial state +// buggy BUT produces interesting results +// function evolve1d(state, rules) { +// const sl = state.length - 1; +// return [ +// rules[[state[sl - 1], state[sl], state[sl + 1]].join("")], +// ...state.map((_,x) => (rules[[state[x - 1], state[x], state[x + 1]].join("")])).slice(0, sl), +// rules[[state[0], state[1], state[2]].join("")] +// ] +// } // get the next evolution of a 1D CA initial state function evolve1d(state, rules) { - function getCell(index) { - const safeIndex = guard(index, state.length); - 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); + const sl = state.length - 1; + const edge1 = [state[sl - 2], state[sl - 1], state[sl]].join(""); + const edge2 = [state[0], state[1], state[2]].join(""); + // normal case (3 neighbor cells) + const center = state + .map((_, x) => rules[[state[x - 1], state[x], state[x + 1]].join("")]) + .slice(1, sl); + return [rules[edge1], ...center, rules[edge2]]; } // create a 2D board from a 1D CA initial state @@ -49,31 +52,6 @@ function createBoard(state, rules, max) { 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.length); - const safeY = guard(yy, rowLength.length); - 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); @@ -152,13 +130,31 @@ function overpopulationRules(cell, neighbors) { // 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 bh = board.length - 1; + const bw = board[0].length - 1; + return board.map((row, y) => { + // handle edges + const prow = y - 1 < 0 ? board[bh] : board[y - 1]; + const nrow = y + 1 > bh ? board[0] : board[y + 1]; + return row.map((cell, x) => { + // handle edges too + const pcell = x - 1 < 0 ? bw : x - 1; + const ncell = x + 1 > bw ? 0 : x + 1; + // the current cell is not included in the result + const neighbors = [ + prow[pcell], + prow[x], + prow[ncell], + row[pcell], + row[ncell], + nrow[pcell], + nrow[x], + nrow[ncell], + ]; const sum = getNeighborsSum(neighbors); return rulesFn(cell, sum); - }) - ); + }); + }); } function getDrawingValues(state, acc, cell) { diff --git a/src/modules/picture.js b/src/modules/picture.js index a65e566..117d358 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -40,7 +40,7 @@ export function picToBoard(pixels, board) { const flat = pixels.reduce((acc, pixel, index) => { const i = index * 4; const count = pixels[i] + pixels[i + 1] + pixels[i + 2]; - const value = count >= 255 ? 1 : 0; + const value = (count >= 255) & 1; acc[index] = value; return acc; }, []); @@ -58,8 +58,7 @@ export function boardToPic(board) { const img = new ImageData(board.width, board.height); const colors = [hexToRGB(live), hexToRGB(dead)]; board.grid.flat().reduce((acc, cell, index) => { - // TODO : bitshift operation instead of ternary - const color = cell === 1 ? colors[0] : colors[1]; + const color = colors[(cell === 1) & 1]; const i = index * 4; acc[i] = color[0]; acc[i + 1] = color[1]; diff --git a/src/modules/preset.js b/src/modules/preset.js index 6b7a261..fc05755 100644 --- a/src/modules/preset.js +++ b/src/modules/preset.js @@ -103,6 +103,32 @@ const presetRuleset = [ "000": 1, }, }, + { + name: "rule 184", + rules: { + 100: 1, + 101: 1, + 110: 0, + 111: 1, + "011": 1, + "010": 0, + "001": 0, + "000": 0, + }, + }, + { + name: "rule 110", + rules: { + 100: 0, + 101: 1, + 110: 1, + 111: 0, + "011": 1, + "010": 1, + "001": 1, + "000": 0, + }, + }, ]; const initialStates = [ diff --git a/tests/core.test.js b/tests/core.test.js new file mode 100644 index 0000000..355865b --- /dev/null +++ b/tests/core.test.js @@ -0,0 +1,18 @@ +import { describe, expect, test } from "vitest"; +import { evolve1d, create1dStateOneCell } from "src/modules/core.js"; +import { presetRuleset } from "src/modules/preset.js"; + +describe("Core", () => { + test("evolve1d, rules73, 9 cells", () => { + const state = [0, 0, 0, 0, 1, 0, 0, 0, 0]; + const got = evolve1d(state, presetRuleset[0].rules); + const valid = [1, 1, 1, 0, 0, 0, 1, 1, 1]; + expect(got.length).toBe(state.length); + expect(got).toEqual(valid); + }), + test("create1dStateOneCell, 49 cells", () => { + const got = create1dStateOneCell(49); + expect(got.length).toBe(49); + expect(got[24]).toBe(1); + }); +});