explorata/src/components/CanvasBoard.vue

247 lines
7.6 KiB
Vue

<template>
<canvas
id="board-canvas"
ref="board-canvas"
:width="canvasWidth"
:height="canvasHeight"
/>
<canvas
id="work-canvas"
ref="work-canvas"
:width="canvasWidth"
:height="canvasHeight"
/>
</template>
<script>
import { mapActions, mapState, mapWritableState } from "pinia";
import { globalStore } from "../stores/index.js";
import {
create1dState,
create1dStateOneCell,
create2dState,
createBoard,
conwayRules,
overpopulationRules,
lonelinessRules,
threebornRules,
highLifeRules,
servietteRules,
evolve2d,
} from "../modules/automata.js";
import { getRandomInt, sleep } from "../modules/common.js";
import { picToBoard, picToBlackAndWhite } from "../modules/picture.js";
export default {
name: "CanvasBoard",
data() {
return {
canvas: null,
workCanvas: null,
workCtx: null,
ctx: null,
available2dRules: {
conway: conwayRules,
overpopulation: overpopulationRules,
loneliness: lonelinessRules,
threeborn: threebornRules,
highlife: highLifeRules,
serviette: servietteRules,
},
};
},
computed: {
...mapState(globalStore, {
loop: "loop",
cellProperties: "cellProperties",
ruleset: "ruleset1d",
refreshRate: "refreshRate",
initial1dState: "initial1dState",
drawingDirection: "drawingDirection",
canDraw: "canDraw",
getDraw1d: "draw1d",
getDraw2d: "draw2d",
getDraw2dLast: "draw2dLast",
getDraw2dPicture: "draw2dpicture",
boardWidth: "boardWidth",
boardHeight: "boardHeight",
selected2dRules: "selected2dRules",
}),
...mapWritableState(globalStore, {
lastBoard: "lastBoard",
canvasWidth: "canvasWidth",
canvasHeight: "canvasHeight",
getReset: "reset",
picture: "picture",
}),
// used to determine the dimensions of the board
max() {
return Math.max(this.boardWidth, this.boardHeight);
},
selectedRules() {
return this.available2dRules[this.selected2dRules.id];
},
},
watch: {
getDraw1d(value) {
if (value == true) this.draw1d();
},
getDraw2d(value) {
if (value == true) this.draw2dNew();
},
getDraw2dLast(value) {
if (value == true) this.draw2dLast();
},
getDraw2dPicture(value) {
if (value == true) this.draw2dPicture();
},
getReset(value) {
if (value == true) this.reset();
},
},
mounted() {
this.canvas = Object.freeze(document.getElementById("board-canvas"));
this.workCanvas = Object.freeze(document.getElementById("work-canvas"));
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
this.workCtx = this.workCanvas.getContext("2d", {
willReadFrequently: true,
});
this.canvasWidth = this.canvas.parentElement.clientWidth;
this.canvasHeight = this.canvas.parentElement.clientHeight;
this.setBoardWidth();
this.setBoardHeight();
},
methods: {
...mapActions(globalStore, [
"toggleStop",
"setBoardWidth",
"setBoardHeight",
]),
// draws the board on the canvas
drawCanvas(board, width, height) {
const d = this.cellProperties.size;
// bool to RGBA colors
const img = board.flat().reduce((acc, cell, index) => {
const color = cell === 1 ? 0 : 255;
const i = index * 4;
acc.data[i] = color;
acc.data[i + 1] = color;
acc.data[i + 2] = color;
acc.data[i + 3] = 255;
return acc;
}, new ImageData(width, height));
// rescale and draw
this.ctx.save();
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.workCtx.putImageData(img, 0, 0);
this.ctx.imageSmoothingEnabled = false;
if (this.getDraw2dPicture != true) this.ctx.scale(d, d);
this.ctx.drawImage(
this.workCanvas,
0,
0,
this.canvasWidth,
this.canvasHeight
);
this.ctx.restore();
},
// create a first state, either a single living cell
// at the center or random ones
compute1dInitialState() {
if (this.initial1dState === "onecell")
return create1dStateOneCell(this.boardWidth);
return create1dState(this.boardWidth, getRandomInt, [0, 2]);
},
// initialize board with random cells
randomInitialState() {
return create2dState(
this.boardWidth,
this.boardHeight,
getRandomInt,
[0, 2]
);
},
// draw elementary automaton on the canvas based on selected ruleset
draw1d() {
const initialState = this.compute1dInitialState();
const board = createBoard(initialState, this.ruleset.rules, this.max);
this.lastBoard = Object.freeze(board);
this.drawCanvas(this.lastBoard, this.boardWidth, this.boardHeight);
this.toggleStop();
},
// draw 2D automaton on the canvas in a loop
draw2d(board) {
const newBoard = evolve2d(board, this.selectedRules);
this.drawCanvas(newBoard, this.boardWidth, this.boardHeight);
this.lastBoard = Object.freeze(newBoard);
},
// draw 2d automaton in a loop, starting from passed state
async draw2dNext(board) {
requestAnimationFrame(() => {
if (!this.canDraw) return;
const newBoard = evolve2d(board, this.selectedRules);
this.draw2d(board);
this.lastBoard = Object.freeze(newBoard);
/* await sleep(this.refreshRate) */
return this.draw2dNext(newBoard);
});
},
// draw 2d automaton from a new state
async draw2dNew() {
if (!this.canDraw) return;
const initialState = this.randomInitialState();
let board = evolve2d(initialState, conwayRules);
if (this.loop) return this.draw2dNext(board);
else this.draw2d(board);
this.toggleStop();
},
// draw 2d automaton from the last known generated board
async draw2dLast() {
if (!this.canDraw) return;
if (this.loop) return this.draw2dNext(this.lastBoard);
else this.draw2d(this.lastBoard);
this.toggleStop();
},
// draw 2d automaton from an uploaded picture.
// use the picture representation as an initial state
draw2dPicture() {
// get image data by drawing it on a work canvas
this.ctx.fillStyle = "black";
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.drawImage(
this.picture,
Math.floor((this.canvasWidth - this.picture.width) / 2),
Math.floor((this.canvasHeight - this.picture.height) / 2),
this.picture.width,
this.picture.height
);
const imgData = this.ctx.getImageData(
0,
0,
this.canvasWidth,
this.canvasHeight
);
// convert the image into a 2D board of boolean based on pixel value
this.lastBoard = Object.freeze(
picToBoard(imgData.data, this.canvasWidth, this.canvasHeight)
);
this.toggleStop();
},
// stop drawing routines and clear the canvas
reset() {
this.toggleStop();
this.lastBoard = {};
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.getReset = 0;
},
},
};
</script>
<style>
#canvas-board {
flex: 1;
margin: 0 auto;
}
</style>