私宅
首发于私宅
个人项目开发示例:生命游戏

个人项目开发示例:生命游戏

前些天我在Live里讲程序员的自我提升时,提到了做个人项目是种十分有效的方法:基于一个基础需求进行开发,然后不停地制造新需求和特性进行迭代更新。这个过程既是对开发能力也是对产品设计能力的很好锻炼。

当时举了三个项目的例子。其实原本准备了四个,考虑到时间关系以及类型重复放弃了一个,即生命游戏(John Conway's Game of Life)。不过这个项目其实是很值得一试的,我很久前做过一个,在硬盘角落里躺了好久,翻出来给大家展开讲讲,也算是Live内容的一个补充吧。


先说生命游戏本身,这东西其实很多人都很熟悉了,连LeetCode里都有它的题。给还不清楚的人做一个简单介绍:

生命游戏(Game of Life),或者叫它的全称John Conway's Game of Life。是英国数学家约翰·康威在1970年代所发明的一种元胞自动机。
所谓元胞自动机其实是一种离散的状态机,即无数个独立的格子,每个格子处于某种状态,然后所有格子按照预先设定好的规律进行状态演化。格子们可以是任意维度、任意形状、按任意规律排布的。
而生命游戏就是最简单的元胞自动机之一——在二维平面上的方格子(细胞),每个细胞有两种状态:死或活,而下一回合的状态完全受它周围8个细胞的状态而定。按照以下三条规则进行演化:
1. 活细胞周围的细胞数如果小于2个或多于3个则会死亡;(离群或过度竞争导致死亡)
2. 活细胞周围如果有2或3个细胞可以继续存活;(正常生存)
3. 死细胞(空格)周围如果恰好有3个细胞则会诞生新的活细胞。(繁殖)
这三条规则简称B3/S23。如果调整规则对应的细胞数量,还能衍生出其他类型的自动机。

根据这个规则我们很容易就可以实现一个自由演化的简单版本:

首先生成一个指定大小的二维数组,将数组随机填满0和1,然后每回合遍历整个数组,统计每个元素周围8个的值计算下一回合的死活状态,并生成新的数组代替旧数组,这样交替往复下去,每回合计算完成后将值输出到屏幕上即可。

这就是我们为「生命游戏」项目所开发的最初版本。这个版本用任何语言都能完成,基本逻辑也都是相通的。


但满足于这一步还远远不够。要做到自我提升,首先就是为这个版本逐步增加新的设计与需求。

我开发自己的生命游戏项目时是2013年,当时对前端不很熟悉,但对HTML5中canvas的绘图很感兴趣,所以选择了HTML5+javascript来实现。本文最后会放出这个版本生命游戏的演示地址,如果对编程兴趣不大或者想先有个直观印象,拉到最底下直接访问就行。


那么,我就直接把自己为这个项目所做的功能特性迭代过程列出来吧。

1. 以直观形式在网页输出生命游戏二维数组的格子;

当时有几种选择,比如在table、div中填色,使用canvas直接操作ImageData像素,使用canvas逐个fillRect绘制矩形等方法。这些我都逐一测试过了,测试的结果是fillRect绘制矩形的性能胜出,并且考虑到将来像素尺寸的扩展性,就选择了它。

一开始设定的细胞尺寸都是固定的。首先绘制白底,如果无生命就画灰方块,有生命就画红方块,方块边缘留一个像素。这样就有了标题图中的效果。

2. 运行、单步、停止操作按钮;

这个很好理解,为了便于检查运行效果。单步也可以用于精确控制和查看细胞形状的进化。

3. 生成随机密度细胞的初始化模版;

分为少量随机、中等随机和密集随机,其实就是给三个数字阈值,随机大于此值则有生命,点击初始化后绘制。

4. 调节运行速度;

原理就是修改每一帧绘制完成后到下一回合计算前的setTimout等待时间。

5. 状态展示栏;

一共三项,分别是S当前回合数,C本回合计算时间,D本回合绘制时间。

为了实现时间统计,将计算部分和绘制部分独立分拆成两个函数,也是为将来的优化打下基础。

6. 优化绘制过程;

发现计算时间基本稳定,但增大画布尺寸和细胞数量的话,绘制时间会变得很长。

单独优化绘制过程不太可能,需要在计算时统计所有变动的格子,绘制时只绘制这些格子而不是全部重绘,时间大大缩短。

7. 调节细胞显示尺寸;

有了上一步的优化,在画面上流畅绘制更多格子成为可能。这样就可以减小尺寸,显示更复杂更大的图形了。

但在调节尺寸时如果直接清空画布就不合适了,加了两种处理方式:如果是由大变小,旧图出现在新图中间,周围填满空白;如果是由小变大,就裁切旧图中间的一块。

8. 增加预设模版;

这里要插句题外话,生命游戏远不止是一群随机图形自行演化。研究者们早就发现了里面有许多有规律的图形:

比如静止型的,就是每个活细胞周围恰好都有2~3个,死细胞周围都不是3个,所以图形在没有外来细胞入侵时会一直稳定下去:



还有一类是周期循环型的:


还有则是飞船型,周期变换形状并向某方向前进:


除此之外还有枪(能够连续发射飞船)、长者(几个细胞就能延续几千数万代,构成极大一片混沌)、吞食者(平时静止,但能吸收特定形状的飞船并保持原状)等等等。这些都是生命游戏爱好者和研究者们几十年来的结果,有的形状多达数万甚至数十万个细胞,构成庞大的自动机器。想了解更多的话不妨去专门的Wiki看看:LifeWiki

扯远了,那我们如何定义并导入模版呢?有3~4种较为通用的代码来定义,我们选用了Wiki里常见的RLE格式,编写了一个解析器。并在初始化选项中追加了一批比较有意思的模板,大家都可以在我的演示页面里看到(仅限桌面端,手机版由于画面太小显示不全,只保留了随机生成部分)


上面说的比较详细,下面就只列需求特性,不再写实现思路了,大家可以想想如果是自己的话,会怎么实现,以及这些需求是怎么来的。

10. 将当前全图导出为RLE代码;

11. 使用鼠标左键和右键在图上绘制及擦除细胞;

12. 允许在运行过程中也能用鼠标绘制及擦除;

13. 预置一批可插入的形状集合,并通过两层下拉框进行分类;

14. 在一个小型预览框中预览要插入的形状;

15. 使用旋转及翻转按钮改变可插入形状的造型,并实时生成RLE代码;

16. 插入前鼠标在画面上移动显示半透明预览;

17. 按下Ctrl键时允许拖拽画面;

18. 增加一个改变细胞颜色的按钮;

19. 适配移动端,将原先显示在画面右方的工具栏一部分移到上方,一部分放到弹出窗口中。


好了,以上就是我所做的这个生命游戏模拟器的全部功能。不是自夸,尽管是4年前做的,它实现的功能仍然是当前所有在线版模拟器中最强的。这代表它已经完美了吗?当然没有,无论是从界面效果还是功能和性能上仍然有无数可改进的地方。

由于是一步步迭代来的,当初我的js水平也刚入门,代码结构和可维护性并不好,引用的还是jQuery1.9这个古老版本,要重构的话还有很大空间。

之所以没有大改而原样放出来,无非是想跟大家展示一下一个真正个人项目的开发思路与过程。而且生命游戏即使不编程也是个足够有意思的数学游戏,网上好多DEMO,但大部分功能都很简陋,如果玩过我的这个版本还不满足,那就去下载Golly这种客户端吧。里面搞几十万细胞的巨型战舰都是可以的。


演示地址:Game of life

建议使用电脑访问,移动版在很多功能上有缺失或不好用。


代码也开源了:github.com/atonasting/g


也希望大家看过之后,对我说的个人项目与自我提升有进一步了解,并且真的能尝试通过这种方法提高自己的技术水平。当然,顺便再推荐一下这场Live,虽然结束了,但我自认为很值得一听:

程序员:如何在整个职业生涯中保持竞争力

文章被以下专栏收录