board module
This commit is contained in:
parent
f351f37c46
commit
faee8f61d5
@ -2,9 +2,6 @@
|
|||||||
import { onMounted, watch } from "vue";
|
import { onMounted, watch } from "vue";
|
||||||
import { globalStore } from "../stores/index.js";
|
import { globalStore } from "../stores/index.js";
|
||||||
import {
|
import {
|
||||||
create1dState,
|
|
||||||
create1dStateOneCell,
|
|
||||||
create2dState,
|
|
||||||
createBoard,
|
createBoard,
|
||||||
conwayRules,
|
conwayRules,
|
||||||
overpopulationRules,
|
overpopulationRules,
|
||||||
@ -13,8 +10,11 @@
|
|||||||
highLifeRules,
|
highLifeRules,
|
||||||
servietteRules,
|
servietteRules,
|
||||||
evolve2d,
|
evolve2d,
|
||||||
} from "../modules/automata.js";
|
} from "../modules/core.js";
|
||||||
import { getRandomInt } from "../modules/common.js";
|
import {
|
||||||
|
create1dInitialState,
|
||||||
|
create2dRandomGrid,
|
||||||
|
} from "../modules/board.js";
|
||||||
import { boardToPic, picToBoard } from "../modules/picture.js";
|
import { boardToPic, picToBoard } from "../modules/picture.js";
|
||||||
|
|
||||||
const store = globalStore();
|
const store = globalStore();
|
||||||
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
// used to determine the dimensions of the board
|
// used to determine the dimensions of the board
|
||||||
const max = () => {
|
const max = () => {
|
||||||
return Math.max(store.boardWidth, store.boardHeight);
|
return Math.max(store.board.width, store.board.height);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedRules = () => {
|
const selectedRules = () => {
|
||||||
@ -92,10 +92,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// draws the board on the canvas
|
// draws the board on the canvas
|
||||||
const drawCanvas = async (board, width, height) => {
|
const drawCanvas = async (board) => {
|
||||||
const d = store.cellProperties.size;
|
const d = board.cellProperties.size;
|
||||||
// bool to RGBA colors
|
// bool to RGBA colors
|
||||||
const img = await boardToPic(board, width, height, store.cellProperties);
|
const img = await boardToPic(board);
|
||||||
// rescale and draw
|
// rescale and draw
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
|
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
|
||||||
@ -106,39 +106,22 @@
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
// create a first state, either a single living cell
|
|
||||||
// at the center or random ones
|
|
||||||
const compute1dInitialState = () => {
|
|
||||||
if (store.initial1dState === "onecell")
|
|
||||||
return create1dStateOneCell(store.boardWidth);
|
|
||||||
return create1dState(store.boardWidth, getRandomInt, [0, 2]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// initialize board with random cells
|
|
||||||
const randomInitialState = () => {
|
|
||||||
return create2dState(
|
|
||||||
store.boardWidth,
|
|
||||||
store.boardHeight,
|
|
||||||
getRandomInt,
|
|
||||||
[0, 2]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// draw elementary automaton on the canvas based on selected ruleset
|
// draw elementary automaton on the canvas based on selected ruleset
|
||||||
const draw1d = () => {
|
const draw1d = () => {
|
||||||
const initialState = compute1dInitialState();
|
const initialState = create1dInitialState(
|
||||||
|
store.board,
|
||||||
|
store.initial1dState
|
||||||
|
);
|
||||||
const board = createBoard(initialState, store.ruleset1d.rules, max());
|
const board = createBoard(initialState, store.ruleset1d.rules, max());
|
||||||
store.lastBoard = Object.freeze(board);
|
store.board.grid = Object.freeze(board);
|
||||||
// TODO: the board clearly could be an object
|
drawCanvas(store.board);
|
||||||
drawCanvas(store.lastBoard, store.boardWidth, store.boardHeight);
|
|
||||||
store.toggleStop();
|
store.toggleStop();
|
||||||
};
|
};
|
||||||
|
|
||||||
// draw 2D automaton on the canvas in a loop
|
// draw 2D automaton on the canvas in a loop
|
||||||
const draw2d = (board) => {
|
const draw2d = (board) => {
|
||||||
const newBoard = evolve2d(board, selectedRules());
|
store.board.grid = Object.freeze(evolve2d(board.grid, selectedRules()));
|
||||||
drawCanvas(newBoard, store.boardWidth, store.boardHeight);
|
drawCanvas(store.board);
|
||||||
store.lastBoard = Object.freeze(newBoard);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// draw 2d automaton in a loop, starting from passed state
|
// draw 2d automaton in a loop, starting from passed state
|
||||||
@ -147,7 +130,7 @@
|
|||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!store.canDraw) return;
|
if (!store.canDraw) return;
|
||||||
draw2d(board);
|
draw2d(board);
|
||||||
return draw2dNext(store.lastBoard);
|
return draw2dNext(store.board);
|
||||||
});
|
});
|
||||||
}, store.refreshRate);
|
}, store.refreshRate);
|
||||||
};
|
};
|
||||||
@ -155,18 +138,21 @@
|
|||||||
// draw 2d automaton from a new state
|
// draw 2d automaton from a new state
|
||||||
const draw2dNew = async () => {
|
const draw2dNew = async () => {
|
||||||
if (!store.canDraw) return;
|
if (!store.canDraw) return;
|
||||||
const initialState = randomInitialState();
|
const initialGrid = create2dRandomGrid(
|
||||||
const board = evolve2d(initialState, selectedRules());
|
store.board.width,
|
||||||
if (store.loop) return draw2dNext(board);
|
store.board.height
|
||||||
else draw2d(board);
|
);
|
||||||
|
store.board.grid = Object.freeze(evolve2d(initialGrid, selectedRules()));
|
||||||
|
if (store.loop) return draw2dNext(store.board);
|
||||||
|
else draw2d(store.board);
|
||||||
store.toggleStop();
|
store.toggleStop();
|
||||||
};
|
};
|
||||||
|
|
||||||
// draw 2d automaton from the last known generated board
|
// draw 2d automaton from the last known generated board
|
||||||
const draw2dLast = async () => {
|
const draw2dLast = async () => {
|
||||||
if (!store.canDraw) return;
|
if (!store.canDraw) return;
|
||||||
if (store.loop) return draw2dNext(store.lastBoard);
|
if (store.loop) return draw2dNext(store.board);
|
||||||
else draw2d(store.lastBoard);
|
else draw2d(store.board);
|
||||||
store.toggleStop();
|
store.toggleStop();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,19 +171,25 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
// draw the image back on the work canvas with the dimensions of the board
|
// draw the image back on the work canvas with the dimensions of the board
|
||||||
workCtx.drawImage(store.picture, 0, 0, store.boardWidth, store.boardHeight);
|
workCtx.drawImage(
|
||||||
|
store.picture,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
store.board.width,
|
||||||
|
store.board.height
|
||||||
|
);
|
||||||
|
|
||||||
// get the resized image data from work canvas
|
// get the resized image data from work canvas
|
||||||
const resized = workCtx.getImageData(
|
const resized = workCtx.getImageData(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
store.boardWidth,
|
store.board.width,
|
||||||
store.boardHeight
|
store.board.height
|
||||||
);
|
);
|
||||||
|
|
||||||
// convert the image into a 2D board of boolean based on pixel value
|
// convert the image into a 2D board of boolean based on pixel value
|
||||||
store.lastBoard = Object.freeze(
|
store.board.grid = Object.freeze(
|
||||||
picToBoard(resized.data, store.boardWidth, store.boardHeight)
|
picToBoard(resized.data, store.board.width, store.board.height)
|
||||||
);
|
);
|
||||||
|
|
||||||
store.toggleStop();
|
store.toggleStop();
|
||||||
@ -206,7 +198,7 @@
|
|||||||
// stop drawing routines and clear the canvas
|
// stop drawing routines and clear the canvas
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
store.toggleStop();
|
store.toggleStop();
|
||||||
store.lastBoard = null;
|
store.board.grid = null;
|
||||||
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
|
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
|
||||||
store.reset = false;
|
store.reset = false;
|
||||||
};
|
};
|
||||||
|
@ -5,17 +5,14 @@
|
|||||||
const store = globalStore();
|
const store = globalStore();
|
||||||
|
|
||||||
const updateCellProperties = (event) => {
|
const updateCellProperties = (event) => {
|
||||||
const elem = event.target;
|
const { name, value } = event.target;
|
||||||
store.cellProperties[elem.name] = elem.value;
|
store.setCellProperties(name, value);
|
||||||
store.setBoardWidth();
|
store.setBoardWidth();
|
||||||
store.setBoardHeight();
|
store.setBoardHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
const switchColor = () => {
|
const switchColor = () => {
|
||||||
[store.cellProperties["liveColor"], store.cellProperties["deadColor"]] = [
|
store.switchColor();
|
||||||
store.cellProperties["deadColor"],
|
|
||||||
store.cellProperties["liveColor"],
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -27,7 +24,7 @@
|
|||||||
<input
|
<input
|
||||||
name="liveColor"
|
name="liveColor"
|
||||||
type="color"
|
type="color"
|
||||||
:value="store.cellProperties.liveColor"
|
:value="store.board.cellProperties.liveColor"
|
||||||
@input="updateCellProperties"
|
@input="updateCellProperties"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -36,7 +33,7 @@
|
|||||||
<input
|
<input
|
||||||
name="deadColor"
|
name="deadColor"
|
||||||
type="color"
|
type="color"
|
||||||
:value="store.cellProperties.deadColor"
|
:value="store.board.cellProperties.deadColor"
|
||||||
@input="updateCellProperties"
|
@input="updateCellProperties"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -49,7 +46,7 @@
|
|||||||
name="size"
|
name="size"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
:value="store.cellProperties.size"
|
:value="store.board.cellProperties.size"
|
||||||
@click="updateCellProperties"
|
@click="updateCellProperties"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
28
src/modules/board.js
Normal file
28
src/modules/board.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { create2dState, create1dStateOneCell, create1dState } from "./core.js";
|
||||||
|
|
||||||
|
import { getRandomInt } from "./common.js";
|
||||||
|
|
||||||
|
function Board(width, height, grid = []) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
(this.cellProperties = {
|
||||||
|
size: 3,
|
||||||
|
liveColor: "#000000",
|
||||||
|
deadColor: "#F5F5F5",
|
||||||
|
}),
|
||||||
|
(this.grid = grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// initialize 2d board with random cells
|
||||||
|
const create2dRandomGrid = (width, height) => {
|
||||||
|
return create2dState(width, height, getRandomInt, [0, 2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Board, create1dInitialState, create2dRandomGrid };
|
@ -44,19 +44,21 @@ export function picToBoard(pixels, width, height) {
|
|||||||
acc[index] = value;
|
acc[index] = value;
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
// TODO: The representation has to be 2D, not the data structure
|
||||||
|
// (change to flat)
|
||||||
return toMatrix(flat, width, height);
|
return toMatrix(flat, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert board to ImageData
|
// convert board to ImageData
|
||||||
// TODO : different cell to color functions
|
// TODO : different cell to color functions
|
||||||
// (binary, intermediate states, camaieux, etc)
|
// (binary, intermediate states, camaieux, etc)
|
||||||
export function boardToPic(board, width, height, cellProperties) {
|
export function boardToPic(board) {
|
||||||
const live = cellProperties.liveColor;
|
const live = board.cellProperties.liveColor;
|
||||||
const dead = cellProperties.deadColor;
|
const dead = board.cellProperties.deadColor;
|
||||||
const img = new ImageData(width, height);
|
const img = new ImageData(board.width, board.height);
|
||||||
const colors = [hexToRGB(live), hexToRGB(dead)];
|
const colors = [hexToRGB(live), hexToRGB(dead)];
|
||||||
board.flat().reduce((acc, cell, index) => {
|
board.grid.flat().reduce((acc, cell, index) => {
|
||||||
|
// TODO : bitshift operation instead of ternary
|
||||||
const color = cell === 1 ? colors[0] : colors[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];
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
import { Board } from "../modules/board.js";
|
||||||
|
|
||||||
export const globalStore = defineStore("globalStore", {
|
export const globalStore = defineStore("globalStore", {
|
||||||
state: () => {
|
state: () => {
|
||||||
@ -21,19 +22,12 @@ export const globalStore = defineStore("globalStore", {
|
|||||||
name: "Conway's Game of Life",
|
name: "Conway's Game of Life",
|
||||||
description: "The most popular 2d automata",
|
description: "The most popular 2d automata",
|
||||||
},
|
},
|
||||||
cellProperties: {
|
|
||||||
size: 3,
|
|
||||||
liveColor: "#000000",
|
|
||||||
deadColor: "#F5F5F5",
|
|
||||||
},
|
|
||||||
canvasWidth: 0,
|
canvasWidth: 0,
|
||||||
canvasHeight: 0,
|
canvasHeight: 0,
|
||||||
boardWidth: 0,
|
|
||||||
boardHeight: 0,
|
|
||||||
refreshRate: 300,
|
refreshRate: 300,
|
||||||
initial1dState: "onecell",
|
initial1dState: "onecell",
|
||||||
drawingDirection: "y",
|
drawingDirection: "y",
|
||||||
lastBoard: null,
|
board: new Board(),
|
||||||
draw1d: false,
|
draw1d: false,
|
||||||
draw2d: false,
|
draw2d: false,
|
||||||
draw2dLast: false,
|
draw2dLast: false,
|
||||||
@ -49,13 +43,27 @@ export const globalStore = defineStore("globalStore", {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setBoardWidth() {
|
setBoardWidth() {
|
||||||
this.boardWidth = Math.floor(this.canvasWidth / this.cellProperties.size);
|
this.board.width = Math.floor(
|
||||||
|
this.canvasWidth / this.board.cellProperties.size
|
||||||
|
);
|
||||||
},
|
},
|
||||||
setBoardHeight() {
|
setBoardHeight() {
|
||||||
this.boardHeight = Math.floor(
|
this.board.height = Math.floor(
|
||||||
this.canvasHeight / this.cellProperties.size
|
this.canvasHeight / this.board.cellProperties.size
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
setCellProperties(name, value) {
|
||||||
|
this.board.cellProperties[name] = value;
|
||||||
|
},
|
||||||
|
switchColor() {
|
||||||
|
[
|
||||||
|
this.board.cellProperties["liveColor"],
|
||||||
|
this.board.cellProperties["deadColor"],
|
||||||
|
] = [
|
||||||
|
this.board.cellProperties["deadColor"],
|
||||||
|
this.board.cellProperties["liveColor"],
|
||||||
|
];
|
||||||
|
},
|
||||||
toggleDraw1d() {
|
toggleDraw1d() {
|
||||||
this.setActiveSubMenu("");
|
this.setActiveSubMenu("");
|
||||||
this.setMainMenu(false);
|
this.setMainMenu(false);
|
||||||
|
28
tests/board.test.js
Normal file
28
tests/board.test.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
import {
|
||||||
|
Board,
|
||||||
|
create1dInitialState,
|
||||||
|
create2dRandomGrid,
|
||||||
|
} from "src/modules/board.js";
|
||||||
|
|
||||||
|
describe("Board", () => {
|
||||||
|
const board = new Board(503, 301);
|
||||||
|
test("create1dInitialState onecell", () => {
|
||||||
|
const stateOne = create1dInitialState(board);
|
||||||
|
expect(stateOne.length).toBe(board.width);
|
||||||
|
expect(stateOne).toContain(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("create1dInitialState random", () => {
|
||||||
|
const stateRandom = create1dInitialState(board, "random");
|
||||||
|
expect(stateRandom.length).toBe(board.width);
|
||||||
|
expect(stateRandom).toContain(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("create2dRandomGrid", () => {
|
||||||
|
const board = new Board(503, 301);
|
||||||
|
const got = create2dRandomGrid(board.width, board.height);
|
||||||
|
expect(got.length).toBe(board.height);
|
||||||
|
expect(got[0].length).toBe(board.width);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user