Appearance
GameOfLife
In diesem Artikel erstelle ich Conway's "Game of Life" mit JavaScript, HTML und CSS. Das Ergebnis wird wie folgt aussehen:
Quellcode
vue
<template>
<button class="block" @click="onStart">Neustarten</button>
<canvas id="board" class="board" width="500" height="500"></canvas>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const randomNumber = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
class Matrix {
constructor(columns, rows) {
this.data = Array(columns).fill(false).map(_ => Array(rows).fill(false));
}
set(x, y, isAlive) {
this.data[x][y] = isAlive;
}
get(x, y) {
return this.data[x][y];
}
}
class Board {
constructor(canvas, rows, columns) {
this.canvas = canvas;
this.rows = rows;
this.columns = columns;
this.pixelSize = {
x: canvas.width / rows,
y: canvas.height / columns
};
this.ctx = canvas.getContext('2d');
this.probAlive = 60;
this.iteration = 0;
}
drawGrid() {
for (let x = 0; x < this.rows; x++) {
for (let y = 0; y < this.columns; y++) {
this.ctx.strokeStyle = '#F3F3F3';
this.ctx.strokeRect(x*this.pixelSize.x, y*this.pixelSize.y, this.pixelSize.x, this.pixelSize.y);
}
}
}
drawCell(x, y, isAlive=false) {
this.ctx.fillStyle = isAlive ? 'black' : 'white';
this.ctx.fillRect(x*this.pixelSize.x, y*this.pixelSize.y, this.pixelSize.x, this.pixelSize.y);
}
isOnGrid(cell) {
const isOnXAxis = cell.x >= 0 && cell.x <= this.columns-1;
const isOnYAxis = cell.y >= 0 && cell.y <= this.rows-1;
return isOnXAxis && isOnYAxis;
}
init(matrix) {
const isInitialRun = !Boolean(matrix);
this.matrix = isInitialRun ? new Matrix(this.columns, this.rows) : matrix;
for (let x = 0; x < this.columns; x++) {
for (let y = 0; y < this.rows; y++) {
if (isInitialRun) {
const isAlive = randomNumber(0, 100) > this.probAlive;
this.matrix.set(x, y, isAlive);
this.drawCell(x, y, isAlive);
} else {
this.drawCell(x, y, this.matrix.get(x, y));
}
}
}
}
update(matrix) {
this.init(matrix);
this.drawGrid();
}
getNeighbours(c_x, c_y) {
const neighbours = [];
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
if (x === 0 && y === 0) { continue; }
const neighbourCell = {
x: x + c_x,
y: y + c_y
};
if (this.isOnGrid(neighbourCell)) {
neighbourCell.isAlive = this.matrix.get(neighbourCell.x, neighbourCell.y);
neighbours.push(neighbourCell);
}
}
}
return neighbours;
}
simulate() {
const newMatrix = new Matrix(this.columns, this.rows);
for (let x = 0; x < this.matrix.data.length; x++) {
for (let y = 0; y < this.matrix.data[0].length; y++) {
// get neighbours
const neighbours = this.getNeighbours(x, y);
const livingNeighbours = neighbours.filter(cell => cell.isAlive);
// rules
const isLonely = livingNeighbours.length === 0 || livingNeighbours.length === 1;
const isSurviving = livingNeighbours.length === 2 || livingNeighbours.length === 3;
const isOverpopulation = livingNeighbours.length > 3;
const isReproduction = livingNeighbours.length === 3;
const isCellAlive = this.matrix.get(x, y);
if (!isCellAlive) {
if (isReproduction) { newMatrix.set(x, y, !!1); }
} else {
if (isLonely) { newMatrix.set(x, y, !!0); }
else if (isSurviving) { newMatrix.set(x, y, !!1); }
else if (isOverpopulation) { newMatrix.set(x, y, !!0); }
}
}
}
this.matrix = newMatrix;
this.update(newMatrix);
}
}
async function run(board) {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
board.init();
while (true) {
board.drawGrid();
await delay(20);
board.simulate();
}
}
const canvas = ref();
const board = ref();
onMounted(() => {
canvas.value = document.getElementById('board');
board.value = new Board(canvas.value, 50, 50);
run(board.value);
});
const onStart = () => {
board.value.init();
board.value.drawGrid();
};
const onSimulate = () => {
board.value.simulate();
};
</script>
<style scoped>
.board {
background-color: white;
}
</style>