vue 2 to 3 #1

Merged
gator merged 25 commits from dev into master 2022-12-02 11:23:43 +01:00
22 changed files with 549 additions and 649 deletions
Showing only changes of commit d201a73ede - Show all commits

View File

@ -5,12 +5,12 @@ module.exports = {
},
extends: [
// add more generic rulesets here, such as:
'eslint:recommended',
'plugin:vue/vue3-recommended',
"prettier"
"eslint:recommended",
"plugin:vue/vue3-recommended",
"prettier",
],
rules: {
// override/add rules settings here, such as:
// 'vue/no-unused-vars': 'error'
}
}
},
};

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,30 +1,37 @@
# explorata
Explore 1D and 2D cellular automata, with a few bells and whistles.
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
### References
- https://natureofcode.com/book/chapter-7-cellular-automata/
- https://en.wikipedia.org/wiki/Hashlife
- https://plato.stanford.edu/entries/cellular-automata/supplement.html

View File

@ -1,5 +1,3 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
}
presets: ["@vue/cli-plugin-babel/preset"],
};

View File

@ -1,4 +1,4 @@
version: '3.4'
version: "3.4"
services:
explorata:

View File

@ -1,18 +1,18 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title>Explorata</title>
</head>
<body>
<noscript>
<strong>
Althought you're right to browse the Web with Javascript disabled, Explorata doesn't work properly without it.
Please enable Javascript to continue.
Althought you're right to browse the Web with Javascript disabled,
Explorata doesn't work properly without it. Please enable Javascript to
continue.
</strong>
</noscript>
<div id="app"></div>

View File

@ -6,7 +6,8 @@
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"format": "prettier . --write"
},
"dependencies": {
"@vitejs/plugin-vue": "^3.2.0",

View File

@ -1,71 +1,66 @@
<template>
<div id="main">
<h1
id="main-title"
>
Cellular Automata Explorer
</h1>
<h1 id="main-title">Cellular Automata Explorer</h1>
<div id="container">
<MainMenu />
<Canvas />
<CanvasBoard />
</div>
</div>
</template>
<script>
import MainMenu from './components/MainMenu.vue'
import Canvas from './components/Canvas.vue'
import MainMenu from "./components/MainMenu.vue";
import CanvasBoard from "./components/CanvasBoard.vue";
export default {
name: 'App',
name: "App",
components: {
MainMenu,
Canvas
}
}
CanvasBoard,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
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;
margin: 0;
padding: 0;
}
body {
background: black;
color: white;
font-family: Courier New;
background: black;
color: white;
font-family: Courier New;
}
canvas {
flex: auto;
background: #110812;
margin-right: 10px;
flex: auto;
background: #110812;
margin-right: 10px;
}
h1, h2 {
font-weight: bold;
h1,
h2 {
font-weight: bold;
}
h1 {
margin : 10px auto;
font-size: larger;
text-align: center;
margin: 10px auto;
font-size: larger;
text-align: center;
}
#container {
display: flex;
height: calc(100vh - 100px);
overflow: hidden;
display: flex;
height: calc(100vh - 100px);
overflow: hidden;
}
</style>

View File

@ -1,134 +0,0 @@
<template>
<main id="mainContent">
<canvas
id="canvas"
ref="canvas"
:width="canvasWidth"
:height="canvasHeight"
/>
</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: 'Canvas',
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 = this.$refs['canvas']
this.ctx = this.canvas.getContext('2d')
this.$store.commit('setCanvasWidth', this.canvas.parentElement.clientWidth)
this.$store.commit('setCanvasHeight', this.canvas.parentElement.clientHeight)
this.$root.$on('draw1d', () => { this.draw1d() })
this.$root.$on('draw2dNew', () => { this.draw2dNew() })
this.$root.$on('draw2dLast', () => { this.draw2dLast() })
this.$root.$on('reset', () => { this.reset() })
this.$root.$on('stop', () => { this.stop() })
},
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,142 @@
<template>
<main id="mainContent">
<CanvasBoard
id="canvas-board"
ref="canvas-board"
:width="canvasWidth"
:height="canvasHeight"
/>
</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 = this.$refs["canvas"];
this.ctx = this.canvas.getContext("2d");
this.$store.commit("setCanvasWidth", this.canvas.parentElement.clientWidth);
this.$store.commit(
"setCanvasHeight",
this.canvas.parentElement.clientHeight
);
this.$root.$on("draw1d", () => {
this.draw1d();
});
this.$root.$on("draw2dNew", () => {
this.draw2dNew();
});
this.$root.$on("draw2dLast", () => {
this.draw2dLast();
});
this.$root.$on("reset", () => {
this.reset();
});
this.$root.$on("stop", () => {
this.stop();
});
},
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

@ -8,29 +8,28 @@
</template>
<script>
import MenuCellProperties from './MenuCellProperties.vue'
import MenuGeneralOptions from './MenuGeneralOptions.vue'
import MenuElementaryCA from './MenuElementaryCA.vue'
import Menu2dCA from './Menu2dCA.vue'
import MenuCellProperties from "./MenuCellProperties.vue";
import MenuGeneralOptions from "./MenuGeneralOptions.vue";
import MenuElementaryCA from "./MenuElementaryCA.vue";
import Menu2dCA from "./Menu2dCA.vue";
export default {
name: 'MainMenu',
name: "MainMenu",
components: {
MenuCellProperties,
MenuGeneralOptions,
MenuElementaryCA,
Menu2dCA
Menu2dCA,
},
}
};
</script>
<style>
#sidebar {
width: 25%;
padding: 0 10px;
overflow-y: scroll;
width: 25%;
padding: 0 10px;
overflow-y: scroll;
}
/* Hide scrollbar for Chrome, Safari and Opera */
#sidebar::-webkit-scrollbar {
display: none;
@ -38,26 +37,26 @@ export default {
/* Hide scrollbar for IE, Edge and Firefox */
#sidebar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
@media screen and (max-width: 800px) {
#container {
display: flex;
flex-direction: column;
justify-content: center;
}
#container {
display: flex;
flex-direction: column;
justify-content: center;
}
#mainContent {
flex: 1;
width: 100%;
}
#mainContent {
flex: 1;
width: 100%;
}
#sidebar {
flex: 1;
padding: 0;
width: 100%;
}
#sidebar {
flex: 1;
padding: 0;
width: 100%;
}
}
</style>

View File

@ -2,65 +2,56 @@
<MenuRow row-title="2D Cellular Automata">
<div class="form-field">
<label>Start from last result</label>
<input
type="button"
value="start"
@click="startFromLast"
>
<input type="button" value="start" @click="startFromLast" />
</div>
<div class="form-field">
<input
type="button"
name="start2d"
value="start"
@click="draw2d"
>
<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"
>
/>
</div>
</MenuRow>
</template>
<script>
import MenuRow from './MenuRow.vue'
import {mapGetters} from 'vuex'
import MenuRow from "./MenuRow.vue";
import { mapGetters } from "vuex";
export default {
name: 'Menu2dCA',
name: "Menu2dCA",
components: {
MenuRow
MenuRow,
},
computed: {
...mapGetters({
lastBoard: 'getLastBoard',
})
lastBoard: "getLastBoard",
}),
},
methods: {
draw2d() {
this.$root.$store.state.drawing = 1
this.$root.$emit('draw2dNew')
this.$root.$store.state.drawing = 1;
this.$root.$emit("draw2dNew");
},
reset() {
this.$root.$emit('reset')
this.$root.$emit("reset");
},
stop() {
this.$root.$emit('stop')
this.$root.$emit("stop");
},
startFromLast() {
this.$root.$emit('draw2dLast')
}
}
}
this.$root.$emit("draw2dLast");
},
},
};
</script>

View File

@ -8,7 +8,7 @@
type="color"
@value="cellProperties.liveColor"
@input="updateCellProperties"
>
/>
</div>
<div class="form-field">
<label for="dead">Dead cell color</label>
@ -17,7 +17,7 @@
type="color"
:value="cellProperties.deadColor"
@input="updateCellProperties"
>
/>
</div>
<div class="form-field">
<label>Cell size</label>
@ -27,36 +27,36 @@
min="1"
:value="cellProperties.size"
@input="updateCellProperties"
>
/>
</div>
</form>
</MenuRow>
</template>
<script>
import MenuRow from './MenuRow.vue'
import MenuRow from "./MenuRow.vue";
export default {
name: 'MainMenu',
name: "MainMenu",
components: {
MenuRow
MenuRow,
},
data() {
return {
cellProperties: this.$store.state.cellProperties
}
cellProperties: this.$store.state.cellProperties,
};
},
methods: {
getCellProperties(event) {
const elem = event.target
const prop = this.$store.state.cellProperties
return prop[elem.name]
const elem = event.target;
const prop = this.$store.state.cellProperties;
return prop[elem.name];
},
updateCellProperties(event) {
const elem = event.target
const prop = {'name' : elem.name, 'value' : elem.value}
const elem = event.target;
const prop = { name: elem.name, value: elem.value };
//console.log(prop)
this.$store.commit('setCellProperties', prop)
}
this.$store.commit("setCellProperties", prop);
},
},
}
};
</script>

View File

@ -2,8 +2,9 @@
<MenuRow row-title="Elementary Cellular Automata">
<form>
<div class="form-field">
<label>Initial state presets
<br>
<label
>Initial state presets
<br />
<select
name="initial1dStates"
value="initial1dStates"
@ -13,7 +14,8 @@
v-for="state in initial1dStates"
:key="'preset1d' + state.id"
:value="state.id"
>{{ state.name }}
>
{{ state.name }}
</option>
</select>
</label>
@ -22,149 +24,198 @@
<label>Rules</label>
</div>
<div class="form-field">
<label>Rules presets
<br>
<select
name="rules1d"
value="rules"
@input="update1dRules"
>
<label
>Rules presets
<br />
<select name="rules1d" value="rules" @input="update1dRules">
<option
v-for="(rule, name) in presetRules"
:key="'rule1d' + name"
:value="name"
>{{ name }}
>
{{ name }}
</option>
</select>
</label>
</div>
<div class="form-field">
<a
style="cursor: pointer;"
@click="copy1dRules"
>copy rules</a>
<a style="cursor: pointer" @click="copy1dRules">copy rules</a>
</div>
<div
v-for="(rule, name) in rules"
:key="'rule1d' + name"
class="form-field"
>
<label>{{ name }}
<label
>{{ name }}
<input
:value="rule"
type="checkbox"
:name="name"
:checked="rule"
@input="update1dSingleRule"
>
/>
</label>
</div>
</form>
<div class="form-field">
<input
type="button"
name="start"
value="start"
@click="draw1d"
>
<input type="button" name="start" value="start" @click="draw1d" />
<input
type="button"
name="reset"
class="reset"
value="reset"
@click="reset()"
>
/>
</div>
</MenuRow>
</template>
<script>
import {mapGetters} from 'vuex'
import MenuRow from './MenuRow.vue'
import { mapGetters } from "vuex";
import MenuRow from "./MenuRow.vue";
export default {
name: 'MenuElementaryCA',
name: "MenuElementaryCA",
components: {
MenuRow
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}
"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,
},
},
initial1dStates: [
{ id : "onecell",
name : "One cell at center",
description : "State with a single cell in the middle",
{
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",
}
]
}
id: "random",
name: "Random cell",
description: "State populated with random cells",
},
],
};
},
computed: {
...mapGetters({
initial1dState: 'getInitial1dState',
rules: 'get1dRules'
initial1dState: "getInitial1dState",
rules: "get1dRules",
}),
rules1dFileName() {
return Object.keys(this.rules).map(
(index) => {
return this.rules[index]
}).join("_") + ".json"
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)
copy1dRules() {
const rules = JSON.stringify(this.rules);
navigator.clipboard.writeText(rules);
},
isCurrentPreset(event) {
const elem = event.target
return this.initialState === elem.value
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)
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]
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]}
this.$store.commit('update1dSingleRule', data)
})
const data = { rule: index, value: rules[index] };
this.$store.commit("update1dSingleRule", data);
});
},
updateInitial1dState(event) {
const elem = event.target
this.$store.commit('setInitial1dState', elem.value)
const elem = event.target;
this.$store.commit("setInitial1dState", elem.value);
},
draw1d() {
this.$root.$store.state.drawing = 1
this.$root.$emit('draw1d')
this.$root.$store.state.drawing = 1;
this.$root.$emit("draw1d");
},
reset() {
this.$root.$emit('reset')
this.$root.$emit("reset");
},
}
}
},
};
</script>
<style>
.menu-row a {
.menu-row a {
color: white;
font-weight: bold;
text-decoration: none;
font-size: small;
}
}
</style>

View File

@ -13,7 +13,7 @@
step="10"
:value="canvasWidth"
@input="updateCanvasWidth"
>
/>
</div>
<div class="form-field">
<label>Height</label>
@ -24,7 +24,7 @@
step="10"
:value="canvasHeight"
@input="updateCanvasHeight"
>
/>
</div>
<div class="form-field">
<label>Refresh Rate (ms)</label>
@ -38,57 +38,58 @@
step="100"
:value="refreshRate"
@input="updateRefreshRate"
>
/>
</div>
<div class="form-field">
<label>Invert Drawing Direction
<label
>Invert Drawing Direction
<input
type="checkbox"
:checked="drawingDirection === 'x'"
:value="drawingDirection"
@input="updateDrawingDirection"
>
/>
</label>
</div>
</form>
</form>
</MenuRow>
</template>
<script>
import MenuRow from './MenuRow.vue'
import {mapGetters} from 'vuex'
import MenuRow from "./MenuRow.vue";
import { mapGetters } from "vuex";
export default {
name: 'MenuGeneralOptions',
name: "MenuGeneralOptions",
components: {
MenuRow
MenuRow,
},
computed: {
...mapGetters({
canvasWidth: 'getCanvasWidth',
canvasHeight: 'getCanvasHeight',
refreshRate: 'getRefreshRate',
drawingDirection: 'getDrawingDirection'
})
canvasWidth: "getCanvasWidth",
canvasHeight: "getCanvasHeight",
refreshRate: "getRefreshRate",
drawingDirection: "getDrawingDirection",
}),
},
methods: {
updateCanvasHeight: function(event) {
const elem = event.target
this.$store.commit('setCanvasHeight', elem.value)
updateCanvasHeight: function (event) {
const elem = event.target;
this.$store.commit("setCanvasHeight", elem.value);
},
updateCanvasWidth: function(event) {
const elem = event.target
this.$store.commit('setCanvasWidth', elem.value)
updateCanvasWidth: function (event) {
const elem = event.target;
this.$store.commit("setCanvasWidth", elem.value);
},
updateRefreshRate: function(event) {
const elem = event.target
this.$store.commit('setRefreshRate', elem.value)
updateRefreshRate: function (event) {
const elem = event.target;
this.$store.commit("setRefreshRate", elem.value);
},
updateDrawingDirection: function(event) {
const elem = event.target
const value = elem.checked ? "x" : "y"
this.$store.commit('setDrawingDirection', value)
console.log(this.drawingDirection)
}
}
}
updateDrawingDirection: function (event) {
const elem = event.target;
const value = elem.checked ? "x" : "y";
this.$store.commit("setDrawingDirection", value);
console.log(this.drawingDirection);
},
},
};
</script>

View File

@ -1,82 +1,75 @@
<template>
<div class="menu-row">
<h2
:id="rowTitle"
@click="updateActiveMenu"
>
<h2 :id="rowTitle" @click="updateActiveMenu">
{{ rowTitle }}
</h2>
<div
v-if="activeMenu === rowTitle"
class="menu-row-content"
>
<div v-if="activeMenu === rowTitle" class="menu-row-content">
<slot />
</div>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
import { mapGetters } from "vuex";
export default {
name: 'MenuRow',
name: "MenuRow",
props: {
rowTitle: {
type: String,
default : ''
}
default: "",
},
},
computed: {
...mapGetters({
activeMenu: 'getActiveMenu',
})
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)
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 {
font-size: medium;
padding: 10px;
cursor: pointer;
border: 2px solid darkgrey;
margin: 0 0 10px 0;
font-size: medium;
padding: 10px;
cursor: pointer;
border: 2px solid darkgrey;
margin: 0 0 10px 0;
}
select {
margin-top: 10px;
padding: 5px;
margin-top: 10px;
padding: 5px;
}
input[type="button"] {
min-width: 60px;
padding: 5px;
font-weight: bold;
margin-right: 10px;
min-width: 60px;
padding: 5px;
font-weight: bold;
margin-right: 10px;
}
.form-field {
display: flex;
margin: 10px;
justify-content: space-between;
display: flex;
margin: 10px;
justify-content: space-between;
}
.menu-row {
flex: 1;
flex: 1;
}
label, .form-field label {
margin-right: 10px;
font-weight: bold;
label,
.form-field label {
margin-right: 10px;
font-weight: bold;
}
</style>

View File

@ -1,10 +1,10 @@
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
Vue.config.productionTip = false
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App)
}).$mount('#app')
render: (h) => h(App),
}).$mount("#app");

View File

@ -1,7 +1,7 @@
// handles negative index and index bigger than its array length
function guard(index, array) {
if (index > (array.length - 1)) return 0;
if (index < 0) return (array.length - 1);
if (index > array.length - 1) return 0;
if (index < 0) return array.length - 1;
return index;
}
@ -12,11 +12,8 @@ function evolve1d(state, rules) {
return state[safeIndex];
}
const newState = state.map((_, x) => {
const cells = [
getCell(x - 1),
getCell(x),
getCell(x + 1)];
return rules[cells.join('')];
const cells = [getCell(x - 1), getCell(x), getCell(x + 1)];
return rules[cells.join("")];
});
return newState.map(Number);
@ -47,10 +44,14 @@ function getCellNeighbors(board, cellCoordinates) {
// 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),
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),
];
}
@ -75,58 +76,60 @@ function conwayRules(cell, neighbors) {
// get the next evolution of a 2D CA initial state
// Rules : Moore neighborhood
function evolve2d(board, rulesFn) {
return board.map((row, x) => row.map((cell, y) => {
const neighbors = getCellNeighbors(board, [x, y]);
const sum = getNeighborsSum(neighbors);
return rulesFn(cell, sum);
}));
return board.map((row, x) =>
row.map((cell, y) => {
const neighbors = getCellNeighbors(board, [x, y]);
const sum = getNeighborsSum(neighbors);
return rulesFn(cell, sum);
})
);
}
function getDrawingValues(state, acc, cell) {
const d = cell.dimension;
return Object.keys(state).map(
(key) => {
const fillStyle = (() => {
if (state[key] === '1') return cell.liveColor;
return cell.deadColor;
})();
return Object.keys(state).map((key) => {
const fillStyle = (() => {
if (state[key] === "1") return cell.liveColor;
return cell.deadColor;
})();
return {
move: [key * d, acc * d],
fill: [key * d, acc * d, d, d],
fillStyle,
};
},
);
return {
move: [key * d, acc * d],
fill: [key * d, acc * d, d, d],
fillStyle,
};
});
}
// Populates the first state with a single living cell in the center
function create1dStateOneCell(width) {
return [...Array(width)].map(
(cell, index) => {
if (index === width / 2 || index === width + 1 / 2) return 1
return 0
})
return [...Array(width)].map((cell, index) => {
if (index === width / 2 || index === width + 1 / 2) return 1;
return 0;
});
}
// Populates the first state of a 1D CA with cells returned
// by initFn
function create1dState(width, initFn, args) {
return [...Array(width)].map(
() => initFn(...args)
);
return [...Array(width)].map(() => initFn(...args));
}
// Populates the first state of a 2D CA with cells returned
// by initFn
function create2dState(width, height, initFn, args) {
return [...Array(height)].map(
() => [...Array(width)].map(
() => initFn(...args),
),
return [...Array(height)].map(() =>
[...Array(width)].map(() => initFn(...args))
);
}
export {
getDrawingValues, create1dState, create2dState, createBoard, create1dStateOneCell, conwayRules, evolve1d, evolve2d
getDrawingValues,
create1dState,
create2dState,
createBoard,
create1dStateOneCell,
conwayRules,
evolve1d,
evolve2d,
};

View File

@ -1,146 +0,0 @@
import { getRandomInt, sleep } from './common.js';
import {
evolve2d, initialState1d, initialState2d, conwayRules, createBoard,
} from './automata.js';
let drawing = 1;
const form = Array.from(document.forms.rules.elements);
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
const main = document.querySelector('#main');
const dead = document.querySelector('#dead');
const live = document.querySelector('#live');
const cellSize = document.querySelector('#cellSize');
const startBtn = document.querySelector('#start');
const startBtn2d = document.querySelector('#start2d');
const canvasRefreshBtn = document.querySelector('#canvasRefresh');
const resetBtn = document.querySelectorAll('.reset');
const stopBtn = document.querySelectorAll('.stop');
// const loop = document.querySelector('#loop');
const menuRow = document.querySelectorAll('.menu-row');
const menuRowContent = document.querySelectorAll('.menu-row-content');
function drawCanvas(board, props) {
board.map((row, y) => {
const d = props.size;
return row.map(
(cell, x) => {
ctx.fillStyle = (
() => {
if (cell === 1) return props.liveColor;
return props.deadColor;
})();
ctx.fillRect(x * d, y * d, d, d);
return cell;
},
);
});
}
function getRules() {
const rules = {};
form.reduce((_, i) => {
if (i !== undefined
&& i.type === 'checkbox') {
if (i.checked) rules[i.name] = '1';
else rules[i.name] = '0';
}
return rules;
}, rules);
return rules;
}
function getCellProperties() {
return {
size: cellSize.value,
liveColor: live.value,
deadColor: dead.value,
};
}
function reset() {
drawing = 0;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function resizeCanvas() {
canvas.width = main.offsetWidth * 0.9;
canvas.height = main.offsetHeight * 0.9;
}
async function draw1d() {
const rules = getRules();
const props = getCellProperties();
const initialState = initialState1d(
Math.floor(canvas.width / props.size),
getRandomInt,
[0, 2],
);
const board = createBoard(initialState, rules, Math.floor(canvas.height / props.size));
drawCanvas(board, props);
}
async function draw2d() {
const props = getCellProperties();
const initialState = initialState2d(
Math.floor(canvas.width / props.size),
Math.floor(canvas.height / props.size),
getRandomInt,
[0, 2],
);
const board = evolve2d(initialState, conwayRules);
async function draw2dNext(b) {
if (drawing === 0) return;
const newBoard = evolve2d(b, conwayRules);
drawCanvas(b, props);
await sleep(300);
draw2dNext(newBoard);
}
return draw2dNext(board);
}
// Listeners
startBtn.addEventListener('click', async () => {
reset();
await sleep(60);
drawing = 1;
draw1d();
});
startBtn2d.addEventListener('click', async () => {
reset();
await sleep(60);
drawing = 1;
draw2d();
});
resetBtn.forEach((elem) => {
elem.addEventListener('click', async () => {
reset();
});
});
stopBtn.forEach((elem) => {
elem.addEventListener('click', async () => {
drawing = 0;
});
});
canvasRefreshBtn.addEventListener('click', () => {
const width = document.querySelector('#canvasWidth').value;
const height = document.querySelector('#canvasWidth').value;
canvas.width = width;
canvas.height = height;
});

View File

@ -1,25 +1,25 @@
import Vue from 'vue'
import Vuex from 'vuex'
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex)
Vue.use(Vuex);
export default new Vuex.Store({
state: {
drawing: 0,
rules1d : {
"111" : 0,
"110" : 1,
"101" : 0,
"100" : 0,
"011" : 1,
"010" : 0,
"001" : 0,
"000" : 1
rules1d: {
111: 0,
110: 1,
101: 0,
100: 0,
"011": 1,
"010": 0,
"001": 0,
"000": 1,
},
cellProperties: {
size: 3,
liveColor: '#000000',
deadColor: '#F5F5F5',
liveColor: "#000000",
deadColor: "#F5F5F5",
},
canvasWidth: 0,
canvasHeight: 0,
@ -27,80 +27,78 @@ export default new Vuex.Store({
initial1dState: "onecell",
activeMenu: "",
drawingDirection: "y",
lastBoard: {}
lastBoard: {},
},
mutations: {
update1dSingleRule(state, data) {
state.rules1d[data.rule] = data.value
state.rules1d[data.rule] = data.value;
},
update1dRules(state, data) {
state.rules1d = data
state.rules1d = data;
},
setCellProperties(state, data) {
state.cellProperties[data.name] = data.value
state.cellProperties[data.name] = data.value;
},
setDrawingStatus(state, data) {
state.drawing = data
state.drawing = data;
},
setCanvasWidth(state, data) {
state.canvasWidth = data
state.canvasWidth = data;
},
setCanvasHeight(state, data) {
state.canvasHeight = data
state.canvasHeight = data;
},
setRefreshRate(state, data) {
state.refreshRate = data
state.refreshRate = data;
},
setInitial1dState(state, data) {
state.initial1dState = data
state.initial1dState = data;
},
setActiveMenu(state, data) {
state.activeMenu = data
state.activeMenu = data;
},
setDrawingDirection(state, data) {
state.drawingDirection = data
state.drawingDirection = data;
},
setLastBoard(state, data) {
state.lastBoard = data
state.lastBoard = data;
},
},
getters: {
getCellProperties(state) {
return state.cellProperties
return state.cellProperties;
},
get1dRules(state) {
return state.rules1d
return state.rules1d;
},
getRule1d(state) {
return (rule) => state.rules1d[rule]
return (rule) => state.rules1d[rule];
},
isDrawing(state) {
return state.drawing
return state.drawing;
},
getCanvasWidth(state) {
return state.canvasWidth
return state.canvasWidth;
},
getCanvasHeight(state) {
return state.canvasHeight
return state.canvasHeight;
},
getRefreshRate(state) {
return state.refreshRate
return state.refreshRate;
},
getInitial1dState(state) {
return state.initial1dState
return state.initial1dState;
},
getActiveMenu(state) {
return state.activeMenu
return state.activeMenu;
},
getDrawingDirection(state) {
return state.drawingDirection
return state.drawingDirection;
},
getLastBoard(state) {
return state.lastBoard
}
return state.lastBoard;
},
},
actions: {
},
modules: {
}
})
actions: {},
modules: {},
});

View File

@ -1,16 +1,16 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
const path = require("path");
export default defineConfig({
plugins: [vue()],
server: {
host: '127.0.0.1'
host: "127.0.0.1",
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})
});

View File

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