JS实现细胞自动机小游戏

什么是细胞自动机

细胞自动机(Cellular automaton,CA) ,又称元胞自动机,是一种时间、空间、状态都离散,空间相互作用和时间因果关系为局部的网格动力学模型,具有模拟复杂系统时空演化过程的能力。

在这里插入图片描述

细胞自动机游戏规则

游戏中,对于任意细胞,规则如下:

每个细胞有两种状态:存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。

  1. 当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
  2. 当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
  3. 当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
  4. 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)

可以把最初的细胞结构定义为种子,当所有在种子中的细胞同时被以上规则处理后, 可以得到第一代细胞图。按规则继续处理当前的细胞图,可以得到下一代的细胞图,周而复始。

JS代码实现

cell.js

export default class Cell {
    constructor(ctx, size) {
        this.alive = [];
        this.ctx = ctx;
        this.size = size;
        this.init();
    }

    init() { // 初始化所有细胞为死亡状态
        for (let i = 0; i < this.size; i++) {
            this.alive[i] = [];
            for (let j = 0; j < this.size; j++) {
                this.alive[i][j] = 0;
            }
        }
    }

    draw() { // 画细胞
        for (let i = 0; i < this.size; i++) {
            for (let j = 0; j < this.size; j++) {
                if (this.alive[j][i] === 1) {
                    this.ctx.fillStyle = 'red';
                } else {
                    this.ctx.fillStyle = 'white';
                }
                this.ctx.fillRect(i * 10 + 1, j * 10 + 1, 8, 8);
            }
        }
    }

    transform() { // 演化细胞
        let sum = 0;
        let alive = this.copyAlive(this.alive);
        // 循环计算所有细胞周围活细胞个数
        for (let i = 0; i < this.size; i++) {
            for (let j = 0; j < this.size; j++) {
                if (i === 0 && j === 0) { // 左上角
                    sum = this.alive[i][j + 1] + this.alive[i + 1][j + 1] + this.alive[i + 1][j];
                }
                if (i === 0 && j === this.size - 1) { // 右上角
                    sum = this.alive[i][j - 1] + this.alive[i + 1][j - 1] + this.alive[i + 1][j];
                }
                if (i === this.size - 1 && j === 0) { // 左下角
                    sum = this.alive[i - 1][j] + this.alive[i - 1][j + 1] + this.alive[i][j + 1];
                }
                if (i === this.size - 1 && j === this.size - 1) { // 右下角
                    sum = this.alive[i - 1][j] + this.alive[i - 1][j - 1] + this.alive[i][j - 1];
                }
                if (i === 0 && j > 0 && j < this.size - 1) { // 上棱
                    sum = this.alive[i][j - 1] + this.alive[i + 1][j - 1] + this.alive[i + 1][j] + this.alive[i + 1][j + 1] + this.alive[i][j + 1];
                }
                if (i === this.size - 1 && j > 0 && j < this.size - 1) { // 下棱
                    sum = this.alive[i][j - 1] + this.alive[i - 1][j - 1] + this.alive[i - 1][j] + this.alive[i - 1][j + 1] + this.alive[i][j + 1];
                }
                if (i > 0 && i < this.size - 1 && j === 0) { // 左棱
                    sum = this.alive[i - 1][j] + this.alive[i - 1][j + 1] + this.alive[i][j + 1] + this.alive[i + 1][j + 1] + this.alive[i + 1][j];
                }
                if (i > 0 && i < this.size - 1 && j === this.size - 1) { // 右棱
                    sum = this.alive[i - 1][j] + this.alive[i - 1][j - 1] + this.alive[i][j - 1] + this.alive[i + 1][j - 1] + this.alive[i + 1][j];
                }
                if (i > 0 && i < this.size - 1 && j > 0 && j < this.size - 1) { // 内部所有细胞
                    sum = this.alive[i - 1][j - 1] + this.alive[i - 1][j] + this.alive[i - 1][j + 1] + this.alive[i][j + 1] + this.alive[i + 1][j + 1] + this.alive[i + 1][j] + this.alive[i + 1][j - 1] + this.alive[i][j - 1];
                }

                // 如果当前细胞为活,周围共有2或3个细胞,则当前细胞存活,低于2或高于3,则死亡
                // 如果当前细胞为死,周围共有3个活细胞,则当前细胞复活,否则死亡
                alive[i][j] = (sum === 3) | (sum === 2 & this.alive[i][j]);
            }
        }
        this.alive = this.copyAlive(alive);
    }

    copyAlive(alive) {
        return alive.map(item => {
            return [...item];
        });
    }
}

index.js

import Cell from './cell';

const size = 50; // 网格尺寸 50 * 50
let isStart = false;
let cvs = document.querySelector('#canvas');
let ctx = cvs.getContext('2d');
let cell = new Cell(ctx,size);

cvs.width = size * 10;
cvs.height = size * 10;

// 画网格
for (let i = 0; i <= size; i++) {
    ctx.beginPath();
    ctx.moveTo(i * 10, 0);
    ctx.lineTo(i * 10, size * 10);
    ctx.stroke();
    ctx.moveTo(0, i * 10);
    ctx.lineTo(size * 10, i * 10);
    ctx.stroke();
    ctx.closePath();
}

// 点击网格编辑地图
cvs.addEventListener('click', e => {
    if (isStart) {
        // 已经开始运行,直接返回,不作响应
        return;
    }
    // 计算点击的是哪一个小方格
    let i = parseInt(e.pageX / 10);
    let j = parseInt(e.pageY / 10);

    // 重复点击同一个小方格可以切换当前小方格对应的细胞生死状态
    cell.alive[j][i] = !cell.alive[j][i] & 1;
    cell.draw();
});

let start = document.querySelector('#btn-start');
let timer = null;
start.addEventListener('click', () => {
    if (isStart) {
        isStart = false;
        start.innerHTML = 'START';
        clearInterval(timer);
        return;
    }
    isStart = true;
    start.innerHTML = 'STOP';
    timer = setInterval(() => {
        cell.transform();
        cell.draw();
    }, 200);
});

let reset = document.querySelector('#btn-reset');
reset.addEventListener('click', () => {
    if (isStart) {
        alert('请先停止运行');
        return;
    }
    cell.init();
    cell.draw();
});

start.parentElement.style.left = size * 10 + 20 + 'px';

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>元胞自动机</title>
    <style>
        body {
            background: #fff;
            overflow: hidden;
        }

        canvas {
            position: absolute;
            left: 0;
            top: 0;
        }

        .toolbox {
            position: absolute;
            top: 20px;
            left: 420px;
        }

        .toolbox button {
            display: block;
            margin-bottom: 20px;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
    <div class="toolbox">
        <button id="btn-start">START</button>
        <button id="btn-reset">RESET</button>
    </div>
</body>

</html>

因为是采用ES6语法开发的,所以需要在本地跑一个服务才能运行,直接运行index.html是没用的。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 点我我会动 设计师:白松林 返回首页