Skip to content
Inhaltsverzeichnis

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>