diff --git a/index.html b/index.html
deleted file mode 100644
index 16ab6c2..0000000
--- a/index.html
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
- Cellular Automaton Explorer
-
-
-
-
- Cellular Automaton Explorer
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/main.js b/main.js
deleted file mode 100644
index 2de9879..0000000
--- a/main.js
+++ /dev/null
@@ -1,135 +0,0 @@
-let drawing = 1;
-
-const form = Array.from(document.forms.rules.elements);
-const canvas = document.querySelector('#canvas');
-const ctx = canvas.getContext('2d');
-const main = document.querySelector('#main');
-const dead = document.querySelector('#dead');
-const live = document.querySelector('#live');
-const startBtn = document.querySelector('#start');
-const resetBtn = document.querySelector('#reset');
-const stopBtn = document.querySelector('#stop');
-const cellSize = document.querySelector('#cellSize');
-const loop = document.querySelector('#loop');
-const menuRow = document.querySelectorAll('.menu-row');
-
-canvas.width = main.offsetWidth * 0.9;
-canvas.height = main.offsetHeight * 0.9;
-
-// TODO: Hide iterator inside
-function evolve(state, acc, rules) {
- const [x, y, z, ...xs] = state;
- if (!xs.length) {
- return acc[acc.length - 1] + acc + acc[0];
- }
-
- const rule = x + y + z;
- const newAcc = acc.concat(rules[rule]);
- return evolve(y + z + xs.join(''), newAcc, rules);
-}
-
-function getRules() {
- const rules = {};
-
- form.reduce((_, i) => {
- if (i !== undefined
- && i.type === 'checkbox') {
- if (i.checked) rules[i.name] = '1';
- else rules[i.name] = '0';
- }
- return rules;
- }, rules);
-
- return rules;
-}
-
-function getDrawingValues(state, acc) {
- const cellDim = cellSize.value;
-
- return Object.keys(state).map(
- (key) => {
- const fillStyle = state[key] === '1' ? live.value : dead.value;
- return {
- move: [key * cellDim, acc * cellDim],
- fill: [key * cellDim, acc * cellDim, cellDim, cellDim],
- fillStyle,
- };
- },
- );
-}
-
-function getRandomInt(min, max) {
- const minVal = Math.ceil(min);
- const maxVal = Math.floor(max);
- // The maximum is exclusive and the minimum is inclusive
- return Math.floor(Math.random() * (maxVal - minVal) + minVal);
-}
-
-async function sleep(ms) {
- await new Promise((resolve) => setTimeout(resolve, ms));
-}
-
-async function draw(state, acc) {
- if (drawing === 0) {
- return;
- }
-
- const position = acc * cellSize.value;
- if (position >= canvas.height && !loop.checked) return;
-
- const rules = getRules();
- const newState = evolve(state, '', rules);
- const line = getDrawingValues(newState, acc);
-
- line.map((cell) => {
- ctx.moveTo(...cell.move);
- ctx.fillRect(...cell.fill);
- ctx.fillStyle = cell.fillStyle;
- return cell;
- });
-
- await sleep(40);
-
- const newAcc = () => {
- if (position >= canvas.height && loop.checked) return 0;
- return acc;
- };
-
- await draw(newState, newAcc() + 1);
-}
-
-function reset() {
- drawing = 0;
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-}
-
-startBtn.addEventListener('click', async () => {
- reset();
-
- await sleep(60);
-
- drawing = 1;
-
- const initialState = [...Array(canvas.width)].map(
- () => getRandomInt(0, 2).toString(),
- ).join('');
-
- draw(initialState, 1);
-});
-
-resetBtn.addEventListener('click', async () => {
- reset();
-});
-
-stopBtn.addEventListener('click', async () => {
- drawing = 0;
-});
-
-menuRow.forEach((elem) => {
- elem.querySelector('h2').addEventListener('click', async (e) => {
- // ugly
- const menuDisplay = e.currentTarget.parentNode.querySelector('.menu-row-content').style;
- if (menuDisplay.display !== 'none') menuDisplay.setProperty('display', 'none');
- else menuDisplay.setProperty('display', 'block');
- });
-});
diff --git a/src/js/automata.js b/src/js/automata.js
index 588e83e..b9548c9 100644
--- a/src/js/automata.js
+++ b/src/js/automata.js
@@ -1,13 +1,59 @@
-// TODO: Hide accumulator inside
-function evolve(state, acc, rules) {
- const [x, y, z, ...xs] = state;
- if (!xs.length) {
- return acc[acc.length - 1] + acc + acc[0];
+// get the next evolution of a 1D CA
+function evolve(state, rules) {
+ function evolveAcc(s, acc) {
+ const [x, y, z, ...xs] = s;
+ if (!xs.length) {
+ return acc[acc.length - 1] + acc + acc[0];
+ }
+
+ const rule = x + y + z;
+ const newAcc = acc.concat(rules[rule]);
+ return evolveAcc(y + z + xs.join(''), newAcc);
+ }
+ return evolveAcc(state, '');
+}
+
+// create a 2D board from a 1D initial state
+function createBoard(state, rules, height) {
+ function createBoardAcc(s, h, acc) {
+ if (h === 0) return acc;
+ const newState = evolve(state, rules);
+ const newAcc = acc.concat(state);
+ return createBoardAcc(newState, h - 1, newAcc);
+ }
+ return createBoardAcc(state, height, []).map(
+ (row) => row.split(''))
+}
+
+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) {
+ // 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;
+ }
+
+ const safeX = guard(xx, board);
+ const safeY = guard(yy, rowLength);
+ return board[safeX][safeY];
}
- const rule = x + y + z;
- const newAcc = acc.concat(rules[rule]);
- return evolve(y + z + xs.join(''), newAcc, rules);
+ // 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),
+ ];
+}
+
+function getNeighborsSum(cells) {
+ return cells.reduce((cell, acc) => cell + acc, 0);
}
function conwayRules(cell, neighbors) {
@@ -21,11 +67,23 @@ function conwayRules(cell, neighbors) {
return cell;
}
+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 = state[key] === '1' ? cell.liveColor : cell.deadColor;
+ 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],
@@ -35,18 +93,18 @@ function getDrawingValues(state, acc, cell) {
);
}
-function initialState1D(width, initFn, args) {
+function initialState1d(width, initFn, args) {
return [...Array(width)].map(
() => initFn(...args).toString(),
).join('');
}
-function initialState2D(width, height, initFn, args) {
+function initialState2d(width, height, initFn, args) {
return [...Array(height)].map(
() => [...Array(width)].map(() => initFn(...args)),
);
}
export {
- evolve, getDrawingValues, initialState1D, initialState2D,
+ getDrawingValues, initialState1d, initialState2d, evolve, evolve2d, conwayRules, createBoard,
};
diff --git a/src/js/main.js b/src/js/main.js
index aea920d..19d8a01 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -1,5 +1,7 @@
-import { getRandomInt, sleep } from './common.js';
-import { evolve, getDrawingValues, initialState1D } from './automata.js';
+import { getRandomInt, sleep, hexToRgb } from './common.js';
+import {
+ evolve, evolve2d, getDrawingValues, initialState1d, conwayRules, createBoard,
+} from './automata.js';
let drawing = 1;
@@ -10,6 +12,7 @@ const main = document.querySelector('#main');
const dead = document.querySelector('#dead');
const live = document.querySelector('#live');
const startBtn = document.querySelector('#start');
+const startBtn2d = document.querySelector('#start2d');
const resetBtn = document.querySelector('#reset');
const stopBtn = document.querySelector('#stop');
const cellSize = document.querySelector('#cellSize');
@@ -39,41 +42,70 @@ function reset() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
+// TODO: Hide accumulator inside maybe?
async function draw(state, acc) {
if (drawing === 0) {
return;
}
- const cell = {
- dimension: cellSize.value,
+ const cellProperties = {
+ dimensions: cellSize.value,
liveColor: live.value,
deadColor: dead.value,
};
- const position = acc * cell.dimension;
- if (position >= canvas.height && !loop.checked) return;
+ // const position = acc * cell.dimension;
+ // if (position >= canvas.height && !loop.checked) return;
const rules = getRules();
- const newState = evolve(state, '', rules);
- const line = getDrawingValues(newState, acc, cell);
+ const board = createBoard(state, rules, canvas.height);
+ // const newState = evolve(state, rules);
+ // const line = getDrawingValues(newState, acc, cell);
- line.map((c) => {
- ctx.moveTo(...c.move);
- ctx.fillRect(...c.fill);
- ctx.fillStyle = c.fillStyle;
- return c;
+ board.reduce((row, prevRow, x) => {
+ const d = cellProperties.dimensions;
+ row.reduce((cell) => {
+ ctx.moveTo(x * d, y * d);
+ ctx.fillRect(x * d, x * d, d, d);
+ ctx.fillStyle = (() => {
+ if (cell === '1') return cellProperties.liveColor;
+ return cellProperties.deadColor;
+ })();
+ })
});
- await sleep(40);
+ // await sleep(40);
- const newAcc = () => {
- if (position >= canvas.height && loop.checked) return 0;
- return acc;
- };
+ // const newAcc = (() => {
+ // if (position >= canvas.height && loop.checked) return 0;
+ // return acc;
+ // })();
- await draw(newState, newAcc() + 1);
+ // await draw(newState, newAcc + 1);
}
+async function draw2d(board, acc) {
+ if (drawing === 0) {
+
+ }
+}
+
+// Listeners
+
+startBtn2d.addEventListener('click', async () => {
+ reset();
+
+ await sleep(60);
+
+ drawing = 1;
+
+ const initialState = initialState2d(canvas.width, canvas.height, getRandomInt, [0, 2]);
+
+ const nextState = evolve2d(initialState, conwayRules);
+
+ draw2d(initialState, 0);
+});
+
startBtn.addEventListener('click', async () => {
reset();
@@ -81,9 +113,9 @@ startBtn.addEventListener('click', async () => {
drawing = 1;
- const initialState = initialState1D(canvas.width, getRandomInt, [0, 2]);
+ const initialState = initialState1d(canvas.width, getRandomInt, [0, 2]);
- draw(initialState, 1);
+ draw(initialState, 0);
});
resetBtn.addEventListener('click', async () => {
diff --git a/style.css b/style.css
deleted file mode 100644
index 299fa3c..0000000
--- a/style.css
+++ /dev/null
@@ -1,62 +0,0 @@
-* {
- margin: 0;
- padding: 0;
-}
-
-body {
- background: black;
- color: white;
- display: flex;
- font-family: Courier New;
-}
-
-canvas {
- background: #110812;
- margin: 20px 0 0 0;
-}
-
-h1, h2 {
- font-weight: bold;
-}
-
-h1 {
- font-size: large;
-}
-
-.menu-row h2 {
- font-size: medium;
- margin-left: 10px;
- padding: 10px;
- cursor: pointer;
- border: 2px solid darkgrey;
-}
-
-sidebar {
- flex: auto;
- padding: 10px;
-}
-
-input[type="button"] {
- min-width: 60px;
- padding: 5px;
- font-weight: bold;
-}
-
-.form-field {
- display: flex;
- margin: 10px;
-}
-
-.menu-row {
- flex: 1;
- margin: 5px 0;
-}
-
-label, .form-field label {
- margin-right: 10px;
- font-weight: bold;
-}
-
-#main {
- flex: 4;
-}