Merge pull request 'fix evolve 2D' (#11) from dev into master
Reviewed-on: #11
This commit is contained in:
commit
0f31f2556c
@ -111,19 +111,19 @@
|
|||||||
|
|
||||||
// draw 2D automaton on the canvas in a loop
|
// draw 2D automaton on the canvas in a loop
|
||||||
const draw2d = (board) => {
|
const draw2d = (board) => {
|
||||||
store.board.grid = Object.freeze(evolve2d(board.grid, selectedRules()));
|
|
||||||
drawCanvas(store.board);
|
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
|
// draw 2d automaton in a loop, starting from passed state
|
||||||
const draw2dNext = async (board) => {
|
const draw2dNext = async (board) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
requestAnimationFrame(() => {
|
if (!store.canDraw) return;
|
||||||
if (!store.canDraw) return;
|
draw2d(board);
|
||||||
draw2d(board);
|
return draw2dNext(store.board);
|
||||||
return draw2dNext(store.board);
|
}, store.renderer.refreshRate);
|
||||||
});
|
|
||||||
}, store.refreshRate);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// draw 2d automaton from a new state
|
// draw 2d automaton from a new state
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
// core functions to generate initial states and evolve them
|
// core functions to generate initial states and evolve them
|
||||||
|
|
||||||
// handles negative index and index bigger than its array length
|
// get the next evolution of a 1D CA initial state
|
||||||
function guard(index, arrayLength) {
|
// buggy BUT produces interesting results
|
||||||
if (index > arrayLength - 1) return 0;
|
// function evolve1d(state, rules) {
|
||||||
if (index < 0) return arrayLength - 1;
|
// const sl = state.length - 1;
|
||||||
return index;
|
// 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
|
// get the next evolution of a 1D CA initial state
|
||||||
function evolve1d(state, rules) {
|
function evolve1d(state, rules) {
|
||||||
function getCell(index) {
|
const sl = state.length - 1;
|
||||||
const safeIndex = guard(index, state.length);
|
const edge1 = [state[sl - 2], state[sl - 1], state[sl]].join("");
|
||||||
return state[safeIndex];
|
const edge2 = [state[0], state[1], state[2]].join("");
|
||||||
}
|
// normal case (3 neighbor cells)
|
||||||
const newState = state.map((_, x) => {
|
const center = state
|
||||||
const cells = [getCell(x - 1), getCell(x), getCell(x + 1)];
|
.map((_, x) => rules[[state[x - 1], state[x], state[x + 1]].join("")])
|
||||||
return rules[cells.join("")];
|
.slice(1, sl);
|
||||||
});
|
return [rules[edge1], ...center, rules[edge2]];
|
||||||
return newState.map(Number);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a 2D board from a 1D CA initial state
|
// create a 2D board from a 1D CA initial state
|
||||||
@ -49,31 +52,6 @@ function createBoard(state, rules, max) {
|
|||||||
return board;
|
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
|
// Sums the value of a cell's neighbors
|
||||||
function getNeighborsSum(cells) {
|
function getNeighborsSum(cells) {
|
||||||
return cells.reduce((cell, acc) => cell + acc, 0);
|
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
|
// get the next evolution of a 2D CA initial state
|
||||||
// Rules : Moore neighborhood
|
// Rules : Moore neighborhood
|
||||||
function evolve2d(board, rulesFn) {
|
function evolve2d(board, rulesFn) {
|
||||||
return board.map((row, x) =>
|
const bh = board.length - 1;
|
||||||
row.map((cell, y) => {
|
const bw = board[0].length - 1;
|
||||||
const neighbors = getCellNeighbors(board, [x, y]);
|
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);
|
const sum = getNeighborsSum(neighbors);
|
||||||
return rulesFn(cell, sum);
|
return rulesFn(cell, sum);
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDrawingValues(state, acc, cell) {
|
function getDrawingValues(state, acc, cell) {
|
||||||
|
@ -40,7 +40,7 @@ export function picToBoard(pixels, board) {
|
|||||||
const flat = pixels.reduce((acc, pixel, index) => {
|
const flat = pixels.reduce((acc, pixel, index) => {
|
||||||
const i = index * 4;
|
const i = index * 4;
|
||||||
const count = pixels[i] + pixels[i + 1] + pixels[i + 2];
|
const count = pixels[i] + pixels[i + 1] + pixels[i + 2];
|
||||||
const value = count >= 255 ? 1 : 0;
|
const value = (count >= 255) & 1;
|
||||||
acc[index] = value;
|
acc[index] = value;
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
@ -58,8 +58,7 @@ export function boardToPic(board) {
|
|||||||
const img = new ImageData(board.width, board.height);
|
const img = new ImageData(board.width, board.height);
|
||||||
const colors = [hexToRGB(live), hexToRGB(dead)];
|
const colors = [hexToRGB(live), hexToRGB(dead)];
|
||||||
board.grid.flat().reduce((acc, cell, index) => {
|
board.grid.flat().reduce((acc, cell, index) => {
|
||||||
// TODO : bitshift operation instead of ternary
|
const color = colors[(cell === 1) & 1];
|
||||||
const color = cell === 1 ? colors[0] : colors[1];
|
|
||||||
const i = index * 4;
|
const i = index * 4;
|
||||||
acc[i] = color[0];
|
acc[i] = color[0];
|
||||||
acc[i + 1] = color[1];
|
acc[i + 1] = color[1];
|
||||||
|
@ -103,6 +103,32 @@ const presetRuleset = [
|
|||||||
"000": 1,
|
"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 = [
|
const initialStates = [
|
||||||
|
18
tests/core.test.js
Normal file
18
tests/core.test.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user