Compare commits

...

23 Commits

Author SHA1 Message Date
09f900ee95 Merge pull request 'mobile-friendly' (#3) from mobile-friendly into dev
Reviewed-on: #3
2022-12-04 18:49:15 +01:00
f7c6467941 plural, stop 2022-12-04 18:47:20 +01:00
d602c83d3d changed rules to ruleset for clarity 2022-12-04 18:38:50 +01:00
cb3ddadfdf ruleset presets typo 2022-12-04 18:17:14 +01:00
5c21fb2ac3 update board dimensions after window resize 2022-12-04 18:14:57 +01:00
2c69840d6f reset/stop to bottom 2022-12-04 17:24:45 +01:00
1204ac01c3 updated example picture 2022-12-04 16:56:47 +01:00
d000641ea5 menu tweak 2022-12-04 16:45:40 +01:00
71b046cbf6 css based active menu 2022-12-04 16:08:56 +01:00
695dab59ee burger menu for mobile (media queries + resize) 2022-12-04 15:36:39 +01:00
6215550ff5 image for readme 2022-12-02 18:16:33 +01:00
7f0f1dfdd7 remove active menu from store 2022-12-02 18:11:12 +01:00
9cd0537717 typo 2022-12-02 17:48:28 +01:00
8736ec566f removed vuex store 2022-12-02 17:45:15 +01:00
2901bb3f77 readme 2022-12-02 17:36:11 +01:00
5eb24797a2 removed old dependencies 2022-12-02 17:27:30 +01:00
d7c2d45180 linting and formating 2022-12-02 17:11:34 +01:00
0556992c3b updated pre-commit config 2022-12-02 17:11:01 +01:00
3a2cbc9349 adapted components to pinia 2022-12-02 17:10:21 +01:00
f318350149 pre-commit config 2022-12-02 17:09:55 +01:00
f178148416 prettier : indent SFC blocks 2022-12-02 15:54:39 +01:00
5972f9e868 add pinia and a tmp global stores 2022-12-02 15:53:49 +01:00
239be6204e rules preset in separate file 2022-12-02 15:52:43 +01:00
21 changed files with 926 additions and 767 deletions

9
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,9 @@
repos:
- repo: https://github.com/pre-commit/mirrors-eslint
rev: "v8.28.0"
hooks:
- id: eslint
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v2.7.1"
hooks:
- id: prettier

View File

@ -1 +1,3 @@
{}
{
"vueIndentScriptAndStyle": true
}

View File

@ -2,6 +2,8 @@
Explore 1D and 2D cellular automata, with a few bells and whistles.
![rules73](./example.png)
## Project setup
```
@ -11,7 +13,7 @@ npm install
### Compiles and hot-reloads for development
```
npm run serve
npm run dev
```
### Compiles and minifies for production
@ -26,9 +28,15 @@ npm run build
npm run lint
```
### Format files
```
npm run format
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
See [Configuration Reference](https://vitejs.dev/guide/).
### References

BIN
example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

90
package-lock.json generated
View File

@ -10,9 +10,9 @@
"dependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"install": "^0.13.0",
"pinia": "^2.0.27",
"vite": "^3.2.4",
"vue": "3.2",
"vuex": "4.1"
"vue": "3.2"
},
"devDependencies": {
"eslint": "^8.28.0",
@ -1548,6 +1548,56 @@
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/pinia": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.27.tgz",
"integrity": "sha512-nOnXP0OFeL8R4WjAHsterU+11vptda643gH02xKNtSCDPiRzVfRYodOLihLDoa0gL1KKuQKV+KOzEgdt3YvqEw==",
"dependencies": {
"@vue/devtools-api": "^6.4.5",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/postcss": {
"version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
@ -1969,17 +2019,6 @@
"eslint": ">=6.0.0"
}
},
"node_modules/vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -3083,6 +3122,23 @@
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"pinia": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.27.tgz",
"integrity": "sha512-nOnXP0OFeL8R4WjAHsterU+11vptda643gH02xKNtSCDPiRzVfRYodOLihLDoa0gL1KKuQKV+KOzEgdt3YvqEw==",
"requires": {
"@vue/devtools-api": "^6.4.5",
"vue-demi": "*"
},
"dependencies": {
"vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"requires": {}
}
}
},
"postcss": {
"version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
@ -3324,14 +3380,6 @@
"semver": "^7.3.6"
}
},
"vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.11"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -12,9 +12,9 @@
"dependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"install": "^0.13.0",
"pinia": "^2.0.27",
"vite": "^3.2.4",
"vue": "3.2",
"vuex": "4.1"
"vue": "3.2"
},
"devDependencies": {
"eslint": "^8.28.0",

View File

@ -1,66 +1,153 @@
<template>
<div id="main">
<h1 id="main-title">Cellular Automata Explorer</h1>
<h1 id="main-title">
<span id="burger-toggle" @click="toggleMainMenu">{{
mainMenu == true ? "▼" : "☰"
}}</span>
Cellular Automata Explorer
</h1>
<div id="container">
<MainMenu />
<MainMenu v-if="mainMenu || windowWidth >= 800" />
<CanvasBoard />
</div>
<MenuReset row-title="" />
</div>
</template>
<script>
import MainMenu from "./components/MainMenu.vue";
import CanvasBoard from "./components/CanvasBoard.vue";
import MainMenu from "./components/MainMenu.vue";
import CanvasBoard from "./components/CanvasBoard.vue";
import MenuReset from "./components/MenuReset.vue";
import { mapWritableState, mapActions } from "pinia";
import { globalStore } from "./stores/index.js";
export default {
export default {
name: "App",
components: {
MainMenu,
MenuReset,
CanvasBoard,
},
};
data() {
return {
mainMenu: false,
windowWidth: window.innerWidth,
};
},
computed: {
...mapWritableState(globalStore, {
canvasWidth: "canvasWidth",
canvasHeight: "canvasHeight",
}),
},
mounted() {
this.$nextTick(() => {
window.addEventListener("resize", this.onResize);
});
},
beforeUnmount() {
window.removeEventListener("resize", this.onResize);
},
methods: {
...mapActions(globalStore, ["setBoardWidth", "setBoardHeight"]),
toggleMainMenu() {
this.mainMenu = !this.mainMenu;
},
onResize() {
this.$nextTick(() => {
this.windowWidth = window.innerWidth;
this.canvasWidth = window.innerWidth;
this.canvasHeight = window.innerHeight;
this.setBoardWidth();
this.setBoardHeight();
});
},
},
};
</script>
<style>
#app {
<style scope>
:root {
--dark1: #000000;
--dark2: #333333;
--dark3: #666666;
--light1: #999999;
--light2: #cccccc;
--light3: #eeeeee;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
/* color: #2c3e50; */
}
}
* {
* {
margin: 0;
padding: 0;
}
}
body {
background: black;
color: white;
body {
background: var(--dark1);
color: var(--light3);
font-family: Courier New;
}
}
canvas {
canvas {
flex: auto;
background: #110812;
margin-right: 10px;
}
background: rgb(0, 0, 0);
background: linear-gradient(
90deg,
rgba(0, 0, 0, 1) 0%,
rgba(131, 131, 131, 1) 52%,
rgba(0, 0, 0, 1) 100%
);
}
h1,
h2 {
h1,
h2 {
font-weight: bold;
}
}
h1 {
h1 {
margin: 10px auto;
font-size: larger;
text-align: center;
}
text-transform: uppercase;
}
#container {
#container {
display: flex;
height: calc(100vh - 100px);
overflow: hidden;
}
flex-direction: column;
}
#burger-toggle {
display: none;
cursor: pointer;
font-size: 1.5em;
vertical-align: middle;
color: var(--light2);
}
@media screen and (max-width: 800px) {
h1 {
font-size: medium;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
}
#burger-toggle {
display: inline;
}
#main-menu {
background: var(--dark2);
margin: 0 auto;
}
}
</style>

View File

@ -1,25 +1,25 @@
<template>
<main id="mainContent">
<canvas
id="canvas-board"
ref="canvas-board"
:width="canvasWidth"
:height="canvasHeight"
/>
</main>
</template>
<script>
import {
import { mapActions, mapState, mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import {
create1dState,
create1dStateOneCell,
create2dState,
createBoard,
conwayRules,
evolve2d,
} from "../modules/automata.js";
import { getRandomInt, sleep } from "../modules/common.js";
import { mapGetters } from "vuex";
export default {
} from "../modules/automata.js";
import { getRandomInt, sleep } from "../modules/common.js";
export default {
name: "CanvasBoard",
data() {
return {
@ -28,27 +28,25 @@ export default {
};
},
computed: {
...mapGetters({
cellProperties: "getCellProperties",
rules: "get1dRules",
canvasWidth: "getCanvasWidth",
canvasHeight: "getCanvasHeight",
refreshRate: "getRefreshRate",
initial1dState: "getInitial1dState",
drawingDirection: "getDrawingDirection",
canDraw: "getCanDraw",
lastBoard: "getLastBoard",
getDraw1d: "getDraw1d",
getDraw2d: "getDraw2d",
getDraw2dLast: "getDraw2dLast",
getReset: "getReset",
...mapState(globalStore, {
cellProperties: "cellProperties",
ruleset: "ruleset1d",
refreshRate: "refreshRate",
initial1dState: "initial1dState",
drawingDirection: "drawingDirection",
canDraw: "canDraw",
getDraw1d: "draw1d",
getDraw2d: "draw2d",
getDraw2dLast: "draw2dLast",
boardWidth: "boardWidth",
boardHeight: "boardHeight",
}),
...mapWritableState(globalStore, {
lastBoard: "lastBoard",
canvasWidth: "canvasWidth",
canvasHeight: "canvasHeight",
getReset: "reset",
}),
boardWidth: function () {
return Math.floor(this.canvasWidth / this.cellProperties.size);
},
boardHeight: function () {
return Math.floor(this.canvasHeight / this.cellProperties.size);
},
},
watch: {
getDraw1d(value) {
@ -67,13 +65,17 @@ export default {
mounted() {
this.canvas = Object.freeze(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
);
this.canvasWidth = this.canvas.parentElement.clientWidth;
this.canvasHeight = this.canvas.parentElement.clientHeight;
this.setBoardWidth();
this.setBoardHeight();
},
methods: {
...mapActions(globalStore, [
"toggleStop",
"setBoardWidth",
"setBoardHeight",
]),
drawCanvas(board) {
const props = this.cellProperties;
board.map((row, y) => {
@ -99,12 +101,12 @@ export default {
const initialState = this.compute1dInitialState();
const board = createBoard(
initialState,
this.rules.rules,
this.ruleset.rules,
this.boardWidth
);
this.$store.commit("setLastBoard", Object.freeze(board));
this.lastBoard = Object.freeze(board);
this.drawCanvas(board);
this.$store.dispatch("stop");
this.toggleStop();
},
draw2d(board) {
if (!this.canDraw) return;
@ -125,22 +127,23 @@ export default {
[0, 2]
);
const board = evolve2d(initialState, conwayRules);
this.$store.commit("setLastBoard", Object.freeze(board));
this.lastBoard = Object.freeze(board);
this.draw2d(board);
},
async draw2dLast() {
this.draw2d(this.lastBoard);
if (this.lastBoard != undefined) this.draw2d(this.lastBoard);
},
reset() {
this.$store.dispatch("stop");
this.toggleStop();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.$store.commit("toggleReset", 0);
this.getReset = 0;
},
},
};
};
</script>
<style>
#mainContent {
min-width: 70%;
}
#canvas-board {
flex: 1;
margin: 0 auto;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div id="sidebar">
<div id="main-menu">
<MenuGeneralOptions />
<MenuCellProperties />
<MenuElementaryCA />
@ -8,11 +8,11 @@
</template>
<script>
import MenuCellProperties from "./MenuCellProperties.vue";
import MenuGeneralOptions from "./MenuGeneralOptions.vue";
import MenuElementaryCA from "./MenuElementaryCA.vue";
import Menu2dCA from "./Menu2dCA.vue";
export default {
import MenuCellProperties from "./MenuCellProperties.vue";
import MenuGeneralOptions from "./MenuGeneralOptions.vue";
import MenuElementaryCA from "./MenuElementaryCA.vue";
import Menu2dCA from "./Menu2dCA.vue";
export default {
name: "MainMenu",
components: {
MenuCellProperties,
@ -20,43 +20,33 @@ export default {
MenuElementaryCA,
Menu2dCA,
},
};
};
</script>
<style>
#sidebar {
width: 25%;
padding: 0 10px;
overflow-y: scroll;
}
#main-menu {
display: flex;
flex-direction: row;
width: 100%;
/* Hide scrollbar for Chrome, Safari and Opera */
#sidebar::-webkit-scrollbar {
flex: 1;
}
/* Hide scrollbar for Chrome, Safari and Opera */
#main-menu::-webkit-scrollbar {
display: none;
}
}
/* Hide scrollbar for IE, Edge and Firefox */
#sidebar {
/* Hide scrollbar for IE, Edge and Firefox */
#main-menu {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
@media screen and (max-width: 800px) {
#container {
display: flex;
@media screen and (max-width: 800px) {
#main-menu {
flex-direction: column;
justify-content: center;
position: absolute;
}
#mainContent {
flex: 1;
width: 100%;
}
#sidebar {
flex: 1;
padding: 0;
width: 100%;
}
}
</style>

View File

@ -1,56 +1,33 @@
<template>
<MenuRow row-title="2D Cellular Automata">
<MenuRow row-title="2D Cellular Automaton">
<div class="form-field">
<label>Start from last result</label>
<input type="button" value="start" @click="draw2dLast" />
<label>Start from empty board</label>
<input
type="button"
name="start2d"
value="start"
@click="toggleDraw2d()"
/>
</div>
<div class="form-field">
<input type="button" name="start2d" value="start" @click="draw2d" />
<input
type="button"
name="stop"
class="stop"
value="stop"
@click="stop"
/>
<input
type="button"
name="reset"
class="reset"
value="reset"
@click="reset"
/>
<label>Start from last result</label>
<input type="button" value="start" @click="toggleDraw2dLast()" />
</div>
</MenuRow>
</template>
<script>
import MenuRow from "./MenuRow.vue";
import { mapGetters } from "vuex";
import { mapActions } from "pinia";
import MenuRow from "./MenuRow.vue";
import { globalStore } from "../stores/index.js";
export default {
export default {
name: "Menu2dCA",
components: {
MenuRow,
},
computed: {
...mapGetters({
lastBoard: "getLastBoard",
}),
},
methods: {
draw2d() {
this.$store.dispatch("draw2d");
...mapActions(globalStore, ["toggleDraw2dLast", "toggleDraw2d"]),
},
draw2dLast() {
this.$store.dispatch("draw2dLast");
},
reset() {
this.$store.dispatch("reset");
},
stop() {
this.$store.dispatch("stop");
},
},
};
};
</script>

View File

@ -34,29 +34,27 @@
</template>
<script>
import MenuRow from "./MenuRow.vue";
export default {
import { mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
export default {
name: "MainMenu",
components: {
MenuRow,
},
data() {
return {
cellProperties: this.$store.state.cellProperties,
};
computed: {
...mapWritableState(globalStore, ["cellProperties"]),
},
methods: {
getCellProperties(event) {
const elem = event.target;
const prop = this.$store.state.cellProperties;
const prop = this.cellProperties;
return prop[elem.name];
},
updateCellProperties(event) {
const elem = event.target;
const prop = { name: elem.name, value: elem.value };
//console.log(prop)
this.$store.commit("setCellProperties", prop);
this.cellProperties[elem.name] = elem.value;
},
},
};
};
</script>

View File

@ -1,5 +1,5 @@
<template>
<MenuRow row-title="Elementary Cellular Automata">
<MenuRow row-title="Elementary Automaton">
<form>
<div class="form-field">
<label>
@ -29,11 +29,11 @@
<br />
<select
name="ruleset-elementary"
:value="rules.name"
@input="updateRules"
:value="ruleset.name"
@input="updateRuleset"
>
<option
v-for="(ruleset, index) in presetRules"
v-for="(ruleset, index) in presetRuleset"
:key="'ruleset-preset-elementary-' + index"
:value="ruleset.name"
>
@ -43,10 +43,10 @@
</label>
</div>
<div class="form-field">
<a style="cursor: pointer" @click="copyRules">copy rules</a>
<a style="cursor: pointer" @click="copyRuleset">copy rules</a>
</div>
<div
v-for="(rule, name, index) in rules.rules"
v-for="(rule, name, index) in ruleset.rules"
:key="'rule-' + index"
class="form-field"
>
@ -63,142 +63,48 @@
</div>
</form>
<div class="form-field">
<input type="button" name="start" value="start" @click="draw1d" />
<input
type="button"
name="reset"
class="reset"
value="reset"
@click="reset"
/>
<input type="button" name="start" value="start" @click="toggleDraw1d()" />
</div>
</MenuRow>
</template>
<script>
import { mapGetters } from "vuex";
import MenuRow from "./MenuRow.vue";
export default {
import { mapActions, mapWritableState } from "pinia";
import { presetRuleset, initialStates } from "../modules/preset.js";
import { globalStore } from "../stores/index.js";
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",
},
],
presetRuleset: presetRuleset,
initialStates: initialStates,
};
},
computed: {
...mapGetters({
initialState: "getInitial1dState",
rules: "get1dRules",
...mapWritableState(globalStore, {
initialState: "initial1dState",
ruleset: "ruleset1d",
}),
rules1dFileName() {
// TODO: broken
return (
Object.keys(this.rules)
Object.keys(this.ruleset)
.map((index) => {
return this.rules[index];
return this.ruleset[index];
})
.join("_") + ".json"
);
},
},
methods: {
copyRules() {
const rules = JSON.stringify(this.rules);
navigator.clipboard.writeText(rules);
...mapActions(globalStore, ["toggleDraw1d"]),
copyRuleset() {
const newRuleset = JSON.stringify(this.ruleset);
navigator.clipboard.writeText(newRuleset);
},
isCurrentPreset(event) {
const elem = event.target;
@ -207,39 +113,28 @@ export default {
updateSingleRule(event) {
const elem = event.target;
const value = elem.checked ? 1 : 0;
const data = { rule: elem.name, value: value };
this.$store.commit("update1dSingleRule", data);
this.ruleset.rules[elem.name] = value;
},
updateRules(event) {
// TODO : change this, awfully confusing
updateRuleset(event) {
const elem = event.target;
const name = elem.value;
const rules = this.presetRules.find((ruleset) => {
const newRuleset = this.presetRuleset.find((ruleset) => {
return ruleset.name === name;
});
Object.keys(rules.rules).map((value) => {
const data = { name: name, rule: value, value: rules.rules[value] };
this.$store.commit("update1dSingleRule", data);
});
this.ruleset = newRuleset;
},
updateInitialState(event) {
const elem = event.target;
this.$store.commit("setInitial1dState", elem.value);
},
draw1d() {
this.$store.dispatch("draw1d");
},
reset() {
this.$store.dispatch("reset");
this.initialState = elem.value;
},
},
};
};
</script>
<style>
.menu-row a {
.menu-row a {
color: white;
font-weight: bold;
text-decoration: none;
font-size: small;
}
}
</style>

View File

@ -56,40 +56,40 @@
</template>
<script>
import MenuRow from "./MenuRow.vue";
import { mapGetters } from "vuex";
export default {
import { mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import MenuRow from "./MenuRow.vue";
export default {
name: "MenuGeneralOptions",
components: {
MenuRow,
},
computed: {
...mapGetters({
canvasWidth: "getCanvasWidth",
canvasHeight: "getCanvasHeight",
refreshRate: "getRefreshRate",
drawingDirection: "getDrawingDirection",
}),
...mapWritableState(globalStore, [
"canvasWidth",
"canvasHeight",
"refreshRate",
"drawingDirection",
]),
},
methods: {
updateCanvasHeight: function (event) {
const elem = event.target;
this.$store.commit("setCanvasHeight", elem.value);
this.canvasHeight = elem.value;
},
updateCanvasWidth: function (event) {
const elem = event.target;
this.$store.commit("setCanvasWidth", elem.value);
this.canvasWidth = elem.value;
},
updateRefreshRate: function (event) {
const elem = event.target;
this.$store.commit("setRefreshRate", elem.value);
this.refreshRate = elem.value;
},
updateDrawingDirection: function (event) {
const elem = event.target;
const value = elem.checked ? "x" : "y";
this.$store.commit("setDrawingDirection", value);
console.log(this.drawingDirection);
this.drawingDirection = value;
},
},
};
};
</script>

View File

@ -0,0 +1,36 @@
<template>
<div class="form-field">
<input
type="button"
name="stop"
class="stop"
value="stop"
@click="toggleStop()"
/>
<input
type="button"
name="reset"
class="reset"
value="reset"
@click="toggleReset()"
/>
</div>
</template>
<script>
import { mapActions } from "pinia";
import { globalStore } from "../stores/index.js";
export default {
name: "MenuReset",
methods: {
...mapActions(globalStore, ["toggleReset", "toggleStop"]),
},
};
</script>
<style scoped>
.form-field {
display: flex;
margin: 5px;
justify-content: flex-end;
}
</style>

View File

@ -3,16 +3,14 @@
<h2 :id="rowTitle" @click="updateActiveMenu">
{{ rowTitle }}
</h2>
<div v-if="activeMenu === rowTitle" class="menu-row-content">
<div class="menu-row-content">
<slot />
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
export default {
name: "MenuRow",
props: {
rowTitle: {
@ -20,56 +18,88 @@ export default {
default: "",
},
},
computed: {
...mapGetters({
activeMenu: "getActiveMenu",
}),
},
methods: {
updateActiveMenu(event) {
const elem = event.target;
const value = elem.id;
if (value == this.activeMenu) this.$store.commit("setActiveMenu", "");
else this.$store.commit("setActiveMenu", value);
},
},
};
};
</script>
<style>
.menu-row h2 {
.menu-row h2 {
font-size: medium;
padding: 10px;
cursor: pointer;
border: 2px solid darkgrey;
border-bottom: 1px solid var(--dark3);
border-top: 1px solid var(--dark3);
margin: 0 0 10px 0;
}
}
select {
select {
margin-top: 10px;
padding: 5px;
}
}
input[type="button"] {
input[type="button"] {
min-width: 60px;
padding: 5px;
font-weight: bold;
margin-right: 10px;
}
}
.form-field {
.form-field {
display: flex;
margin: 10px;
justify-content: space-between;
}
}
.menu-row {
.menu-row {
flex: 1;
}
position: relative;
}
label,
.form-field label {
.menu-row:hover .menu-row-content {
display: block;
width: 100%;
}
.menu-row-content {
position: absolute;
background: var(--dark1);
display: none;
}
label,
.form-field label {
margin-right: 10px;
font-weight: bold;
}
}
@media screen and (max-width: 800px) {
.menu-row {
margin: 0 auto;
width: 100%;
}
.menu-row h2,
.form-field {
margin: 0;
}
.menu-row h2 {
border-bottom: 1px solid var(--dark3);
border-top: none;
}
.form-field {
padding: 10px;
}
.menu-row:active .menu-row-content {
display: flex;
flex-direction: column;
height: 100%;
}
.menu-row-content {
position: relative;
width: 100%;
}
}
</style>

95
src/components/preset.js Normal file
View File

@ -0,0 +1,95 @@
const presetRuleset = [
{
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,
},
},
];
const 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",
},
];
export { presetRuleset, initialStates };

View File

@ -1,9 +1,10 @@
import { createApp } from "vue";
import App from "./App.vue";
import { store } from "./store";
import { createPinia } from "pinia";
const app = createApp(App);
const pinia = createPinia();
app.use(store);
app.use(pinia);
app.mount("#app");

95
src/modules/preset.js Normal file
View File

@ -0,0 +1,95 @@
const presetRuleset = [
{
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,
},
},
];
const 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",
},
];
export { presetRuleset, initialStates };

View File

@ -1,174 +0,0 @@
/* 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";
export const store = createStore({
strict: process.env.NODE_ENV !== "production",
state: {
rules1d: {
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",
},
canvasWidth: 0,
canvasHeight: 0,
boardWidth: 0,
boardHeight: 0,
refreshRate: 300,
initial1dState: "onecell",
activeMenu: "",
drawingDirection: "y",
lastBoard: {},
draw1d: false,
draw2d: false,
draw2dLast: false,
reset: false,
canDraw: true,
},
mutations: {
update1dSingleRule(state, data) {
state.rules1d.name = data.name;
state.rules1d.rules[data.rule] = data.value;
},
update1dRules(state, data) {
state.rules1d = data;
},
setCellProperties(state, data) {
state.cellProperties[data.name] = data.value;
},
setCanvasWidth(state, data) {
state.canvasWidth = data;
},
setCanvasHeight(state, data) {
state.canvasHeight = data;
},
setRefreshRate(state, data) {
state.refreshRate = data;
},
setInitial1dState(state, data) {
state.initial1dState = data;
},
setActiveMenu(state, data) {
state.activeMenu = data;
},
setDrawingDirection(state, data) {
state.drawingDirection = data;
},
setLastBoard(state, data) {
state.lastBoard = data;
},
setCanvas(state, data) {
state.canvas = data;
},
setContext(state, data) {
state.ctx = data;
},
toggleDraw1d(state, data) {
state.draw1d = data;
},
toggleDraw2d(state, data) {
state.draw2d = data;
},
toggleDraw2dLast(state, data) {
state.draw2dLast = data;
},
toggleReset(state, data) {
state.reset = data;
},
canDraw(state, data) {
state.canDraw = data;
},
},
getters: {
getCellProperties(state) {
return state.cellProperties;
},
get1dRules(state) {
return state.rules1d;
},
getRule1d(state) {
return state.rules1d;
},
getCanvasWidth(state) {
return state.canvasWidth;
},
getCanvasHeight(state) {
return state.canvasHeight;
},
getRefreshRate(state) {
return state.refreshRate;
},
getInitial1dState(state) {
return state.initial1dState;
},
getActiveMenu(state) {
return state.activeMenu;
},
getDrawingDirection(state) {
return state.drawingDirection;
},
getLastBoard(state) {
return state.lastBoard;
},
getDraw1d(state) {
return state.draw1d;
},
getDraw2d(state) {
return state.draw2d;
},
getDraw2dLast(state) {
return state.draw2dLast;
},
getReset(state) {
return state.reset;
},
getCanDraw(state) {
return state.canDraw;
},
},
actions: {
draw1d({ commit }) {
commit("toggleDraw1d", true);
},
draw2d({ commit }) {
commit("canDraw", true);
commit("toggleDraw2d", true);
},
draw2dLast({ commit }) {
commit("canDraw", true);
commit("toggleDraw2dLast", true);
},
reset({ dispatch, commit }) {
dispatch("stop");
commit("toggleReset", true);
},
stop({ commit }) {
commit("toggleDraw1d", false);
commit("toggleDraw2d", false);
commit("toggleDraw2dLast", false);
commit("canDraw", false);
},
},
modules: {},
});

72
src/stores/index.js Normal file
View File

@ -0,0 +1,72 @@
import { defineStore } from "pinia";
export const globalStore = defineStore("globalStore", {
state: () => {
return {
ruleset1d: {
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",
},
canvasWidth: 0,
canvasHeight: 0,
boardWidth: 0,
boardHeight: 0,
refreshRate: 300,
initial1dState: "onecell",
drawingDirection: "y",
lastBoard: {},
draw1d: false,
draw2d: false,
draw2dLast: false,
reset: false,
canDraw: true,
};
},
actions: {
setBoardWidth() {
this.boardWidth = Math.floor(this.canvasWidth / this.cellProperties.size);
},
setBoardHeight() {
this.boardHeight = Math.floor(
this.canvasHeight / this.cellProperties.size
);
},
toggleDraw1d() {
this.draw1d = true;
},
toggleDraw2d() {
this.toggleStop();
this.canDraw = true;
this.draw2d = true;
},
toggleDraw2dLast() {
this.toggleStop();
this.canDraw = true;
this.draw2dLast = true;
},
toggleReset() {
this.toggleStop();
this.reset = true;
},
toggleStop() {
this.draw1d = false;
this.draw2d = false;
this.draw2dLast = false;
this.canDraw = false;
},
},
});

View File

@ -1,13 +0,0 @@
module.exports = {
configureWebpack: {
devServer: {
overlay: {
warnings: true,
errors: true,
},
watchOptions: {
ignored: [/node_modules/, /public/, /\.#/],
},
},
},
};