什么是细胞自动机
细胞自动机(Cellular automaton,CA) ,又称元胞自动机,是一种时间、空间、状态都离散,空间相互作用和时间因果关系为局部的网格动力学模型,具有模拟复杂系统时空演化过程的能力。
细胞自动机游戏规则
游戏中,对于任意细胞,规则如下:
每个细胞有两种状态:存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。
- 当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
- 当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
- 当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
- 当前细胞为死亡状态时,当周围有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
是没用的。