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 }}
@ -25,35 +24,37 @@
</div>
<div class="form-field">
<label
>Rules presets
>Rules presets
<br />
<select name="rules1d" value="rules" @input="update1dRules">
<select
name="ruleset-elementary"
:value="rules.name"
@input="updateRules"
>
<option
v-for="(rule, name) in presetRules"
:key="'rule1d' + name"
:value="name"
v-for="ruleset in presetRules"
:value="ruleset.name"
>
{{ 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
>{{ name }}
>{{ name }}
<input
:value="rule"
type="checkbox"
:name="name"
:checked="rule"
@input="update1dSingleRule"
@input="updateSingleRule"
/>
</label>
</div>
@ -72,150 +73,171 @@
</template>
<script>
import { mapGetters } from "vuex";
import MenuRow from "./MenuRow.vue";
export default {
name: "MenuElementaryCA",
components: {
MenuRow,
},
data() {
// TODO: Why not a getter in the store?
return {
presetRules: {
"rule 73": {
100: 0,
101: 0,
110: 1,
111: 0,
"011": 1,
"010": 0,
"001": 0,
"000": 1,
},
"rule 86": {
100: 1,
101: 0,
110: 0,
111: 1,
"011": 0,
"010": 1,
"001": 0,
"000": 1,
},
"rule 90": {
100: 1,
101: 0,
110: 1,
111: 0,
"011": 0,
"010": 0,
"001": 1,
"000": 0,
},
"rule 45?": {
100: 0,
101: 0,
110: 1,
111: 0,
"011": 1,
"010": 0,
"001": 1,
"000": 1,
},
"rule 54?": {
100: 1,
101: 0,
110: 1,
111: 1,
"011": 0,
"010": 1,
"001": 1,
"000": 0,
},
"unknown rule": {
100: 0,
101: 0,
110: 0,
111: 1,
"011": 0,
"010": 0,
"001": 1,
"000": 1,
},
import { mapGetters } from "vuex";
import MenuRow from "./MenuRow.vue";
export default {
name: "MenuElementaryCA",
components: {
MenuRow,
},
data() {
// TODO: Why not a getter in the store?
return {
presetRules: [
{
name: "rule 73",
rules: {
100: 0,
101: 0,
110: 1,
111: 0,
"011": 1,
"010": 0,
"001": 0,
"000": 1,
}
},
{
name: "rule 86",
rules:{
100: 1,
101: 0,
110: 0,
111: 1,
"011": 0,
"010": 1,
"001": 0,
"000": 1,
},
},
{
name: "rule 90",
rules: {
100: 1,
101: 0,
110: 1,
111: 0,
"011": 0,
"010": 0,
"001": 1,
"000": 0,
},
},
{
name: "rule 45?",
rules: {
100: 0,
101: 0,
110: 1,
111: 0,
"011": 1,
"010": 0,
"001": 1,
"000": 1,
},
},
{
name: "rule 54?",
rules: {
100: 1,
101: 0,
110: 1,
111: 1,
"011": 0,
"010": 1,
"001": 1,
"000": 0,
},
},
{
name: "unknown rule",
rules: {
100: 0,
101: 0,
110: 0,
111: 1,
"011": 0,
"010": 0,
"001": 1,
"000": 1,
},
}
],
initialStates: [
{
id: "onecell",
name: "One cell at center",
description: "State with a single cell in the middle",
},
{
id: "random",
name: "Random cell",
description: "State populated with random cells",
},
],
};
},
computed: {
...mapGetters({
initialState: "getInitial1dState",
rules: "get1dRules",
}),
rules1dFileName() {
return (
Object.keys(this.rules)
.map((index) => {
return this.rules[index];
})
.join("_") + ".json"
);
},
initial1dStates: [
{
id: "onecell",
name: "One cell at center",
description: "State with a single cell in the middle",
},
{
id: "random",
name: "Random cell",
description: "State populated with random cells",
},
],
};
},
computed: {
...mapGetters({
initial1dState: "getInitial1dState",
rules: "get1dRules",
}),
rules1dFileName() {
return (
Object.keys(this.rules)
.map((index) => {
return this.rules[index];
})
.join("_") + ".json"
);
},
},
methods: {
copy1dRules() {
const rules = JSON.stringify(this.rules);
navigator.clipboard.writeText(rules);
},
isCurrentPreset(event) {
const elem = event.target;
return this.initialState === elem.value;
},
update1dSingleRule(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) {
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] };
methods: {
copyRules() {
const rules = JSON.stringify(this.rules);
navigator.clipboard.writeText(rules);
},
isCurrentPreset(event) {
const elem = event.target;
return this.initialState === elem.value;
},
updateSingleRule(event) {
const elem = event.target;
const value = elem.checked ? 1 : 0;
const data = { rule: elem.name, value: value };
this.$store.commit("update1dSingleRule", data);
});
},
updateRules(event) {
// TODO : change this, awfully confusing
const elem = event.target;
const name = elem.value;
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);
});
},
updateInitialState(event) {
const elem = event.target;
this.$store.commit("setInitial1dState", elem.value);
},
draw1d() {
this.$store.commit("setDrawingStatus", 1);
this.$store.dispatch("draw1d");
},
reset() {
this.$store.dispatch("reset");
},
},
updateInitial1dState(event) {
const elem = event.target;
this.$store.commit("setInitial1dState", elem.value);
},
draw1d() {
this.$root.$store.state.drawing = 1;
this.$root.$emit("draw1d");
},
reset() {
this.$root.$emit("reset");
},
},
};
};
</script>
<style>
.menu-row a {
color: white;
font-weight: bold;
text-decoration: none;
font-size: small;
}
.menu-row a {
color: white;
font-weight: bold;
text-decoration: none;
font-size: small;
}
</style>

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,25 +1,53 @@
/* 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: {
111: 0,
110: 1,
101: 0,
100: 0,
"011": 1,
"010": 0,
"001": 0,
"000": 1,
name: "rule 73",
rules:
{
111: 0,
110: 1,
101: 0,
100: 0,
"011": 1,
"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: {},
});