- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
康威生命游戏(Conway's Game of Life)是元胞自动机(Cellular Automata)最经典的入门案例,也是 Coding Challenge #85 的核心主题。本文以该编码挑战为骨架,完整讲解规则、二维数组建模、邻居计数与 p5.js 渲染实现,并结合仓库中的 sketch.js 与 Processing 版本源码逐行剖析,读完你将能独立写出可运行的生命游戏模拟器,并理解所有元胞自动机的通用编程模式。
一、挑战概述:一句话的目标
原文档(_CodingChallenges/085-the-game-of-life.md)对本次挑战的定位非常简洁:
In this coding challenge, I attempt to code Conway's Game of Life cellular automata simulation in JavaScript using the p5.js library.
即:使用 p5.js 库,用 JavaScript 编写康威生命游戏的元胞自动机模拟。挑战视频编号 85,发布于 2017-12-11(front matter 中的date: 2017-12-11),对应仓库目录为CC_085_The_Game_of_Life,包含 P5(index.html+sketch.js)与 Processing(CC_085_The_Game_of_Life.pde)两套完整实现,代码结构几乎一一对应,便于跨语言对照学习。
二、康威生命游戏的四大规则
生命游戏是一个二维的、基于网格(grid)的元胞自动机:每个格子称为一个"细胞"(cell),只有两种状态——存活(1,白色)或死亡(0,黑色)。时间按"代"(generation)离散推进,每一代的状态只由上一代自身状态 + 周围 8 个邻居的存活数量决定。
四条规则(这也是后续代码next[i][j]计算的直接依据):
- 孤单致死(Underpopulation):存活细胞若周围存活邻居数
neighbors < 2,下一代死亡。 - 过密致死(Overpopulation):存活细胞若周围存活邻居数
neighbors > 3,下一代死亡。 - 存活(Survival):存活细胞若周围存活邻居数
neighbors == 2或neighbors == 3,下一代继续存活。 - 繁殖(Birth / Reproduction):死亡细胞若周围存活邻居数恰好为
neighbors == 3,下一代变为存活。
在 sketch.js 中,这四条规则被浓缩为两行判断:
if (state == 0 && neighbors == 3) { next[i][j] = 1; // 繁殖:死细胞恰好 3 个活邻居 → 复活 } else if (state == 1 && (neighbors < 2 || neighbors > 3)) { next[i][j] = 0; // 孤单致死 / 过密致死 } else { next[i][j] = state; // 其余情况(2 或 3 个邻居)保持原状态 }Processing 版本(CC_085_The_Game_of_Life.pde)逻辑完全一致,只是把grid换成int[][]、next换成int[][],可见这套规则与语言无关,是整个模拟的"引擎"。
规则出处参考:康威生命游戏维基百科条目 与 《数学游戏》专栏(Scientific American, 1970 年 10 月),这两条外部资料被原文档收录在 front matter 的
links字段中。
三、用二维数组建模网格
生命游戏的核心数据结构是二维数组。由于 JavaScript 原生不支持真正的多维数组,挑战中专门封装了一个make2DArray(cols, rows)工厂函数,来创建"数组的数组":
function make2DArray(cols, rows) { let arr = new Array(cols); // 外层:列 for (let i = 0; i < arr.length; i++) { arr[i] = new Array(rows); // 内层:行 } return arr; }对应的全局变量与画布参数(sketch.js#L16-L19):
| 变量 | 含义 | 取值 |
|---|---|---|
resolution | 每个细胞的边长(像素) | 10 |
cols | 列数 =width / resolution | 画布 600px →60 |
rows | 行数 =height / resolution | 画布 400px →40 |
grid | 二维数组,grid[i][j]取 0 或 1 | 初始随机填充 |
在setup()中,画布被设为createCanvas(600, 400),因此网格是60 列 × 40 行、共 2400 个细胞。随后用嵌套for循环对每个细胞做随机初始化:grid[i][j] = floor(random(2)),即以 50% 概率随机生成初始存活/死亡分布——这也是观察生命游戏从混沌演化出稳定图案的经典起点。
前置知识:如果不熟悉二维数组的创建、初始化与嵌套循环遍历,可先阅读仓库内配套教程 《2D Arrays in JavaScript》;另外原文档还关联了更早一期的《The Game of Life》Processing 版讲解(_more/archive/nature-of-code/7-Cellular-Automata/7.3-The-Game-of-Life.md),可对照理解同一算法在 Java 与 JavaScript 中的差异。
四、邻居计数与环形(环绕)边界
生命游戏每一代都要为每个细胞统计其8 个邻居中的存活数量。countNeighbors函数用两层for循环遍历i = -1..1、j = -1..1的 3×3 邻域,关键技巧在于边界处理:
function countNeighbors(grid, x, y) { let sum = 0; for (let i = -1; i < 2; i++) { for (let j = -1; j < 2; j++) { let col = (x + i + cols) % cols; // 列方向环绕(wrapping) let row = (y + j + rows) % rows; // 行方向环绕 sum += grid[col][row]; } } sum -= grid[x][y]; // 减去自己(避免把自己算进邻居) return sum; }这里有两个值得深究的实现细节:
- 取模运算实现环形世界(torus):
(x + i + cols) % cols让网格左、右、上、下边缘互相连通——最左列细胞可以看到最右列的邻居,最顶行能看到最底行。这是很多生命游戏实现默认采用的"无限平面近似"策略,避免边界细胞因邻居不足而产生失真。关于取模运算本身的原理,原文档关联了 Golan Levin 的 《The Modulo Operator》 客座教程。 - 先加后减避免分支判断:循环把 3×3 共 9 个格子的值全部累加(包括细胞自己),最后用
sum -= grid[x][y]一次性剔除自身,比在循环内加if判断更简洁高效。
五、双缓冲:为什么必须用next数组
这是元胞自动机最容易踩的坑,也是本挑战代码中最关键的设计决策。
draw()中没有直接修改grid,而是先新建一个空的next数组,把根据规则计算出的下一代状态全部写入next,等整个网格计算完毕后才执行grid = next完成整体替换(sketch.js#L49-L69)。
原因在于:元胞自动机的状态更新必须"同步"进行。每个细胞的新状态只能由"上一代"的全局状态决定;如果边遍历边修改grid,那么右侧、下方尚未遍历的细胞就会读到"已经更新过的"邻居,导致同一代内传播错误,演化结果完全失真。next数组相当于一个缓冲区(buffer),保证所有细胞都基于同一代快照计算——这也是并行计算中"双缓冲(double buffering)"思想的直观体现。
渲染部分同样在draw()中完成:先background(0)清屏,再遍历所有细胞,仅对存活细胞(grid[i][j] == 1)绘制白色方块,且每格缩小 1 像素(rect(x, y, resolution - 1, resolution - 1))以形成网格线:
background(0); for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { let x = i * resolution; let y = j * resolution; if (grid[i][j] == 1) { fill(255); stroke(0); rect(x, y, resolution - 1, resolution - 1); } } }六、运行方式:P5 网页版与 Processing 版
仓库为同一挑战提供了两种运行入口,代码逻辑完全同构:
1. p5.js 网页版(推荐,零依赖)
- 入口文件:P5/index.html
- 逻辑文件:P5/sketch.js
index.html通过 CDN 引入 p5.js 1.4.1,再加载本地sketch.js:
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.min.js"></script> <script type="text/javascript" src="sketch.js"></script>直接浏览器打开该 HTML 文件即可看到生命游戏在 600×400 画布上自动演化。
2. Processing(Java)版
- 入口文件:CC_085_The_Game_of_Life.pde
.pde文件用 Processing IDE(或processing-java命令行工具)打开运行。其源码注释明确标注"Originally written using p5.js",两种语言的函数一一对应(createCanvas↔size,floor(random(2))↔floor(random(2)),background/fill/stroke/rect均同名),非常适合作为"同一算法、两种语言"的对照练习。
提示:Processing 版同样实现了环形边界(
(x + i + cols) % cols)与双缓冲(int[][] next = new int[cols][rows];后grid = next;),可逐行与 p5.js 版对比,加深对语言差异(动态类型 vs 静态类型、数组声明方式)的理解。
七、扩展玩法与社区贡献
生命游戏的价值远不止"跑起来"。原文档的 front matter 中收录了contributions字段,记录了社区在本次挑战基础上衍生的大量变体,可作为功能扩展的直接灵感清单:
- 交互式网格:Knut Jackowski 的 "editable grid"、Ayush Koul 的 "Editable, Playable and with brief info"、Sungyoung Kim 的 "with user interactions!"——用鼠标点击绘制/擦除细胞,让用户手动摆放图案后再启动演化;
- 界面与框架变体:React.js 版(Fi Graham、Saswat Dash、Melissa Longenberger)、vanilla JS 版(Shekhar Tyagi)、LÖVE/Lua 版(vinnyI-afk)、Pygame/Python 版(Pathik Ghugare)、C 语言浏览器版(Imtiaz Kabir)、"terminal commands"版(Andrew Y);
- 视觉效果升级:Colorful Game of Life(Jamie McCrory)、"The Greg of Life"(Greg Kreisman)、"Life Game Canvas"(Ryosuke Osaki);
- 算法与数据结构优化:Rick Sidwell 的 "Yet Another Life" 使用**稀疏数组(sparse arrays)**大幅降低大网格的内存占用——从源码结构看,这是针对全量二维数组方案的一种典型性能优化方向;
- 创意衍生:"Photo to cellular automata"(Lenoir Christopher)把照片像素映射为初始细胞状态、"Game of Life With Patterns"(HoangTran)内置经典图案库(滑翔机、振荡器等)。
这些贡献分布在各社区作者的独立仓库中,仓库本体的contributions列表(_CodingChallenges/085-the-game-of-life.md 的 front matter)保留了完整作者与来源链接,便于检索和借鉴。
八、小结:从生命游戏到元胞自动机
通过本挑战,你实际上掌握了任意二维元胞自动机的通用编程骨架:
- 建模:用
make2DArray构造二维数组表示网格状态; - 演化:
countNeighbors统计邻域,用规则函数生成下一代; - 同步更新:
next缓冲区 +grid = next保证"一代一快照"; - 渲染:p5.js 的
rect/fill把数组状态可视化。
康威生命游戏只是这个框架下"规则最经典"的一个实例——把countNeighbors换成"左右邻居之和"就得到一维规则 30/90(初等元胞自动机),换成连续数值与距离加权就得到 Reaction-Diffusion、流场等更复杂的模拟(仓库中的 CC_013_ReactionDiffusion 正是同一编程思路的进阶挑战)。理解本次的四个规则与双缓冲机制,你就拥有了探索整个元胞自动机家族的第一把钥匙。
- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
相关推荐
用 p5.js 复刻 Agar.io 基础游戏机制:Coding Challenge 32.1 全解析
用 p5.js 复刻 Agar.io 基础游戏机制:Coding Challenge 32.1 全解析 这篇技术指南围绕 Coding Train(The Co
教程文档示例工程用 Yew 实现康威生命游戏:从细胞自动机核心到交互式 Web 界面
用 Yew 实现康威生命游戏:从细胞自动机核心到交互式 Web 界面 本篇技术指南以 Yew 官方示例 examples/game_of_life https:
前端Web框架用 p5.js 复刻经典扫雷游戏:Minesweeper Coding Challenge 71 源码级实战解析
用 p5.js 复刻经典扫雷游戏:Minesweeper Coding Challenge 71 源码级实战解析 导读 本文基于 Coding Train(Th
教程文档示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考