Merge pull request 'vue 2 to 3' (#1) from dev into master
Reviewed-on: #1
This commit is contained in:
commit
f9354d0a17
23
.eslintrc.js
23
.eslintrc.js
@ -1,12 +1,25 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true, // remove
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
// add more generic rulesets here, such as:
|
||||
'eslint:recommended',
|
||||
//'plugin:vue/vue3-recommended',
|
||||
'plugin:vue/recommended', // Use this if you are using Vue.js 2.x.
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-essential",
|
||||
"plugin:vue/vue3-strongly-recommended",
|
||||
"plugin:vue/vue3-recommended",
|
||||
"prettier",
|
||||
],
|
||||
rules: {
|
||||
// override/add rules settings here, such as:
|
||||
// 'vue/no-unused-vars': 'error'
|
||||
}
|
||||
}
|
||||
"vue/match-component-import-name": "warn",
|
||||
"vue/no-ref-object-destructure": "warn",
|
||||
"vue/no-required-prop-with-default": "warn",
|
||||
"vue/no-restricted-class": "warn",
|
||||
"vue/no-template-target-blank": "warn",
|
||||
"vue/no-this-in-before-route-enter": "warn",
|
||||
"vue/prefer-prop-type-boolean-first": "warn",
|
||||
},
|
||||
};
|
||||
|
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM node:lts-alpine
|
||||
|
||||
# install simple http server for serving static content
|
||||
RUN npm install -g http-server
|
||||
|
||||
# make the 'app' folder the current working directory
|
||||
WORKDIR /app
|
||||
|
||||
# copy both 'package.json' and 'package-lock.json' (if available)
|
||||
COPY package*.json ./
|
||||
# build stage
|
||||
FROM node:lts-alpine as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# production stage
|
||||
FROM nginx:stable-alpine as production-stage
|
||||
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
@ -1,29 +1,38 @@
|
||||
# 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
|
||||
- https://www.conwaylife.com/wiki/Cellular_automaton
|
||||
|
@ -1,5 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
}
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
||||
|
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@ -0,0 +1,15 @@
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
explorata:
|
||||
container_name: explorata
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
networks:
|
||||
- ariona
|
||||
|
||||
networks:
|
||||
ariona:
|
||||
external: true
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
21
index.html
Normal file
21
index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!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" />
|
||||
<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.
|
||||
</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
32325
package-lock.json
generated
32325
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -3,26 +3,24 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli": "^4.5.15",
|
||||
"@vue/cli-service-global": "^4.5.15",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vuex": "^3.6.2"
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"install": "^0.13.0",
|
||||
"vite": "^3.2.4",
|
||||
"vue": "3.2",
|
||||
"vuex": "4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"prettier": "2.8.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
@ -1,17 +0,0 @@
|
||||
<!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="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
66
src/App.vue
66
src/App.vue
@ -1,72 +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;
|
||||
margin-top: 60px;
|
||||
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;
|
||||
}
|
||||
|
||||
#main {
|
||||
flex: 4;
|
||||
margin: 10px auto;
|
||||
font-size: larger;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
display: flex;
|
||||
height: calc(100vh - 100px);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
0
src/assets/main.css
Normal file
0
src/assets/main.css
Normal file
@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<main id="main">
|
||||
<canvas
|
||||
id="canvas"
|
||||
ref="canvas"
|
||||
:width="canvasWidth"
|
||||
:height="canvasHeight"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
<script>
|
||||
import { evolve2d, initialState1d, initialState2d, conwayRules, createBoard } 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: 'getRuleSet1d',
|
||||
drawing: 'isDrawing',
|
||||
canvasWidth: 'getCanvasWidth',
|
||||
canvasHeight: 'getCanvasHeight'
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
this.canvas = this.$refs['canvas']
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
this.$root.$on('draw1d', () => { this.draw1d() })
|
||||
this.$root.$on('draw2d', () => { this.draw2d() })
|
||||
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
|
||||
})()
|
||||
this.ctx.fillRect(x * d, y * d, d, d)
|
||||
return cell
|
||||
},
|
||||
)
|
||||
})
|
||||
},
|
||||
async draw1d() {
|
||||
const initialState = initialState1d(
|
||||
Math.floor(this.canvas.width / this.cellProperties.size),
|
||||
getRandomInt,
|
||||
[0, 2],
|
||||
)
|
||||
const board = createBoard(
|
||||
initialState,
|
||||
this.rules,
|
||||
Math.floor(this.canvas.height / this.cellProperties.size)
|
||||
)
|
||||
this.drawCanvas(board)
|
||||
},
|
||||
async draw2d() {
|
||||
if (this.drawing === 0) return
|
||||
const initialState = initialState2d(
|
||||
Math.floor(this.canvas.width / this.cellProperties.size),
|
||||
Math.floor(this.canvas.height / this.cellProperties.size),
|
||||
getRandomInt,
|
||||
[0, 2],
|
||||
);
|
||||
const board = evolve2d(initialState, conwayRules);
|
||||
|
||||
const draw2dNext = async (b) => {
|
||||
if (this.drawing === 0) return
|
||||
const newBoard = evolve2d(b, conwayRules)
|
||||
this.drawCanvas(b, this.cellProperties)
|
||||
await sleep(300)
|
||||
draw2dNext(newBoard)
|
||||
}
|
||||
return draw2dNext(board)
|
||||
},
|
||||
stop() {
|
||||
this.$store.commit('setDrawingStatus', 0)
|
||||
},
|
||||
reset() {
|
||||
this.stop()
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
146
src/components/CanvasBoard.vue
Normal file
146
src/components/CanvasBoard.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<main id="mainContent">
|
||||
<canvas
|
||||
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",
|
||||
canvasWidth: "getCanvasWidth",
|
||||
canvasHeight: "getCanvasHeight",
|
||||
refreshRate: "getRefreshRate",
|
||||
initial1dState: "getInitial1dState",
|
||||
drawingDirection: "getDrawingDirection",
|
||||
canDraw: "getCanDraw",
|
||||
lastBoard: "getLastBoard",
|
||||
getDraw1d: "getDraw1d",
|
||||
getDraw2d: "getDraw2d",
|
||||
getDraw2dLast: "getDraw2dLast",
|
||||
getReset: "getReset",
|
||||
}),
|
||||
boardWidth: function () {
|
||||
return Math.floor(this.canvasWidth / this.cellProperties.size);
|
||||
},
|
||||
boardHeight: function () {
|
||||
return Math.floor(this.canvasHeight / this.cellProperties.size);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
getDraw1d(value) {
|
||||
if (value == true) this.draw1d();
|
||||
},
|
||||
getDraw2d(value) {
|
||||
if (value == true) this.draw2dNew();
|
||||
},
|
||||
getDraw2dLast(value) {
|
||||
if (value == true) this.draw2dLast();
|
||||
},
|
||||
getReset(value) {
|
||||
if (value == true) this.reset();
|
||||
},
|
||||
},
|
||||
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
|
||||
);
|
||||
},
|
||||
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]);
|
||||
},
|
||||
draw1d() {
|
||||
const initialState = this.compute1dInitialState();
|
||||
const board = createBoard(
|
||||
initialState,
|
||||
this.rules.rules,
|
||||
this.boardWidth
|
||||
);
|
||||
this.$store.commit("setLastBoard", Object.freeze(board));
|
||||
this.drawCanvas(board);
|
||||
this.$store.dispatch("stop");
|
||||
},
|
||||
draw2d(board) {
|
||||
if (!this.canDraw) return;
|
||||
const draw2dNext = async (b) => {
|
||||
if (!this.canDraw) return;
|
||||
const newBoard = evolve2d(b, conwayRules);
|
||||
this.drawCanvas(b, this.cellProperties);
|
||||
await sleep(this.refreshRate);
|
||||
draw2dNext(newBoard);
|
||||
};
|
||||
return draw2dNext(board);
|
||||
},
|
||||
draw2dNew() {
|
||||
const initialState = create2dState(
|
||||
this.boardWidth,
|
||||
this.boardHeight,
|
||||
getRandomInt,
|
||||
[0, 2]
|
||||
);
|
||||
const board = evolve2d(initialState, conwayRules);
|
||||
this.$store.commit("setLastBoard", Object.freeze(board));
|
||||
this.draw2d(board);
|
||||
},
|
||||
async draw2dLast() {
|
||||
this.draw2d(this.lastBoard);
|
||||
},
|
||||
reset() {
|
||||
this.$store.dispatch("stop");
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.$store.commit("toggleReset", 0);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
#mainContent {
|
||||
min-width: 70%;
|
||||
}
|
||||
</style>
|
@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<div class="form-field" v-if="type === 'checkbox'">
|
||||
<label>{{ name }}
|
||||
<input type="checkbox" name="{{ name }}">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-field" v-else-if="type === ''">
|
||||
</div>
|
||||
</template>
|
@ -8,25 +8,55 @@
|
||||
</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 {
|
||||
flex-basis: fit-content;
|
||||
padding: 0 10px;
|
||||
width: fit-content;
|
||||
width: 25%;
|
||||
padding: 0 10px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
#sidebar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
#sidebar {
|
||||
-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;
|
||||
}
|
||||
|
||||
#mainContent {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,48 +1,56 @@
|
||||
<template>
|
||||
<MenuRow row-title="2D Cellular Automata">
|
||||
<div class="form-field">
|
||||
<input
|
||||
type="button"
|
||||
name="start2d"
|
||||
value="start"
|
||||
@click="draw2d"
|
||||
>
|
||||
<label>Start from last result</label>
|
||||
<input type="button" value="start" @click="draw2dLast" />
|
||||
</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"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</MenuRow>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MenuRow from './MenuRow.vue'
|
||||
import MenuRow from "./MenuRow.vue";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'Menu2dCA',
|
||||
name: "Menu2dCA",
|
||||
components: {
|
||||
MenuRow
|
||||
MenuRow,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
lastBoard: "getLastBoard",
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
draw2d() {
|
||||
this.$root.$store.state.drawing = 1
|
||||
this.$root.$emit('draw2d')
|
||||
this.$store.dispatch("draw2d");
|
||||
},
|
||||
draw2dLast() {
|
||||
this.$store.dispatch("draw2dLast");
|
||||
},
|
||||
reset() {
|
||||
this.$root.$emit('reset')
|
||||
this.$store.dispatch("reset");
|
||||
},
|
||||
stop() {
|
||||
this.$root.$emit('stop')
|
||||
this.$store.dispatch("stop");
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -8,7 +8,7 @@
|
||||
type="color"
|
||||
@value="cellProperties.liveColor"
|
||||
@input="updateCellProperties"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="dead">Dead cell color</label>
|
||||
@ -17,45 +17,46 @@
|
||||
type="color"
|
||||
:value="cellProperties.deadColor"
|
||||
@input="updateCellProperties"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Cell size</label>
|
||||
<input
|
||||
name="size"
|
||||
type="number"
|
||||
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>
|
||||
|
@ -1,79 +1,245 @@
|
||||
<template>
|
||||
<MenuRow row-title="Elementary Cellular Automata">
|
||||
<div class="form-field">
|
||||
<label>Rules</label>
|
||||
</div>
|
||||
<form>
|
||||
<div class="form-field">
|
||||
<label>
|
||||
Initial state presets
|
||||
<br />
|
||||
<select
|
||||
name="initialStates"
|
||||
:value="initialState"
|
||||
@input="updateInitialState"
|
||||
>
|
||||
<option
|
||||
v-for="(state, index) in initialStates"
|
||||
:key="'initial-state-elementary' + index"
|
||||
:value="state.id"
|
||||
>
|
||||
{{ state.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Rules</label>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label
|
||||
>Rules presets
|
||||
<br />
|
||||
<select
|
||||
name="ruleset-elementary"
|
||||
:value="rules.name"
|
||||
@input="updateRules"
|
||||
>
|
||||
<option
|
||||
v-for="(ruleset, index) in presetRules"
|
||||
:key="'ruleset-preset-elementary-' + index"
|
||||
:value="ruleset.name"
|
||||
>
|
||||
{{ ruleset.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<a style="cursor: pointer" @click="copyRules">copy rules</a>
|
||||
</div>
|
||||
<div
|
||||
v-for="rule in rules"
|
||||
:key="rule"
|
||||
v-for="(rule, name, index) in rules.rules"
|
||||
:key="'rule-' + index"
|
||||
class="form-field"
|
||||
>
|
||||
<label>{{ rule }}
|
||||
<label
|
||||
>{{ name }}
|
||||
<input
|
||||
:value="getRule(rule)"
|
||||
:value="rule"
|
||||
type="checkbox"
|
||||
:name="rule"
|
||||
:checked="getRule(rule) == 1"
|
||||
@input="update1dRules"
|
||||
>
|
||||
:name="name"
|
||||
:checked="rule"
|
||||
@input="updateSingleRule"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-field">
|
||||
<input
|
||||
type="button"
|
||||
name="start"
|
||||
value="start"
|
||||
@click="draw1d"
|
||||
>
|
||||
<!-- <input -->
|
||||
<!-- type="button" -->
|
||||
<!-- name="stop" -->
|
||||
<!-- class="stop" -->
|
||||
<!-- value="stop" -->
|
||||
<!-- > -->
|
||||
<input type="button" name="start" value="start" @click="draw1d" />
|
||||
<input
|
||||
type="button"
|
||||
name="reset"
|
||||
class="reset"
|
||||
value="reset"
|
||||
@click="reset()"
|
||||
>
|
||||
@click="reset"
|
||||
/>
|
||||
</div>
|
||||
</MenuRow>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 {
|
||||
rules: ["111", "110", "101", "100", "011", "010", "001", "000"]
|
||||
}
|
||||
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"
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
update1dRules(event) {
|
||||
const elem = event.target
|
||||
const value = elem.checked ? 1 : 0
|
||||
const data = { 'rule' : elem.name, 'value' : value}
|
||||
this.$store.commit('update1dRules', data)
|
||||
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) => {
|
||||
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.$root.$store.state.drawing = 1
|
||||
this.$root.$emit('draw1d')
|
||||
},
|
||||
getRule(id) {
|
||||
const rules = this.$store.state.rules1d
|
||||
return rules[id]
|
||||
this.$store.dispatch("draw1d");
|
||||
},
|
||||
reset() {
|
||||
this.$root.$emit('reset')
|
||||
this.$store.dispatch("reset");
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.menu-row a {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
font-size: small;
|
||||
}
|
||||
</style>
|
||||
|
@ -2,53 +2,94 @@
|
||||
<MenuRow row-title="General Options">
|
||||
<form>
|
||||
<div class="form-field">
|
||||
<label for="live">Canvas Resolution</label>
|
||||
<label>Canvas Resolution</label>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Width</label>
|
||||
<input
|
||||
id="canvasWidth"
|
||||
name="canvasWidth"
|
||||
type="number"
|
||||
step="10"
|
||||
:value="canvasWidth"
|
||||
@input="updateCanvasWidth"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Height</label>
|
||||
<input
|
||||
id="canvasHeight"
|
||||
name="canvasHeight"
|
||||
type="number"
|
||||
step="10"
|
||||
:value="canvasHeight"
|
||||
@input="updateCanvasHeight"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Refresh Rate (ms)</label>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<input
|
||||
id="refreshRate"
|
||||
name="refreshRate"
|
||||
type="number"
|
||||
min="100"
|
||||
step="100"
|
||||
:value="refreshRate"
|
||||
@input="updateRefreshRate"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label
|
||||
>Invert Drawing Direction
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="drawingDirection === 'x'"
|
||||
:value="drawingDirection"
|
||||
@input="updateDrawingDirection"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</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'
|
||||
})
|
||||
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);
|
||||
},
|
||||
updateDrawingDirection: function (event) {
|
||||
const elem = event.target;
|
||||
const value = elem.checked ? "x" : "y";
|
||||
this.$store.commit("setDrawingDirection", value);
|
||||
console.log(this.drawingDirection);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,63 +1,75 @@
|
||||
<template>
|
||||
<div class="menu-row">
|
||||
<h2
|
||||
@click="isHidden = !isHidden"
|
||||
>
|
||||
<h2 :id="rowTitle" @click="updateActiveMenu">
|
||||
{{ rowTitle }}
|
||||
</h2>
|
||||
<div
|
||||
v-if="!isHidden"
|
||||
class="menu-row-content"
|
||||
>
|
||||
<div v-if="activeMenu === rowTitle" class="menu-row-content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'MenuRow',
|
||||
name: "MenuRow",
|
||||
props: {
|
||||
rowTitle: {
|
||||
type: String,
|
||||
default : ''
|
||||
}
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isHidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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>
|
||||
|
15
src/main.js
15
src/main.js
@ -1,10 +1,9 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import { store } from "./store";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
const app = createApp(App);
|
||||
|
||||
new Vue({
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
app.use(store);
|
||||
|
||||
app.mount("#app");
|
||||
|
@ -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,25 +12,39 @@ 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);
|
||||
}
|
||||
|
||||
// create a 2D board from a 1D CA initial state
|
||||
// function createBoard(state, rules, height) {
|
||||
// function createBoardAcc(s, h, acc) {
|
||||
// if (h === 0) return acc;
|
||||
// const newState = evolve1d(s, rules);
|
||||
// const newAcc = acc.concat([s]);
|
||||
// return createBoardAcc(newState, h - 1, newAcc);
|
||||
// }
|
||||
// return createBoardAcc(state, height, []);
|
||||
// }
|
||||
|
||||
// performance "choke point" in full imperative
|
||||
function createBoard(state, rules, height) {
|
||||
function createBoardAcc(s, h, acc) {
|
||||
if (h === 0) return acc;
|
||||
const newState = evolve1d(s, rules);
|
||||
const newAcc = acc.concat([s]);
|
||||
return createBoardAcc(newState, h - 1, newAcc);
|
||||
var board = [];
|
||||
let prevState = [];
|
||||
for (let i = 0; i < height; i++) {
|
||||
let nextState = [];
|
||||
if (i == 0) {
|
||||
nextState = evolve1d(state, rules);
|
||||
} else {
|
||||
nextState = evolve1d(prevState, rules);
|
||||
}
|
||||
board = board.concat([nextState]);
|
||||
prevState = nextState;
|
||||
}
|
||||
return createBoardAcc(state, height, []);
|
||||
return board;
|
||||
}
|
||||
|
||||
// Find the neighbor of a given cell in a 2D CA board
|
||||
@ -47,10 +61,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),
|
||||
];
|
||||
}
|
||||
|
||||
@ -73,50 +91,62 @@ 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;
|
||||
});
|
||||
}
|
||||
|
||||
// Populates the first state of a 1D CA with cells returned
|
||||
// by initFn
|
||||
function initialState1d(width, initFn, args) {
|
||||
return [...Array(width)].map(
|
||||
() => initFn(...args),
|
||||
);
|
||||
function create1dState(width, initFn, args) {
|
||||
return [...Array(width)].map(() => initFn(...args));
|
||||
}
|
||||
|
||||
// Populates the first state of a 2D CA with cells returned
|
||||
// by initFn
|
||||
function initialState2d(width, height, initFn, args) {
|
||||
return [...Array(height)].map(
|
||||
() => [...Array(width)].map(
|
||||
() => initFn(...args),
|
||||
),
|
||||
function create2dState(width, height, initFn, args) {
|
||||
return [...Array(height)].map(() =>
|
||||
[...Array(width)].map(() => initFn(...args))
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
getDrawingValues, initialState1d, initialState2d, evolve1d, evolve2d, conwayRules, createBoard,
|
||||
getDrawingValues,
|
||||
create1dState,
|
||||
create2dState,
|
||||
createBoard,
|
||||
create1dStateOneCell,
|
||||
conwayRules,
|
||||
evolve1d,
|
||||
evolve2d,
|
||||
};
|
||||
|
@ -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;
|
||||
});
|
@ -1,68 +1,174 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
/* 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
|
||||
|
||||
Vue.use(Vuex)
|
||||
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 default new Vuex.Store({
|
||||
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
|
||||
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: '#AA78E8',
|
||||
liveColor: "#000000",
|
||||
deadColor: "#F5F5F5",
|
||||
},
|
||||
canvasWidth: 1280,
|
||||
canvasHeight: 720,
|
||||
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.rule] = data.value
|
||||
state.rules1d = data;
|
||||
},
|
||||
setCellProperties(state, data) {
|
||||
state.cellProperties[data.name] = data.value
|
||||
},
|
||||
setDrawingStatus(state, data) {
|
||||
state.drawing = 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
|
||||
return state.cellProperties;
|
||||
},
|
||||
getRuleSet1d(state) {
|
||||
return state.rules1d
|
||||
get1dRules(state) {
|
||||
return state.rules1d;
|
||||
},
|
||||
getRule1d(state) {
|
||||
return (rule) => state.rules1d[rule]
|
||||
},
|
||||
isDrawing(state) {
|
||||
return state.drawing
|
||||
return state.rules1d;
|
||||
},
|
||||
getCanvasWidth(state) {
|
||||
return state.canvasWidth
|
||||
return state.canvasWidth;
|
||||
},
|
||||
getCanvasHeight(state) {
|
||||
return state.canvasHeight
|
||||
}
|
||||
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: {
|
||||
}
|
||||
})
|
||||
modules: {},
|
||||
});
|
||||
|
16
vite.config.js
Normal file
16
vite.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
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",
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
});
|
@ -3,11 +3,11 @@ module.exports = {
|
||||
devServer: {
|
||||
overlay: {
|
||||
warnings: true,
|
||||
errors: true
|
||||
errors: true,
|
||||
},
|
||||
watchOptions: {
|
||||
ignored: [/node_modules/, /public/, /\.#/],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user