wip moved canvas routine in store. it's awful

This commit is contained in:
Ali Gator 2022-12-01 12:03:34 +01:00
parent 999f6d1899
commit aace63b1a2
7 changed files with 376 additions and 300 deletions

View File

@ -3,20 +3,20 @@
<h1 id="main-title">Cellular Automata Explorer</h1>
<div id="container">
<MainMenu />
<Board />
<CanvasBoard />
</div>
</div>
</template>
<script>
import MainMenu from "./components/MainMenu.vue";
import Board from "./components/Board.vue";
import CanvasBoard from "./components/CanvasBoard.vue";
export default {
name: "App",
components: {
MainMenu,
Board,
CanvasBoard,
},
};
</script>

0
src/assets/main.css Normal file
View File

View File

@ -1,128 +0,0 @@
<template>
<main id="mainContent">
<canvas
id="canvas-board"
ref="canvas-board"
:width="canvasWidth"
:height="canvasHeight"
>
</canvas>
</main>
</template>
<script>
import {
create1dState,
create1dStateOneCell,
create2dState,
createBoard,
conwayRules,
evolve2d,
} from "../modules/automata.js";
import { getRandomInt, sleep } from "../modules/common.js";
import { mapGetters } from "vuex";
export default {
name: "CanvasBoard",
data() {
return {
canvas: null,
ctx: null,
};
},
computed: {
...mapGetters({
cellProperties: "getCellProperties",
rules: "get1dRules",
drawing: "isDrawing",
canvasWidth: "getCanvasWidth",
canvasHeight: "getCanvasHeight",
refreshRate: "getRefreshRate",
initial1dState: "getInitial1dState",
drawingDirection: "getDrawingDirection",
lastBoard: "getLastBoard",
}),
boardWidth: function () {
return Math.floor(this.canvasWidth / this.cellProperties.size);
},
boardHeight: function () {
return Math.floor(this.canvasHeight / this.cellProperties.size);
},
},
mounted() {
this.canvas = document.getElementById("canvas-board")
this.ctx = this.canvas.getContext("2d");
this.$store.commit("setCanvasWidth", this.canvas.parentElement.clientWidth);
this.$store.commit(
"setCanvasHeight",
this.canvas.parentElement.clientHeight
);
},
methods: {
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;
});
});
},
compute1dInitialState() {
if (this.initial1dState === "onecell")
return create1dStateOneCell(this.boardWidth);
return create1dState(this.boardWidth, getRandomInt, [0, 2]);
},
async draw1d() {
const initialState = this.compute1dInitialState();
const board = createBoard(initialState, this.rules, this.boardWidth);
this.$store.commit("setLastBoard", board);
this.drawCanvas(board);
},
async draw2d(board) {
if (this.drawing === 0) return;
const draw2dNext = async (b) => {
if (this.drawing === 0) return;
const newBoard = evolve2d(b, conwayRules);
this.drawCanvas(b, this.cellProperties);
await sleep(this.refreshRate);
draw2dNext(newBoard);
};
return draw2dNext(board);
},
async draw2dNew() {
const initialState = create2dState(
this.boardWidth,
this.boardHeight,
getRandomInt,
[0, 2]
);
const board = evolve2d(initialState, conwayRules);
this.$store.commit("setLastBoard", board);
this.draw2d(board);
},
async draw2dLast() {
console.log(this.lastBoard);
this.draw2d(this.lastBoard);
},
stop() {
this.$store.commit("setDrawingStatus", 0);
},
reset() {
this.stop();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
},
};
</script>
<style>
#mainContent {
min-width: 70%;
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<main id="mainContent">
<canvas
id="canvas-board"
ref="canvas-board"
:width="canvasWidth"
:height="canvasHeight"
>
</canvas>
</main>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "CanvasBoard",
computed: {
...mapGetters({
canvasWidth: "getCanvasWidth",
canvasHeight: "getCanvasHeight",
}),
},
mounted() {
const canvas = document.getElementById("canvas-board")
const ctx = canvas.getContext("2d");
this.$store.commit(
"setCanvas",
canvas
)
this.$store.commit(
"setContext",
ctx
)
this.$store.commit(
"setCanvasWidth",
canvas.parentElement.clientWidth
);
this.$store.commit(
"setCanvasHeight",
canvas.parentElement.clientHeight
);
this.$store.commit(
"setBoardWidth",
canvas.parentElement.clientWidth
);
this.$store.commit(
"setBoardHeight",
canvas.parentElement.clientHeight
);
},
};
</script>
<style>
#mainContent {
min-width: 70%;
}
</style>

View File

@ -2,17 +2,16 @@
<MenuRow row-title="Elementary Cellular Automata">
<form>
<div class="form-field">
<label
>Initial state presets
<label>
Initial state presets
<br />
<select
name="initial1dStates"
value="initial1dStates"
@input="updateInitial1dState"
name="initialStates"
:value="initialState"
@input="updateInitialState"
>
<option
v-for="state in initial1dStates"
:key="'preset1d' + state.id"
v-for="state in initialStates"
:value="state.id"
>
{{ state.name }}
@ -27,23 +26,25 @@
<label
>Rules presets
<br />
<select name="rules1d" value="rules" @input="update1dRules">
<option
v-for="(rule, name) in presetRules"
:key="'rule1d' + name"
:value="name"
<select
name="ruleset-elementary"
:value="rules.name"
@input="updateRules"
>
{{ name }}
<option
v-for="ruleset in presetRules"
:value="ruleset.name"
>
{{ ruleset.name }}
</option>
</select>
</label>
</div>
<div class="form-field">
<a style="cursor: pointer" @click="copy1dRules">copy rules</a>
<a style="cursor: pointer" @click="copyRules">copy rules</a>
</div>
<div
v-for="(rule, name) in rules"
:key="'rule1d' + name"
v-for="(rule, name) in rules.rules"
class="form-field"
>
<label
@ -53,7 +54,7 @@
type="checkbox"
:name="name"
:checked="rule"
@input="update1dSingleRule"
@input="updateSingleRule"
/>
</label>
</div>
@ -82,8 +83,10 @@ export default {
data() {
// TODO: Why not a getter in the store?
return {
presetRules: {
"rule 73": {
presetRules: [
{
name: "rule 73",
rules: {
100: 0,
101: 0,
110: 1,
@ -92,8 +95,11 @@ export default {
"010": 0,
"001": 0,
"000": 1,
}
},
"rule 86": {
{
name: "rule 86",
rules:{
100: 1,
101: 0,
110: 0,
@ -103,7 +109,10 @@ export default {
"001": 0,
"000": 1,
},
"rule 90": {
},
{
name: "rule 90",
rules: {
100: 1,
101: 0,
110: 1,
@ -113,7 +122,10 @@ export default {
"001": 1,
"000": 0,
},
"rule 45?": {
},
{
name: "rule 45?",
rules: {
100: 0,
101: 0,
110: 1,
@ -123,7 +135,10 @@ export default {
"001": 1,
"000": 1,
},
"rule 54?": {
},
{
name: "rule 54?",
rules: {
100: 1,
101: 0,
110: 1,
@ -133,7 +148,10 @@ export default {
"001": 1,
"000": 0,
},
"unknown rule": {
},
{
name: "unknown rule",
rules: {
100: 0,
101: 0,
110: 0,
@ -143,8 +161,9 @@ export default {
"001": 1,
"000": 1,
},
},
initial1dStates: [
}
],
initialStates: [
{
id: "onecell",
name: "One cell at center",
@ -160,7 +179,7 @@ export default {
},
computed: {
...mapGetters({
initial1dState: "getInitial1dState",
initialState: "getInitial1dState",
rules: "get1dRules",
}),
rules1dFileName() {
@ -174,7 +193,7 @@ export default {
},
},
methods: {
copy1dRules() {
copyRules() {
const rules = JSON.stringify(this.rules);
navigator.clipboard.writeText(rules);
},
@ -182,31 +201,34 @@ export default {
const elem = event.target;
return this.initialState === elem.value;
},
update1dSingleRule(event) {
updateSingleRule(event) {
const elem = event.target;
const value = elem.checked ? 1 : 0;
const data = { rule: elem.name, value: value };
this.$store.commit("update1dSingleRule", data);
},
update1dRules(event) {
updateRules(event) {
// TODO : change this, awfully confusing
const elem = event.target;
const name = elem.value;
const rules = this.presetRules[name];
Object.keys(rules).map((index) => {
const data = { rule: index, value: rules[index] };
const rules = this.presetRules.find(
(ruleset) => { return ruleset.name === name }
)
Object.keys(rules.rules).map((value, index) => {
const data = { name: name, rule: value, value: rules.rules[value] };
this.$store.commit("update1dSingleRule", data);
});
},
updateInitial1dState(event) {
updateInitialState(event) {
const elem = event.target;
this.$store.commit("setInitial1dState", elem.value);
},
draw1d() {
this.$root.$store.state.drawing = 1;
this.$root.$emit("draw1d");
this.$store.commit("setDrawingStatus", 1);
this.$store.dispatch("draw1d");
},
reset() {
this.$root.$emit("reset");
this.$store.dispatch("reset");
},
},
};

View File

@ -2,8 +2,12 @@ import { createApp } from 'vue'
import App from "./App.vue";
import { store } from "./store";
const gobalsProperties = {'canvas': null};
const app = createApp(App)
app.provide('gobalsProperties', gobalsProperties)
app.use(store)
app.mount('#app')

View File

@ -1,9 +1,32 @@
/* TODO: terminology is to be changed for :
canvas/board :
currently, the canvas object is named board,
while the structure used to store automata current state is named "board" as well. This is confusing
drawing board could be enough to lift any ambiguity
rules:
confusion bewteen ruleset and rules.
it's never clear if we refers to a rule or the whole (named) set
*/
import { createStore } from 'vuex'
import {
create1dState,
create1dStateOneCell,
create2dState,
createBoard,
conwayRules,
evolve2d,
} from "../modules/automata.js";
import { getRandomInt, sleep } from "../modules/common.js";
export const store = createStore({
strict: process.env.NODE_ENV !== 'production',
state: {
drawing: 0,
rules1d: {
name: "rule 73",
rules:
{
111: 0,
110: 1,
101: 0,
@ -12,14 +35,19 @@ export const store = createStore({
"010": 0,
"001": 0,
"000": 1,
}
},
cellProperties: {
size: 3,
liveColor: "#000000",
deadColor: "#F5F5F5",
},
canvas: null,
ctx: null,
canvasWidth: 0,
canvasHeight: 0,
boardWidth: 0,
boardHeight: 0,
refreshRate: 300,
initial1dState: "onecell",
activeMenu: "",
@ -28,7 +56,8 @@ export const store = createStore({
},
mutations: {
update1dSingleRule(state, data) {
state.rules1d[data.rule] = data.value;
state.rules1d.name = data.name
state.rules1d.rules[data.rule] = data.value;
},
update1dRules(state, data) {
state.rules1d = data;
@ -60,6 +89,20 @@ export const store = createStore({
setLastBoard(state, data) {
state.lastBoard = data;
},
setCanvas(state, data) {
state.canvas = data
},
setContext(state, data) {
state.ctx = data
},
setBoardWidth(state) {
const width = Math.floor(state.canvasWidth / state.cellProperties.size);
state.boardWidth = width
},
setBoardHeight(state) {
const height = Math.floor(state.canvasHeight / state.cellProperties.size);
state.boardHeight = height
},
},
getters: {
getCellProperties(state) {
@ -69,7 +112,8 @@ export const store = createStore({
return state.rules1d;
},
getRule1d(state) {
return (rule) => state.rules1d[rule];
// getter with side-effect. no work
return state.rules1d;
},
isDrawing(state) {
return state.drawing;
@ -96,6 +140,84 @@ export const store = createStore({
return state.lastBoard;
},
},
actions: {},
actions: {
async drawCanvas({state}, board) {
/** Draw the board on the canvas according to the
cells' properties and drawing direction */
const props = state.cellProperties
board.map((row, y) => {
const d = props.size;
return row.map((cell, x) => {
state.ctx.fillStyle = (() => {
if (cell === 1) return props.liveColor;
return props.deadColor;
})();
if (state.drawingDirection === "x")
state.ctx.fillRect(y * d, x * d, d, d);
else state.ctx.fillRect(x * d, y * d, d, d);
return cell;
});
});
},
async compute1dInitialState({state}) {
/**
create the initial state for an elementary automaton based :
- on the selected board dimensions
- if we want to start from a single cell or several random ones
*/
if (state.initial1dState === "onecell")
return create1dStateOneCell(state.canvasWidth);
return create1dState(state.canvasWidth, getRandomInt, [0, 2]);
},
async draw1d({state, commit, dispatch}) {
/** draw an elementary cellular automaton from an initial state and
a set of rules */
const initialState = await dispatch("compute1dInitialState")
const board = createBoard(initialState, state.rules1d.rules, state.boardWidth);
commit("setLastBoard", board);
await dispatch("drawCanvas", board);
},
async draw2d({state, dispatch}, board) {
/** draw a 2D cellular automaton from an initial state and
a set of rules */
if (this.drawing === 0) return;
const draw2dNext = async (b) => {
if (state.drawing === 0) return;
const newBoard = evolve2d(b, conwayRules);
dispatch("drawCanvas", b, this.cellProperties);
await sleep(this.refreshRate);
draw2dNext(newBoard);
};
return draw2dNext(board);
},
async draw2dNew({state, commit, dispatch}) {
/** draw a 2d cellular automaton from a random initial state
and a set of rules */
const initialState = create2dState(
state.boardWidth,
state.boardHeight,
getRandomInt,
[0, 2]
);
const board = evolve2d(initialState, conwayRules);
commit("setLastBoard", board);
await dispatch("draw2d", board);
},
async draw2dLast({state, dispatch}) {
/** draw a 2d cellular automaton from the latest known state
of the board and a set of rules */
await dispatch("draw2d", state.lastBoard);
},
stop({ commit }) {
/** stop currently running drawing routine */
commit("setDrawingStatus", 0);
},
reset({state, dispatch}) {
/** stop currently running drawing routine and clear the board */
dispatch("stop");
state.ctx.clearRect(0, 0, state.canvasWidth, state.canvasHeight);
}
},
modules: {},
});