234 lines
6.2 KiB
Vue
234 lines
6.2 KiB
Vue
<script setup>
|
|
import { onMounted, watch } from "vue";
|
|
import { globalStore } from "../stores/index.js";
|
|
import {
|
|
create1dState,
|
|
create1dStateOneCell,
|
|
create2dState,
|
|
createBoard,
|
|
conwayRules,
|
|
overpopulationRules,
|
|
lonelinessRules,
|
|
threebornRules,
|
|
highLifeRules,
|
|
servietteRules,
|
|
evolve2d,
|
|
} from "../modules/automata.js";
|
|
import { getRandomInt } from "../modules/common.js";
|
|
import { boardToPic, picToBoard } from "../modules/picture.js";
|
|
|
|
const store = globalStore();
|
|
|
|
// TODO: Do we really need to declare a work canvas in this scope?
|
|
// Do we really need to declare a canvas here at all?
|
|
let canvas = null;
|
|
let workCanvas = null;
|
|
let workCtx = null;
|
|
let ctx = null;
|
|
const available2dRules = {
|
|
conway: conwayRules,
|
|
overpopulation: overpopulationRules,
|
|
loneliness: lonelinessRules,
|
|
threeborn: threebornRules,
|
|
highlife: highLifeRules,
|
|
serviette: servietteRules,
|
|
};
|
|
|
|
// used to determine the dimensions of the board
|
|
const max = () => {
|
|
return Math.max(store.boardWidth, store.boardHeight);
|
|
};
|
|
|
|
const selectedRules = () => {
|
|
return available2dRules[store.selected2dRules.id];
|
|
};
|
|
|
|
watch(
|
|
() => store.draw1d,
|
|
(value) => {
|
|
if (value == true) draw1d();
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => store.draw2d,
|
|
(value) => {
|
|
if (value == true) draw2dNew();
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => store.draw2dLast,
|
|
async (value) => {
|
|
if (value == true) await draw2dLast();
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => store.draw2dpicture,
|
|
(value) => {
|
|
if (value == true) draw2dPicture();
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => store.reset,
|
|
(value) => {
|
|
if (value == true) reset();
|
|
}
|
|
);
|
|
|
|
onMounted(() => {
|
|
canvas = Object.freeze(document.getElementById("board-canvas"));
|
|
workCanvas = Object.freeze(document.getElementById("work-canvas"));
|
|
ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
workCtx = workCanvas.getContext("2d", {
|
|
willReadFrequently: true,
|
|
});
|
|
store.canvasWidth = canvas.parentElement.clientWidth;
|
|
store.canvasHeight = canvas.parentElement.clientHeight;
|
|
store.setBoardWidth();
|
|
store.setBoardHeight();
|
|
});
|
|
|
|
// draws the board on the canvas
|
|
const drawCanvas = async (board, width, height) => {
|
|
const d = store.cellProperties.size;
|
|
// bool to RGBA colors
|
|
const img = await boardToPic(board, width, height, store.cellProperties);
|
|
// rescale and draw
|
|
ctx.save();
|
|
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
|
|
workCtx.putImageData(img, 0, 0);
|
|
ctx.imageSmoothingEnabled = false;
|
|
ctx.scale(d, d);
|
|
ctx.drawImage(workCanvas, 0, 0, store.canvasWidth, store.canvasHeight);
|
|
ctx.restore();
|
|
};
|
|
|
|
// create a first state, either a single living cell
|
|
// at the center or random ones
|
|
const compute1dInitialState = () => {
|
|
if (store.initial1dState === "onecell")
|
|
return create1dStateOneCell(store.boardWidth);
|
|
return create1dState(store.boardWidth, getRandomInt, [0, 2]);
|
|
};
|
|
|
|
// initialize board with random cells
|
|
const randomInitialState = () => {
|
|
return create2dState(
|
|
store.boardWidth,
|
|
store.boardHeight,
|
|
getRandomInt,
|
|
[0, 2]
|
|
);
|
|
};
|
|
|
|
// draw elementary automaton on the canvas based on selected ruleset
|
|
const draw1d = () => {
|
|
const initialState = compute1dInitialState();
|
|
const board = createBoard(initialState, store.ruleset1d.rules, max());
|
|
store.lastBoard = Object.freeze(board);
|
|
// TODO: the board clearly could be an object
|
|
drawCanvas(store.lastBoard, store.boardWidth, store.boardHeight);
|
|
store.toggleStop();
|
|
};
|
|
|
|
// draw 2D automaton on the canvas in a loop
|
|
const draw2d = (board) => {
|
|
const newBoard = evolve2d(board, selectedRules());
|
|
drawCanvas(newBoard, store.boardWidth, store.boardHeight);
|
|
store.lastBoard = Object.freeze(newBoard);
|
|
};
|
|
|
|
// draw 2d automaton in a loop, starting from passed state
|
|
const draw2dNext = async (board) => {
|
|
setTimeout(() => {
|
|
requestAnimationFrame(() => {
|
|
if (!store.canDraw) return;
|
|
draw2d(board);
|
|
return draw2dNext(store.lastBoard);
|
|
});
|
|
}, store.refreshRate);
|
|
};
|
|
|
|
// draw 2d automaton from a new state
|
|
const draw2dNew = async () => {
|
|
if (!store.canDraw) return;
|
|
const initialState = randomInitialState();
|
|
const board = evolve2d(initialState, selectedRules());
|
|
if (store.loop) return draw2dNext(board);
|
|
else draw2d(board);
|
|
store.toggleStop();
|
|
};
|
|
|
|
// draw 2d automaton from the last known generated board
|
|
const draw2dLast = async () => {
|
|
if (!store.canDraw) return;
|
|
if (store.loop) return store.draw2dNext(store.lastBoard);
|
|
else draw2d(store.lastBoard);
|
|
store.toggleStop();
|
|
};
|
|
|
|
// draw 2d automaton from an uploaded picture.
|
|
// use the picture representation as an initial state
|
|
const draw2dPicture = () => {
|
|
// draw image on canvas
|
|
ctx.fillStyle = "black";
|
|
ctx.fillRect(0, 0, store.canvasWidth, store.canvasHeight);
|
|
ctx.drawImage(
|
|
store.picture,
|
|
Math.floor((store.canvasWidth - store.picture.width) / 2),
|
|
Math.floor((store.canvasHeight - store.picture.height) / 2),
|
|
store.picture.width,
|
|
store.picture.height
|
|
);
|
|
|
|
// draw the image back on the work canvas with the dimensions of the board
|
|
workCtx.drawImage(store.picture, 0, 0, store.boardWidth, store.boardHeight);
|
|
|
|
// get the resized image data from work canvas
|
|
const resized = workCtx.getImageData(
|
|
0,
|
|
0,
|
|
store.boardWidth,
|
|
store.boardHeight
|
|
);
|
|
|
|
// convert the image into a 2D board of boolean based on pixel value
|
|
store.lastBoard = Object.freeze(
|
|
picToBoard(resized.data, store.boardWidth, store.boardHeight)
|
|
);
|
|
|
|
store.toggleStop();
|
|
};
|
|
|
|
// stop drawing routines and clear the canvas
|
|
const reset = () => {
|
|
store.toggleStop();
|
|
store.lastBoard = null;
|
|
ctx.clearRect(0, 0, store.canvasWidth, store.canvasHeight);
|
|
store.reset = false;
|
|
};
|
|
</script>
|
|
<template>
|
|
<canvas
|
|
id="board-canvas"
|
|
ref="board-canvas"
|
|
:width="store.canvasWidth"
|
|
:height="store.canvasHeight"
|
|
/>
|
|
<canvas
|
|
id="work-canvas"
|
|
ref="work-canvas"
|
|
:width="store.canvasWidth"
|
|
:height="store.canvasHeight"
|
|
/>
|
|
</template>
|
|
<style>
|
|
#canvas-board {
|
|
flex: 1;
|
|
margin: 0 auto;
|
|
}
|
|
</style>
|