From 61846b88a5d7d43a39ecfbd56c9d9ba6e160b8ad Mon Sep 17 00:00:00 2001 From: Gator Date: Sun, 18 Dec 2022 13:50:21 +0100 Subject: [PATCH 1/9] high life rule --- src/components/CanvasBoard.vue | 2 ++ src/modules/automata.js | 16 ++++++++++++++++ src/modules/preset.js | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index c2f3373..38f1d56 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -24,6 +24,7 @@ overpopulationRules, lonelinessRules, threebornRules, + highLifeRules, evolve2d, } from "../modules/automata.js"; import { getRandomInt, sleep } from "../modules/common.js"; @@ -42,6 +43,7 @@ overpopulation: overpopulationRules, loneliness: lonelinessRules, threeborn: threebornRules, + highlife: highLifeRules, }, }; }, diff --git a/src/modules/automata.js b/src/modules/automata.js index ac4df56..87ab8ad 100644 --- a/src/modules/automata.js +++ b/src/modules/automata.js @@ -91,6 +91,21 @@ function conwayRules(cell, neighbors) { return cell; } +// variation of the game of life where a +// cell comes to live if 6 neigbor cells are alive +function highLifeRules(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 === 2) return 1; + // highlife rules + if ((cell === 0 && neighbors === 3) || neighbors === 6) return 1; + // the cell remains the same if none apply + return cell; +} + // variation on the game of life's rules, // where the "three live neighbors" rule is ignored function threebornRules(cell, neighbors) { @@ -184,6 +199,7 @@ export { overpopulationRules, lonelinessRules, threebornRules, + highLifeRules, evolve1d, evolve2d, }; diff --git a/src/modules/preset.js b/src/modules/preset.js index 922aab0..731d18e 100644 --- a/src/modules/preset.js +++ b/src/modules/preset.js @@ -116,6 +116,12 @@ const preset2dRules = [ description: "Variation on Conway's Game of Life *without* the 'three live neighbors' rule", }, + { + id: "highlife", + name: "HighLife variation", + description: + "Variation on Conway's Game of Life where a cell live if the six neighbor cells are alive", + }, ]; export { presetRuleset, initialStates, preset2dRules }; -- 2.45.2 From cc4a7b08b389010f27a6307ccae628ad38b95498 Mon Sep 17 00:00:00 2001 From: Gator Date: Mon, 19 Dec 2022 16:00:38 +0100 Subject: [PATCH 2/9] wip optimizing render --- README.md | 1 + src/components/CanvasBoard.vue | 170 +++++++++++++++++---------------- src/components/Menu2dCA.vue | 1 - src/modules/automata.js | 10 ++ src/modules/picture.js | 2 +- src/modules/preset.js | 18 ++++ src/stores/index.js | 3 +- 7 files changed, 119 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 34b63fe..1d41d91 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,4 @@ See [Configuration Reference](https://vitejs.dev/guide/). - https://en.wikipedia.org/wiki/Hashlife - https://plato.stanford.edu/entries/cellular-automata/supplement.html - https://www.conwaylife.com/wiki/Cellular_automaton +- https://conwaylife.com/ diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index 38f1d56..ce484d8 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -25,6 +25,7 @@ lonelinessRules, threebornRules, highLifeRules, + servietteRules, evolve2d, } from "../modules/automata.js"; import { getRandomInt, sleep } from "../modules/common.js"; @@ -44,11 +45,13 @@ loneliness: lonelinessRules, threeborn: threebornRules, highlife: highLifeRules, + serviette: servietteRules, }, }; }, computed: { ...mapState(globalStore, { + loop: "loop", cellProperties: "cellProperties", ruleset: "ruleset1d", refreshRate: "refreshRate", @@ -61,7 +64,6 @@ getDraw2dPicture: "draw2dpicture", boardWidth: "boardWidth", boardHeight: "boardHeight", - picture: "picture", selected2dRules: "selected2dRules", }), ...mapWritableState(globalStore, { @@ -69,11 +71,15 @@ canvasWidth: "canvasWidth", canvasHeight: "canvasHeight", getReset: "reset", + picture: "picture", }), // used to determine the dimensions of the board max() { return Math.max(this.boardWidth, this.boardHeight); }, + selectedRules() { + return this.available2dRules[this.selected2dRules.id]; + }, }, watch: { getDraw1d(value) { @@ -111,21 +117,32 @@ "setBoardHeight", ]), // draws the board on the canvas - drawCanvas(board) { - const props = this.cellProperties; - board.map((row, y) => { - const d = props.size; - return row.map((cell, x) => { - this.ctx.fillStyle = (() => { - if (cell === 1) return props.liveColor; - return props.deadColor; - })(); - if (this.drawingDirection === "x") - this.ctx.fillRect(y * d, x * d, d, d); - else this.ctx.fillRect(x * d, y * d, d, d); - return cell; - }); - }); + drawCanvas(board, width, height) { + const d = this.cellProperties.size; + // bool to RGBA colors + const img = board.flat().reduce((acc, cell, index) => { + const color = cell === 1 ? 0 : 255; + const i = index * 4; + acc.data[i] = color; + acc.data[i + 1] = color; + acc.data[i + 2] = color; + acc.data[i + 3] = 255; + return acc; + }, new ImageData(width, height)); + // rescale and draw + this.ctx.save(); + this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); + this.workCtx.putImageData(img, 0, 0); + this.ctx.imageSmoothingEnabled = false; + if (this.getDraw2dPicture != true) this.ctx.scale(d, d); + this.ctx.drawImage( + this.workCanvas, + 0, + 0, + this.canvasWidth, + this.canvasHeight + ); + this.ctx.restore(); }, // create a first state, either a single living cell // at the center or random ones @@ -134,101 +151,88 @@ return create1dStateOneCell(this.boardWidth); return create1dState(this.boardWidth, getRandomInt, [0, 2]); }, - // draw elementary automaton on the canvas based on selected ruleset - draw1d() { - const initialState = this.compute1dInitialState(); - const board = createBoard(initialState, this.ruleset.rules, this.max); - this.lastBoard = Object.freeze(board); - this.drawCanvas(this.lastBoard); - this.toggleStop(); - }, - // draw 2D automaton on the canvas in a loop - draw2d(board) { - if (!this.canDraw) return; - const draw2dNext = async (b) => { - if (!this.canDraw) return; - const newBoard = evolve2d( - b, - this.available2dRules[this.selected2dRules.id] - ); - this.drawCanvas(b, this.cellProperties); - this.lastBoard = Object.freeze(newBoard); - await sleep(this.refreshRate); - draw2dNext(newBoard); - }; - return draw2dNext(board); - }, - // draw 2d automaton from a new state - draw2dNew() { - const initialState = create2dState( + // initialize board with random cells + randomInitialState() { + return create2dState( this.boardWidth, this.boardHeight, getRandomInt, [0, 2] ); - const board = evolve2d(initialState, conwayRules); + }, + // draw elementary automaton on the canvas based on selected ruleset + draw1d() { + const initialState = this.compute1dInitialState(); + const board = createBoard(initialState, this.ruleset.rules, this.max); this.lastBoard = Object.freeze(board); - this.draw2d(board); + this.drawCanvas(this.lastBoard, this.boardWidth, this.boardHeight); + this.toggleStop(); + }, + // draw 2D automaton on the canvas in a loop + draw2d(board) { + const newBoard = evolve2d(board, this.selectedRules); + this.drawCanvas(newBoard, this.boardWidth, this.boardHeight); + this.lastBoard = Object.freeze(newBoard); + }, + // draw 2d automaton in a loop, starting from passed state + async draw2dNext(board) { + requestAnimationFrame(() => { + if (!this.canDraw) return; + const newBoard = evolve2d(board, this.selectedRules); + this.draw2d(board); + this.lastBoard = Object.freeze(newBoard); + /* await sleep(this.refreshRate) */ + return this.draw2dNext(newBoard); + }); + }, + // draw 2d automaton from a new state + async draw2dNew() { + if (!this.canDraw) return; + const initialState = this.randomInitialState(); + let board = evolve2d(initialState, conwayRules); + if (this.loop) return this.draw2dNext(board); + else this.draw2d(board); + this.toggleStop(); }, // draw 2d automaton from the last known generated board - draw2dLast() { - if (this.lastBoard != undefined) this.draw2d(this.lastBoard); + async draw2dLast() { + if (!this.canDraw) return; + if (this.loop) return this.draw2dNext(this.lastBoard); + else this.draw2d(this.lastBoard); + this.toggleStop(); }, // draw 2d automaton from an uploaded picture. // use the picture representation as an initial state draw2dPicture() { // get image data by drawing it on a work canvas - this.workCtx.drawImage( + this.ctx.fillStyle = "black"; + this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); + this.ctx.drawImage( this.picture, - 0, - 0, - this.canvasWidth, - this.canvasHeight + Math.floor((this.canvasWidth - this.picture.width) / 2), + Math.floor((this.canvasHeight - this.picture.height) / 2), + this.picture.width, + this.picture.height ); - const imgData = this.workCtx.getImageData( + + const imgData = this.ctx.getImageData( 0, 0, this.canvasWidth, this.canvasHeight ); - // convert the image to black and white - const black = picToBlackAndWhite( - imgData.data, - this.canvasWidth, - this.canvasHeight - ); - - // draw it back on the canvas - this.ctx.putImageData(black, 0, 0); - - // draw the image back on the work canvas with the dimensions of the board - this.workCtx.drawImage( - this.picture, - 0, - 0, - this.boardWidth, - this.boardHeight - ); - - const resized = this.workCtx.getImageData( - 0, - 0, - this.boardWidth, - this.boardHeight - ); - - // convert the resized image into a 2D board of boolean based on pixel value + // convert the image into a 2D board of boolean based on pixel value this.lastBoard = Object.freeze( - picToBoard(resized.data, this.boardWidth, this.boardHeight) + picToBoard(imgData.data, this.canvasWidth, this.canvasHeight) ); - this.toggleStop(); }, // stop drawing routines and clear the canvas reset() { this.toggleStop(); - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.lastBoard = {}; + this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); this.getReset = 0; }, }, diff --git a/src/components/Menu2dCA.vue b/src/components/Menu2dCA.vue index e8a8afc..ee010a2 100644 --- a/src/components/Menu2dCA.vue +++ b/src/components/Menu2dCA.vue @@ -67,7 +67,6 @@ preparePicture(event) { const files = event.target.files; this.picture = new Image(); - this.picture.width = this.canvasWidth; if (FileReader && files && files.length) { const reader = new FileReader(); diff --git a/src/modules/automata.js b/src/modules/automata.js index 87ab8ad..ec3aa00 100644 --- a/src/modules/automata.js +++ b/src/modules/automata.js @@ -91,6 +91,15 @@ function conwayRules(cell, neighbors) { return cell; } +// Get the next evolution of a cell according to +// Conway's game of life rules +function servietteRules(cell, neighbors) { + // loneliness rule + if (cell === 0 && [2, 3, 4].find((x) => x == neighbors)) return 1; + // the cell remains the same if none apply + return 0; +} + // variation of the game of life where a // cell comes to live if 6 neigbor cells are alive function highLifeRules(cell, neighbors) { @@ -200,6 +209,7 @@ export { lonelinessRules, threebornRules, highLifeRules, + servietteRules, evolve1d, evolve2d, }; diff --git a/src/modules/picture.js b/src/modules/picture.js index 9a91b67..6907a72 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -31,7 +31,7 @@ export function picToBoard(pixels, width, height) { if (index % 4 == 0) { const count = pixels[index] + pixels[index + 1] + pixels[index + 2]; const value = count >= 255 ? 1 : 0; - acc.push(value); + acc[index] = value; } return acc; }, []); diff --git a/src/modules/preset.js b/src/modules/preset.js index 731d18e..6835011 100644 --- a/src/modules/preset.js +++ b/src/modules/preset.js @@ -77,6 +77,19 @@ const presetRuleset = [ "000": 1, }, }, + { + name: "unknown rule 2", + rules: { + 100: 1, + 101: 0, + 110: 1, + 111: 0, + "011": 0, + "010": 0, + "001": 0, + "000": 1, + }, + }, ]; const initialStates = [ @@ -122,6 +135,11 @@ const preset2dRules = [ description: "Variation on Conway's Game of Life where a cell live if the six neighbor cells are alive", }, + { + id: "serviette", + name: "Serviette variation", + description: "bla", + }, ]; export { presetRuleset, initialStates, preset2dRules }; diff --git a/src/stores/index.js b/src/stores/index.js index cd81b56..eca1648 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -33,7 +33,7 @@ export const globalStore = defineStore("globalStore", { refreshRate: 300, initial1dState: "onecell", drawingDirection: "y", - lastBoard: {}, + lastBoard: null, draw1d: false, draw2d: false, draw2dLast: false, @@ -43,6 +43,7 @@ export const globalStore = defineStore("globalStore", { picture: null, mainMenu: false, activeSubMenu: "", + loop: true, }; }, actions: { -- 2.45.2 From a402739b976c5b15d2df06a872655d2800b14280 Mon Sep 17 00:00:00 2001 From: Gator Date: Mon, 19 Dec 2022 21:49:57 +0100 Subject: [PATCH 3/9] loop and next button for manual control --- src/components/MenuReset.vue | 30 ++++++++++++++++++++++++++++-- src/stores/index.js | 20 +++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/components/MenuReset.vue b/src/components/MenuReset.vue index ad90d12..e0cef4c 100644 --- a/src/components/MenuReset.vue +++ b/src/components/MenuReset.vue @@ -1,5 +1,23 @@ diff --git a/src/stores/index.js b/src/stores/index.js index eca1648..374085c 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -43,7 +43,8 @@ export const globalStore = defineStore("globalStore", { picture: null, mainMenu: false, activeSubMenu: "", - loop: true, + loop: false, + lastAction: "drawfromlast", }; }, actions: { @@ -62,6 +63,7 @@ export const globalStore = defineStore("globalStore", { }, toggleDraw2d() { this.setActiveSubMenu(""); + this.lastAction = "draw2d"; this.setMainMenu(false); this.toggleStop(); this.canDraw = true; @@ -69,6 +71,7 @@ export const globalStore = defineStore("globalStore", { }, toggleDraw2dLast() { this.setActiveSubMenu(""); + this.lastAction = "drawfromlast"; this.setMainMenu(false); this.toggleStop(); this.canDraw = true; @@ -90,6 +93,21 @@ export const globalStore = defineStore("globalStore", { this.draw2dpicture = false; this.canDraw = false; }, + 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; -- 2.45.2 From e43bd48794c47daf0562473a287bb1acb629a359 Mon Sep 17 00:00:00 2001 From: Gator Date: Mon, 19 Dec 2022 22:53:48 +0100 Subject: [PATCH 4/9] boardToPic --- src/components/CanvasBoard.vue | 22 +++++++--------------- src/modules/picture.js | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index ce484d8..a526153 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -28,13 +28,14 @@ servietteRules, evolve2d, } from "../modules/automata.js"; - import { getRandomInt, sleep } from "../modules/common.js"; - import { picToBoard, picToBlackAndWhite } from "../modules/picture.js"; + import { getRandomInt } from "../modules/common.js"; + import { boardToPic, picToBoard } from "../modules/picture.js"; export default { name: "CanvasBoard", data() { return { + board: null, canvas: null, workCanvas: null, workCtx: null, @@ -65,13 +66,13 @@ boardWidth: "boardWidth", boardHeight: "boardHeight", selected2dRules: "selected2dRules", + picture: "picture", }), ...mapWritableState(globalStore, { lastBoard: "lastBoard", canvasWidth: "canvasWidth", canvasHeight: "canvasHeight", getReset: "reset", - picture: "picture", }), // used to determine the dimensions of the board max() { @@ -120,21 +121,13 @@ drawCanvas(board, width, height) { const d = this.cellProperties.size; // bool to RGBA colors - const img = board.flat().reduce((acc, cell, index) => { - const color = cell === 1 ? 0 : 255; - const i = index * 4; - acc.data[i] = color; - acc.data[i + 1] = color; - acc.data[i + 2] = color; - acc.data[i + 3] = 255; - return acc; - }, new ImageData(width, height)); + const img = boardToPic(board, width, height); // rescale and draw this.ctx.save(); this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); this.workCtx.putImageData(img, 0, 0); this.ctx.imageSmoothingEnabled = false; - if (this.getDraw2dPicture != true) this.ctx.scale(d, d); + this.ctx.scale(d, d); this.ctx.drawImage( this.workCanvas, 0, @@ -181,7 +174,6 @@ const newBoard = evolve2d(board, this.selectedRules); this.draw2d(board); this.lastBoard = Object.freeze(newBoard); - /* await sleep(this.refreshRate) */ return this.draw2dNext(newBoard); }); }, @@ -189,7 +181,7 @@ async draw2dNew() { if (!this.canDraw) return; const initialState = this.randomInitialState(); - let board = evolve2d(initialState, conwayRules); + let board = evolve2d(initialState, this.selectRules); if (this.loop) return this.draw2dNext(board); else this.draw2d(board); this.toggleStop(); diff --git a/src/modules/picture.js b/src/modules/picture.js index 6907a72..b3c9db1 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -35,5 +35,19 @@ export function picToBoard(pixels, width, height) { } return acc; }, []); - return toMatrix(flat, Math.max(width, height)); + + return toMatrix(flat, width, height); +} + +// convert board to ImageData +export function boardToPic(board, width, height) { + return board.flat().reduce((acc, cell, index) => { + const color = cell === 1 ? 0 : 255; + const i = index * 4; + acc.data[i] = color; + acc.data[i + 1] = color; + acc.data[i + 2] = color; + acc.data[i + 3] = 255; + return acc; + }, new ImageData(width, height)); } -- 2.45.2 From 75caa2054f55b58143e13e0052019bdd1d30a675 Mon Sep 17 00:00:00 2001 From: Gator Date: Tue, 20 Dec 2022 10:47:32 +0100 Subject: [PATCH 5/9] typo --- src/components/CanvasBoard.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index a526153..59dde32 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -181,7 +181,7 @@ async draw2dNew() { if (!this.canDraw) return; const initialState = this.randomInitialState(); - let board = evolve2d(initialState, this.selectRules); + let board = evolve2d(initialState, this.selectedRules); if (this.loop) return this.draw2dNext(board); else this.draw2d(board); this.toggleStop(); -- 2.45.2 From 99a54eeb132de71c961d598069010e48109e8bce Mon Sep 17 00:00:00 2001 From: Gator Date: Tue, 20 Dec 2022 11:59:48 +0100 Subject: [PATCH 6/9] wip picture to board --- src/components/CanvasBoard.vue | 23 +++++++++++++++++++++-- src/modules/picture.js | 9 ++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index 59dde32..545ef5b 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -196,7 +196,7 @@ // draw 2d automaton from an uploaded picture. // use the picture representation as an initial state draw2dPicture() { - // get image data by drawing it on a work canvas + // draw image on canvas this.ctx.fillStyle = "black"; this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); this.ctx.drawImage( @@ -207,6 +207,7 @@ this.picture.height ); + // get image data from canvas const imgData = this.ctx.getImageData( 0, 0, @@ -214,10 +215,28 @@ this.canvasHeight ); + // draw the image back on the work canvas with the dimensions of the board + this.workCtx.drawImage( + this.picture, + 0, + 0, + this.boardWidth, + this.boardHeight + ); + + // get the resized image data from work canvas + const resized = this.workCtx.getImageData( + 0, + 0, + this.boardWidth, + this.boardHeight + ); + // convert the image into a 2D board of boolean based on pixel value this.lastBoard = Object.freeze( - picToBoard(imgData.data, this.canvasWidth, this.canvasHeight) + picToBoard(resized.data, this.boardWidth, this.boardHeight) ); + this.toggleStop(); }, // stop drawing routines and clear the canvas diff --git a/src/modules/picture.js b/src/modules/picture.js index b3c9db1..6e0e097 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -28,11 +28,10 @@ export function picToBlackAndWhite(pixels, width, height) { // convert an ImageData into a 2D array of boolean (0, 1) values export function picToBoard(pixels, width, height) { const flat = pixels.reduce((acc, pixel, index) => { - if (index % 4 == 0) { - const count = pixels[index] + pixels[index + 1] + pixels[index + 2]; - const value = count >= 255 ? 1 : 0; - acc[index] = value; - } + const i = index * 4; + const count = pixels[i] + pixels[i + 1] + pixels[i + 2]; + const value = count >= 255 ? 1 : 0; + acc[index] = value; return acc; }, []); -- 2.45.2 From 7500c6ac1fa4af66dd66449abac2b97e196cce89 Mon Sep 17 00:00:00 2001 From: Gator Date: Tue, 20 Dec 2022 12:30:58 +0100 Subject: [PATCH 7/9] rule 30 --- src/modules/preset.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/modules/preset.js b/src/modules/preset.js index 6835011..6b7a261 100644 --- a/src/modules/preset.js +++ b/src/modules/preset.js @@ -64,6 +64,19 @@ const presetRuleset = [ "000": 0, }, }, + { + name: "rule 30", + rules: { + 100: 1, + 101: 0, + 110: 0, + 111: 0, + "011": 1, + "010": 1, + "001": 1, + "000": 0, + }, + }, { name: "unknown rule", rules: { -- 2.45.2 From 44d749ac4e5c3f698dd80b25f2a7d959e043e0fb Mon Sep 17 00:00:00 2001 From: Gator Date: Tue, 20 Dec 2022 12:32:01 +0100 Subject: [PATCH 8/9] restore color selection --- src/components/CanvasBoard.vue | 2 +- src/modules/picture.js | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index 545ef5b..dc2d39a 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -121,7 +121,7 @@ drawCanvas(board, width, height) { const d = this.cellProperties.size; // bool to RGBA colors - const img = boardToPic(board, width, height); + const img = boardToPic(board, width, height, this.cellProperties); // rescale and draw this.ctx.save(); this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); diff --git a/src/modules/picture.js b/src/modules/picture.js index 6e0e097..4457d3a 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -1,3 +1,13 @@ +// https://stackoverflow.com/questions/21646738/convert-hex-to-rgba +// [ +function hexToRGB(hex) { + return [ + parseInt(hex.slice(1, 3), 16), + parseInt(hex.slice(3, 5), 16), + parseInt(hex.slice(5, 7), 16), + ]; +} + // https://stackoverflow.com/questions/4492385/convert-simple-array-into-two-dimensional-array-matrix // convert a 1D array into a 2D matrix export function toMatrix(array, width) { @@ -39,13 +49,17 @@ export function picToBoard(pixels, width, height) { } // convert board to ImageData -export function boardToPic(board, width, height) { +// TODO : different cell to color functions +// (binary, intermediate states, camaieux, etc) +export function boardToPic(board, width, height, cellProperties) { + const live = cellProperties.liveColor; + const dead = cellProperties.deadColor; return board.flat().reduce((acc, cell, index) => { - const color = cell === 1 ? 0 : 255; + const color = cell === 1 ? hexToRGB(live) : hexToRGB(dead); const i = index * 4; - acc.data[i] = color; - acc.data[i + 1] = color; - acc.data[i + 2] = color; + acc.data[i] = color[0]; + acc.data[i + 1] = color[1]; + acc.data[i + 2] = color[2]; acc.data[i + 3] = 255; return acc; }, new ImageData(width, height)); -- 2.45.2 From c8257524058761b12470ff61fe29078ee80e3fca Mon Sep 17 00:00:00 2001 From: Gator Date: Tue, 20 Dec 2022 16:27:04 +0100 Subject: [PATCH 9/9] refresh rate, some attempts at optimizing --- src/components/CanvasBoard.vue | 24 ++++++++++++------------ src/modules/automata.js | 12 ++++++------ src/modules/picture.js | 17 ++++++++++------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index dc2d39a..3eef80b 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -89,8 +89,8 @@ getDraw2d(value) { if (value == true) this.draw2dNew(); }, - getDraw2dLast(value) { - if (value == true) this.draw2dLast(); + async getDraw2dLast(value) { + if (value == true) await this.draw2dLast(); }, getDraw2dPicture(value) { if (value == true) this.draw2dPicture(); @@ -118,10 +118,10 @@ "setBoardHeight", ]), // draws the board on the canvas - drawCanvas(board, width, height) { + async drawCanvas(board, width, height) { const d = this.cellProperties.size; // bool to RGBA colors - const img = boardToPic(board, width, height, this.cellProperties); + const img = await boardToPic(board, width, height, this.cellProperties); // rescale and draw this.ctx.save(); this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); @@ -168,14 +168,14 @@ this.lastBoard = Object.freeze(newBoard); }, // draw 2d automaton in a loop, starting from passed state - async draw2dNext(board) { - requestAnimationFrame(() => { - if (!this.canDraw) return; - const newBoard = evolve2d(board, this.selectedRules); - this.draw2d(board); - this.lastBoard = Object.freeze(newBoard); - return this.draw2dNext(newBoard); - }); + async draw2dNext(board, time) { + setTimeout(() => { + requestAnimationFrame(() => { + if (!this.canDraw) return; + this.draw2d(board); + return this.draw2dNext(this.lastBoard); + }); + }, this.refreshRate); }, // draw 2d automaton from a new state async draw2dNew() { diff --git a/src/modules/automata.js b/src/modules/automata.js index ec3aa00..b4f1ce3 100644 --- a/src/modules/automata.js +++ b/src/modules/automata.js @@ -1,14 +1,14 @@ // 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; +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 function evolve1d(state, rules) { function getCell(index) { - const safeIndex = guard(index, state); + const safeIndex = guard(index, state.length); return state[safeIndex]; } const newState = state.map((_, x) => { @@ -55,8 +55,8 @@ function getCellNeighbors(board, cellCoordinates) { // handles board edges where the cell is missing neighbors function getCell(xx, yy) { - const safeX = guard(xx, board); - const safeY = guard(yy, rowLength); + const safeX = guard(xx, board.length); + const safeY = guard(yy, rowLength.length); return board[safeX][safeY]; } diff --git a/src/modules/picture.js b/src/modules/picture.js index 4457d3a..6be2aec 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -54,13 +54,16 @@ export function picToBoard(pixels, width, height) { export function boardToPic(board, width, height, cellProperties) { const live = cellProperties.liveColor; const dead = cellProperties.deadColor; - return board.flat().reduce((acc, cell, index) => { - const color = cell === 1 ? hexToRGB(live) : hexToRGB(dead); + const img = new ImageData(width, height); + const colors = [hexToRGB(live), hexToRGB(dead)]; + board.flat().reduce((acc, cell, index) => { + const color = colors[cell]; const i = index * 4; - acc.data[i] = color[0]; - acc.data[i + 1] = color[1]; - acc.data[i + 2] = color[2]; - acc.data[i + 3] = 255; + acc[i] = color[0]; + acc[i + 1] = color[1]; + acc[i + 2] = color[2]; + acc[i + 3] = 255; return acc; - }, new ImageData(width, height)); + }, img.data); + return img; } -- 2.45.2