生命游戏(Game of Life)。这款游戏由英国数学家约翰·康威(John Horton Conway)于1970年发明,并在当时的计算机界引起了极大的关注。生命游戏是一款基于元胞自动机理论的模拟游戏,它的规则简单而富有哲理。
生命游戏是一个无限的,由二维正交正方形的网格组成的计算机模拟图像。每个单元可能出现两种状态:活或死的(填充或未填充的分别)。每个单元格都与其八个相邻的单元交互,这八个单元格是水平、垂直或对角线相邻的单元格。在每个时间步上,都会发生以下变化:
1:当前元胞为存活状态时,如果周围的存活元胞数小于2或大于3,该元胞变成死亡状态。
2:当前元胞为存活状态时,如果周围有2个或3个存活元胞时,该元胞保持原样。
3:当前元胞为死亡状态时,如果周围有3个存活元胞时,该元胞变成存活状态。
canvas画布html代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
margin: 0;
padding: 0;
}
#myCanvas {
position: absolute;
top: 0;
left: 0;
}
#button-container {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
margin-bottom: 20px;
}
#button-container button {
display: inline-block;
margin: 0 10px;
padding: 10px 20px;
color: #fff;
background-color: #6c8e91;
;
border: none;
border-radius: 5px;
cursor: pointer;
}
#button-container button:hover {
background-color: #6c8e91bd;
}
</style>
</head>
<body>
<canvas id="myCanvas"></canvas>
<div id="button-container">
<button onclick="MoveToCenter()">回原点</button>
<button onclick="Game()">开始</button>
<button onclick="Over()">停止</button>
</div>
</body>
</html>
网格画布js代码如下:
// 加载纹理图片
const myCanvas = document.querySelector("#myCanvas");
const ctx = myCanvas.getContext("2d"); // 获取canvas上下文对象
let clickedGrids = new Map(); // 使用哈希表存储格子位置信息
const solidColor = "#ccc"; // 实线颜色
const dashedColor = "#ccc"; // 虚线颜色
const zeroinColor = "red"; // 0 点坐标颜色
const zeroColor = "#ccc"; // 0 点坐标系颜色
const redColor = "#53333B"; // 细胞颜色
const pageSlicePos = { // 坐标系的原点
x: 0,
y: 0,
};
let scale = 3; // 初始缩放比例
myCanvas.width = window.innerWidth;
myCanvas.height = window.innerHeight;
myCanvas.addEventListener("mousedown", mouseDown);
myCanvas.addEventListener("mousewheel", mouseWheel);
// 回到坐标原点,原点居中
function MoveToCenter() {
pageSlicePos.x = myCanvas.width / 2;
pageSlicePos.y = myCanvas.height / 2;
}
let requestId = null;
function draw() {
drawLineGrid();
requestId = requestAnimationFrame(draw);
}
const textureImage = new Image();
textureImage.src = '2.png';
// 绘制网格 scaleVal 缩放倍数
function drawLineGrid(scaleVal = scale) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = ctx.createPattern(textureImage, "repeat");
ctx.fillRect(0, 0, myCanvas.width, myCanvas.height);
// 网格大小 5 表示一个单位的小正方形的基本宽高大小
// 5 * scaleVal表示放大或缩小后的小正方形实际像素宽高大小
const girdSize = 5 * scaleVal;
const CanvasWidth = ctx.canvas.width;
const CanvasHeight = ctx.canvas.height;
ctx.save(); // 保存画布状态
ctx.fillStyle = zeroinColor;
ctx.fillRect(pageSlicePos.x - 3, pageSlicePos.y - 3, 6, 6);
const canvasXHeight = CanvasHeight - pageSlicePos.y;
const canvasYWidth = CanvasWidth - pageSlicePos.x;
const xPageSliceTotal = Math.ceil(canvasXHeight / girdSize);
const xRemaining = pageSlicePos.y;
const xRemainingTotal = Math.ceil(xRemaining / girdSize);
const yPageSliceTotal = Math.ceil(canvasYWidth / girdSize);
const yRemaining = pageSlicePos.x;
const yRemainingTotal = Math.ceil(yRemaining / girdSize);
for (let i = 0; i < xPageSliceTotal; i++) {
ctx.beginPath();
ctx.moveTo(0, pageSlicePos.y + girdSize * i);
ctx.lineTo(CanvasWidth, pageSlicePos.y + girdSize * i);
ctx.strokeStyle = i === 0 ? zeroColor : i % 5 === 0 ? solidColor : dashedColor;
ctx.stroke();
}
for (let i = 0; i < xRemainingTotal; i++) {
if (i === 0) continue;
ctx.beginPath();
ctx.moveTo(0, pageSlicePos.y - girdSize * i);
ctx.lineTo(CanvasWidth, pageSlicePos.y - girdSize * i);
ctx.strokeStyle = i === 0 ? zeroColor : i % 5 === 0 ? solidColor : dashedColor;
ctx.stroke();
}
for (let j = 0; j < yPageSliceTotal; j++) {
ctx.beginPath();
ctx.moveTo(pageSlicePos.x + girdSize * j, 0);
ctx.lineTo(pageSlicePos.x + girdSize * j, CanvasHeight);
ctx.strokeStyle = j === 0 ? zeroColor : j % 5 === 0 ? solidColor : dashedColor;
ctx.stroke();
}
for (let j = 0; j < yRemainingTotal; j++) {
if (j === 0) continue;
ctx.beginPath();
ctx.moveTo(pageSlicePos.x - girdSize * j, 0);
ctx.lineTo(pageSlicePos.x - girdSize * j, CanvasHeight);
ctx.strokeStyle = j === 0 ? zeroColor : j % 5 === 0 ? solidColor : dashedColor;
ctx.stroke();
}
ctx.fillStyle = redColor;
clickedGrids.forEach((value, key) => {
const [x, y] = key.split(',').map(Number); // 将键字符串解析为坐标
ctx.fillRect(x * girdSize + pageSlicePos.x, y * girdSize + pageSlicePos.y, girdSize, girdSize);
//ctx.fillRect(gridPos.x * girdSize + pageSlicePos.x, gridPos.y * girdSize + pageSlicePos.y, girdSize, girdSize);
});
ctx.restore(); // 恢复画布状态
}
画布无边界,缩放拖动js代码如下:
// 滚轮缩放倍数, 以鼠标滚轮位置为中心缩放、放大以及边界判断
function mouseWheel(e) {
const x = (e.offsetX - pageSlicePos.x) / scale;
const y = (e.offsetY - pageSlicePos.y) / scale;
if (e.wheelDelta > 0) {
if (scale < 10) {
scale++;
pageSlicePos.x -= x;
pageSlicePos.y -= y;
}
} else {
if (scale > 1) {
scale--;
pageSlicePos.x += x;
pageSlicePos.y += y;
}
}
}
// 拖动 canvas 动态渲染,拖动时,动态设置 pageSlicePos 的值
function mouseDown(e) {
const downX = e.clientX;
const downY = e.clientY;
const { x, y } = pageSlicePos;
if (event.button === 0) {
myCanvas.style.cursor = "grabbing";
myCanvas.onmousemove = (ev) => {
const moveX = ev.clientX;
const moveY = ev.clientY;
pageSlicePos.x = x + (moveX - downX);
pageSlicePos.y = y + (moveY - downY);
};
} else if (event.button === 2) {
const gridPos = {
x: Math.floor((e.clientX - pageSlicePos.x) / (5 * scale)),
y: Math.floor((e.clientY - pageSlicePos.y) / (5 * scale)),
};
// 将格子的位置作为键,将布尔值设为true表示红色
clickedGrids.set(`${gridPos.x},${gridPos.y}`, true);
console.log(gridPos)
myCanvas.onmousemove = (ev) => {
const moveX = ev.clientX;
const moveY = ev.clientY;
const gridPos = {
x: Math.floor((moveX - pageSlicePos.x) / (5 * scale)),
y: Math.floor((moveY - pageSlicePos.y) / (5 * scale)),
};
clickedGrids.set(`${gridPos.x},${gridPos.y}`, true);
};
}
myCanvas.onmouseup = (en) => {
myCanvas.onmousemove = null;
myCanvas.onmouseup = null;
myCanvas.style.cursor = "";
};
}
// 页面卸载时取消动画渲染
window.addEventListener("beforeunload", () => {
cancelAnimationFrame(requestId);
});
// 页面禁止鼠标右键弹出
document.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
draw();
MoveToCenter();
Game of Life主要规则,js代码如下:
let GameOfLifeId = 0;
function GameOfLife() { // 康威生命游戏
let newClickedGrids = new Map(); // 使用新的哈希表存储更新后的格子位置信息
clickedGrids.forEach((value, key) => {
const [x, y] = key.split(',').map(Number);
let liveNeighbors = 0;
// 检查当前单元格的邻居
for (let i = x - 1; i <= x + 1; i++) {
for (let j = y - 1; j <= y + 1; j++) {
if (i === x && j === y) continue; // 跳过当前单元格
const neighborKey = `${i},${j}`;
if (clickedGrids.has(neighborKey)) { // 统计活着的邻居
liveNeighbors++;
} else {
let deadNeighborCount = 0; // 检查死亡的邻居,看看它们是否应该复活
for (let ni = i - 1; ni <= i + 1; ni++) {
for (let nj = j - 1; nj <= j + 1; nj++) {
if (ni === i && nj === j) continue;
if (clickedGrids.has(`${ni},${nj}`)) {
deadNeighborCount++;
}
}
}
if (deadNeighborCount === 3) {
newClickedGrids.set(neighborKey, true);
}
}
}
}
// 应用康威生命游戏规则
if ((value && liveNeighbors === 2) || liveNeighbors === 3) {
newClickedGrids.set(key, true);
}
});
clickedGrids = newClickedGrids; // 更新格子位置信息
}
function Game() {
GameOfLifeId = setInterval(GameOfLife, 300);
}
function Over() {
clearInterval(GameOfLifeId);
}
给出两个简单的细胞集,代码如下:
// 原子集
const atom = {
glider: () => { // 滑翔机
clickedGrids.set(`${0},${0}`, true);
clickedGrids.set(`${0},${1}`, true);
clickedGrids.set(`${0},${2}`, true);
clickedGrids.set(`${1},${0}`, true);
clickedGrids.set(`${2},${1}`, true);
},
piston: () => { // 活塞
clickedGrids.set(`${-1},${-1}`, true);
clickedGrids.set(`${-1},${-2}`, true);
clickedGrids.set(`${-1},${1}`, true);
clickedGrids.set(`${-1},${2}`, true);
clickedGrids.set(`${0},${2}`, true);
clickedGrids.set(`${0},${-2}`, true);
clickedGrids.set(`${1},${-1}`, true);
clickedGrids.set(`${1},${0}`, true);
clickedGrids.set(`${1},${1}`, true);
clickedGrids.set(`${2},${0}`, true);
clickedGrids.set(`${3},${0}`, true);
clickedGrids.set(`${4},${0}`, true);
clickedGrids.set(`${4},${-1}`, true);
clickedGrids.set(`${4},${1}`, true);
clickedGrids.set(`${7},${1}`, true);
clickedGrids.set(`${7},${-1}`, true);
clickedGrids.set(`${7},${0}`, true);
clickedGrids.set(`${9},${-1}`, true);
clickedGrids.set(`${9},${-2}`, true);
clickedGrids.set(`${8},${-2}`, true);
clickedGrids.set(`${9},${1}`, true);
clickedGrids.set(`${9},${2}`, true);
clickedGrids.set(`${8},${2}`, true);
},
}
atom.piston(); // 初始生命 - 滑翔机