From bb8184f7e4bd149f42d2a8f5f74149108da10dfa Mon Sep 17 00:00:00 2001 From: Gator Date: Sun, 1 Jan 2023 22:51:21 +0100 Subject: [PATCH] cells CELLS. interlinked --- src/components/CanvasBoard.vue | 28 ++++----- src/components/MenuReset.vue | 2 +- src/modules/board.js | 59 +++++++++++++++---- src/modules/cell.js | 6 ++ src/modules/core.js | 102 +++++++-------------------------- src/modules/picture.js | 12 ++-- src/stores/index.js | 12 ---- 7 files changed, 94 insertions(+), 127 deletions(-) create mode 100644 src/modules/cell.js diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index 1de00d2..76018ea 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -2,7 +2,6 @@ import { onMounted, watch } from "vue"; import { globalStore } from "../stores/index.js"; import { - createBoard, conwayRules, overpopulationRules, lonelinessRules, @@ -12,6 +11,7 @@ evolve2d, } from "../modules/core.js"; import { + createBoard, create1dInitialState, create2dRandomGrid, } from "../modules/board.js"; @@ -100,30 +100,31 @@ // draw elementary automaton on the canvas based on selected ruleset const draw1d = () => { const initialState = create1dInitialState( - store.board, + store.board.width, store.initial1dState ); - const board = createBoard(initialState, store.ruleset1d.rules, max()); - store.board.grid = Object.freeze(board); + const grid = createBoard(initialState, store.ruleset1d.rules, max()); + store.board.grid = Object.freeze(grid); drawCanvas(store.board); store.toggleStop(); }; // draw 2D automaton on the canvas in a loop const draw2d = (board) => { - drawCanvas(store.board); - const newBoard = Object.freeze(evolve2d(board.grid, selectedRules())); - if (store.board.grid == newBoard) store.toggleStop(); - store.board.grid = newBoard; + drawCanvas(board); + const newGrid = evolve2d(board.grid, board.width, selectedRules()); + store.board.grid = Object.freeze(newGrid); }; // draw 2d automaton in a loop, starting from passed state const draw2dNext = async (board) => { setTimeout(() => { if (!store.canDraw) return; - draw2d(board); - return draw2dNext(store.board); - }, store.renderer.refreshRate); + requestAnimationFrame(() => { + draw2d(board); + return draw2dNext(store.board); + }); + }, 24); }; // draw 2d automaton from a new state @@ -133,7 +134,7 @@ store.board.width, store.board.height ); - store.board.grid = Object.freeze(evolve2d(initialGrid, selectedRules())); + store.board.grid = Object.freeze(initialGrid); if (store.loop) return draw2dNext(store.board); else draw2d(store.board); store.toggleStop(); @@ -142,6 +143,7 @@ // draw 2d automaton from the last known generated board const draw2dLast = async () => { if (!store.canDraw) return; + if (!store.board.grid) return; if (store.loop) return draw2dNext(store.board); else draw2d(store.board); store.toggleStop(); @@ -162,7 +164,7 @@ store.board.width, store.board.height ); - const newBoard = picToBoard(resized.data, store.board); + const newBoard = picToBoard(resized.data); store.board.grid = Object.freeze(newBoard); store.toggleStop(); }; diff --git a/src/components/MenuReset.vue b/src/components/MenuReset.vue index 2da985d..7c75a00 100644 --- a/src/components/MenuReset.vue +++ b/src/components/MenuReset.vue @@ -22,7 +22,7 @@ name="next" class="next" value="Next" - @click="store.toggleNext()" + @click="store.toggleDraw2dLast()" /> new Cell(s))); + prevState = nextState; + } + return board; +} + // create a first state, either a single living cell // at the center or random ones -const create1dInitialState = (board, type = "onecell") => { - if (type === "onecell") return create1dStateOneCell(board.width); - return create1dState(board.width, getRandomInt, [0, 2]); +export const create1dInitialState = (width, type = "onecell") => { + if (type === "onecell") return create1dStateOneCell(width); + return create1dState(width, getRandomInt, [0, 2]); }; // initialize 2d board with random cells -const create2dRandomGrid = (width, height) => { +export const create2dRandomGrid = (width, height) => { return create2dState(width, height, getRandomInt, [0, 2]); }; - -export { Board, create1dInitialState, create2dRandomGrid }; diff --git a/src/modules/cell.js b/src/modules/cell.js new file mode 100644 index 0000000..8c4a79e --- /dev/null +++ b/src/modules/cell.js @@ -0,0 +1,6 @@ +export class Cell { + constructor(state = 0) { + this.state = state; + this.prevState = null; + } +} diff --git a/src/modules/core.js b/src/modules/core.js index 499c4e2..517519d 100644 --- a/src/modules/core.js +++ b/src/modules/core.js @@ -1,5 +1,7 @@ // core functions to generate initial states and evolve them +import { Cell } from "./cell.js"; + // get the next evolution of a 1D CA initial state // buggy BUT produces interesting results // function evolve1d(state, rules) { @@ -23,41 +25,6 @@ function evolve1d(state, rules) { return [rules[edge1], ...center, rules[edge2]]; } -// 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, max) { - var board = []; - let prevState = []; - for (let i = 0; i < max; i++) { - let nextState = []; - // use the passed initial step during first iteration - if (i == 0) { - nextState = evolve1d(state, rules); - } else { - nextState = evolve1d(prevState, rules); - } - // flat array - board.push(...nextState); - prevState = nextState; - } - return board; -} - -// 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) { @@ -130,48 +97,23 @@ function overpopulationRules(cell, neighbors) { // get the next evolution of a 2D CA initial state // Rules : Moore neighborhood -function evolve2d(board, rulesFn) { - 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) { - 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, - }; - }); +function evolve2d(grid, width, rulesFn) { + const bh = grid.length - 1; + const bw = width; + return grid.reduce((acc, cell, y) => { + const sum = + grid[Math.abs((y - 1) % bh)].state + + grid[Math.abs((y + 1) % bh)].state + + grid[Math.abs((y - bw - 1) % bh)].state + + grid[Math.abs((y - bw) % bh)].state + + grid[Math.abs((y - bw + 1) % bh)].state + + grid[Math.abs((y + bw - 1) % bh)].state + + grid[Math.abs((y + bw) % bh)].state + + grid[Math.abs((y + bw + 1) % bh)].state; + acc[y] = new Cell(rulesFn(cell.state, sum)); + acc[y].prevState = cell.state; + return acc; + }, []); } // Populates the first state with a single living cell in the center @@ -191,16 +133,12 @@ function create1dState(width, 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)) - ); + return [...Array(height * width)].map(() => new Cell(initFn(...args))); } export { - getDrawingValues, create1dState, create2dState, - createBoard, create1dStateOneCell, conwayRules, overpopulationRules, diff --git a/src/modules/picture.js b/src/modules/picture.js index 6edda32..41933b9 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -1,3 +1,4 @@ +import { Cell } from "./cell.js"; // https://stackoverflow.com/questions/21646738/convert-hex-to-rgba // [ function hexToRGB(hex) { @@ -36,17 +37,14 @@ export function picToBlackAndWhite(pixels, width, height) { } // convert an ImageData into a 2D array of boolean (0, 1) values -export function picToBoard(pixels, board) { - const flat = pixels.reduce((acc, pixel, index) => { +export function picToBoard(pixels) { + return pixels.reduce((acc, pixel, index) => { const i = index * 4; const count = pixels[i] + pixels[i + 1] + pixels[i + 2]; const value = (count >= 255) & 1; - acc[index] = value; + acc[index] = new Cell(value); return acc; }, []); - // TODO: The representation has to be 2D, not the data structure - // (change to flat) - return toMatrix(flat, board.width, board.height); } // convert board to ImageData @@ -58,7 +56,7 @@ export function boardToPic(board) { const img = new ImageData(board.width, board.height); const colors = [hexToRGB(live), hexToRGB(dead)]; board.grid.reduce((acc, cell, index) => { - const color = colors[(cell === 1) & 1]; + const color = colors[(cell.state === 1) & 1]; const i = index * 4; acc[i] = color[0]; acc[i + 1] = color[1]; diff --git a/src/stores/index.js b/src/stores/index.js index b0693d3..a939b72 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -103,18 +103,6 @@ export const globalStore = defineStore("globalStore", { toggleLoop() { this.loop = !this.loop; }, - toggleNext() { - switch (this.lastAction) { - case "drawfromlast": - this.toggleDraw2dLast(); - break; - case "draw2d": - this.toggleDraw2d(); - break; - default: - return; - } - }, setActiveSubMenu(data) { if (this.activeSubMenu == data) this.activeSubMenu = ""; else this.activeSubMenu = data;