生命游戏,算法与实现

yanlb2000

0 一些关于"生命游戏"的基本概念

"生命游戏"在计算机界不是什么新鲜的事物,也不仅仅属于计算机科学。按我的理解,它大概是指这样的一些模型,构造一个虚拟的"世界",这世界往往是些"单元"或称"格子"组成的平面或立体的矩阵,比如二维平面、三维立体。这就是虚拟的生命活动的场所。然后,就可以在这些单元中安放"元素"或称"细胞"。每个细胞可以都是相同的,也可以不同,都有它自己的特性。它们是这个虚拟生物界中的生命。

在有一类模型中,一个单元(占据一个格子)就是一个"生物"或一个"生命",特别是当每个单元的特性可以不同的时候,不同的特性代表了不同的物种。

在另一类模型中,一个单元仅仅是一个细胞,相邻的若干细胞才组成一个生物。这种模型中的细胞往往被认为是相同的,因为这样比较简单。不同的细胞数和相互间的构型才被看成是不同"物种"的区分的依据。当然,也可以考虑不相同的细胞的情况,这就要相对复杂许多了。

上面是构造的情况,然后,就是运行规则了。这些规则规定了对于一定的细胞或细胞构型,会在下一步产生什么样的新的细胞或构型。这些规则可以非常简单,也可以很复杂。于是,虚拟的生物世界就可以在这些规则的驱动下,一步步地运行下去了。由于使用了"格子"、"单元",所以也把这样的系统称为"格子机"或"单元自动机"。

我们可以将这种生命游戏看作是利用电脑玩的一些小把戏,好玩。但其实,生命游戏所揭示的原理并不简单。一方面,我们可以看到,看似简单的一些元素和几条很简单的规则,就可能产生非常复杂的变化现象、组织结构;而从另一方面说,我们这个自然界,这个社会,看似纷繁复杂,但在本质上,可能也只是由一些基本的规律来产生,来推动的。

1 著名的John Conway的"生命游戏"

这里,不得不谈到著名的John

Conway的生命游戏。七十年代曾经风靡计算机界,据说,当时全世界1/3的计算机运行过这个生命游戏的不同实现版本。

这个游戏的规则其实是非常简单的。它构筑在一个最简单的二维纵横网格上,(也就是一个二维矩阵,虽然生命游戏不排除有三角形、六角形甚至更古怪的各种立体网格,但我没考虑过,也没听说过。绝大多数生命游戏模型都是这种平面纵横网格的。)每个格子的状态就是两种,要么空,要么有一个细胞,所有细胞都是相同的(这显然属于我上面说的第二类模型)。

规则是这样:

邻居数:这个单元的8个相邻单元中共有多少细胞。

生:如果一个空单元的邻居数是3,那么下一步,该单元将有一个新的细胞出现。

死:如果一个细胞的邻居数大于3,那么下一步它将因拥挤(缺少食物?)而死;如果一个细胞的邻居数小于2,那么下一步它将因孤独而死。换句话说,只有邻居是2或3的细胞才能继续存活下去。

在由当前状态变成下一步状态时,所有单元都是同时考虑的。当给定一个初始状态后(即一个散步着若干细胞的单元矩阵),就可以在这个规则下运行下去了。

仔细想一下,这实在是非常简单的。但正是这种非常简单的规则,却也能产生意料不到的结果。除了一些非常简单的原始状态,实在难以预料一种给定的初始状态,最后会发展到什么程度。

总的来说,可以有以下几种情况:

死亡:所有的细胞最终消失。

停止:在某一步以后,网格的状态不再有任何变化。

循环:在若干步以后,网格的状态变化将进入一个(有限)周期的循环。

显然,上面几种都是简单的情况,停止可以认为是周期为1

的循环。最难以逆料的是:

混沌:不论经过多少次有限步数的变化,总是不能再现任何曾经出现过的状态,当然也不死亡、不停止。就是说,不会进入循环。这时,我们无法确定,系统将来是会死亡、还是停止演变,还是进入某种循环,还是继续保持这样的不确定状态。或者说,系统的状态是混沌的。

比如,一个起先只有7个细胞的称为Acorn的构型,竟然能产生5000个变化。另外,还发现两个起先只有226个细胞的构型,发展到后来,竟然需要一个10,000,000,000平方的矩阵才能容纳得下!

这个"游戏"的算法实现相应也是简单的。只需用两个二维矩阵就可以了,矩阵的元素就取普通的整数,只取两种值,1代表相应单元有细胞,0代表没有。一个矩阵存放当前的状态,另一个用以逐个单元地计算、存放下一步状态,待全部计算完毕,就将这个矩阵的值取代原来的矩阵。

这个"游戏"的实现太多了,如果你愿意,你能在网络上轻易地搜索(使用关键词"Game

of Life")到一大堆。几年前,微软出的Entertainment

Pack中就包含了这么个游戏,编得还是不错的。

我也未能免俗,根据我们当时的Pascal教科书上的习题要求(呵呵,教科书上也有),用Pascal编制了这个生命游戏。因为那时用的是VAX机的字符终端VT220,所以还是呆板的字符界面。为了达到好的视觉效果和程序控制界面,大量使用了Ansi

ESC系列控制符。呵呵,一般的网虫是上了BBS才了解Ansi控制符的,我可是早几年就对这些东东掌握得一清二楚了。

2 我的"捕食者与被捕食者"生命游戏

好了,可以谈谈我编制的另一个生命游戏了,这也是花费我较长时间的一个。我之所以编制这样一个程序,一方面是因为一直对此比较感兴趣,常有些思考和想法。另一方面,当时我正好决定学习C++,单单看书,纸上谈兵是不行的,得找个具体题目练练才有好的效果。于是,决定来个一举两得。

2.1 模型概述

其实,我的这个模型说起来还是很简单的,它属于我上面说的第一类模型。一个平面网格中散步着一些生物(每个生物占据一个格子,我还不准备考虑体积不同的生物),有的是捕食者,有的是被捕食者,作为捕食者的食物。食物(被捕食者)可以自发地、随机地产生,而捕食者必须经常捕食到食物才能存活,否则就死亡。如果要繁殖,那还必须捕食到更多的食物。

以上这些最基本的规则、概念。为了更接近实际生物界的情况(当然还是差得远),还有很多修正,补充。比如,网格的有些地方比较"富饶",食物产生得多、快。有些地方则相对贫瘠。捕食者的情况就更复杂,它们有各自不同的行为特征,这些都由它们所携带的"基因"组所控制。不同的基因控制不同的方面。比如,有的"运动"较快、每一步所跨越的网格数多;有的则相对行动缓慢。(不要以为行动快的就好,在食物富饶的地区,还是走得慢点的好。)有的"喜欢"经常转换行动方向,而有的则不太喜欢这样。有的捕食距离较远,但成功率低(它们适合于生活在食物富饶的地区);另一些则只能捕食较近的食物,同时成功率相对较高。

捕食者的繁殖也是重要的、复杂的内容。我还没有考虑到两性生殖的情况,只考虑分裂生殖。积累到多少食物量才能分裂;还有关键的,分裂时如何控制基因的变异从而产生新种。基因变异的机率多大,如何变异等等。

哦,这已经够复杂的了,不愿意考虑更多情况了。

2.2 程序实现

我之所以要把生命游戏的实现用C++来做,是因为我发现,这种类型的模型非常适合于使用面向对象的编程概念。一个生物体,就是一个对象。这个生物体的各种特性,就是这个对象的属性;这个生物体的各种行为,就是这个对象的方法;而生物体之间的交互作用、捕食与被捕食等,可以用消息、事件来实现。另外,所有生物体都有些基本的、共同的属性、方法,这些属性、方法有的是完全相同的,有的是"名"相同但实现不同,比如在网格上的位置坐标、重量、如何在屏幕上显示自己等。这些都对应了对象类的特性、方法的继承、重载等。

具体实现中,遇到的第一个问题是,采用什么数据结构?想当然的,是二维矩阵。但矩阵的弊病是显然的。因为这样的网格往往是稀疏矩阵,大部分单元都是空的,用矩阵很不合算,浪费大量程序空间。其实,也是现实上的限制,我用的Borland

C++ for DOS

3.1,不直接支持大于64K的数据结构指针(可能有一定的解决方法,但我当时还不会,估计还挺麻烦)。即使简单的整形数矩阵,都不能开得很大,何况是复杂的对象矩阵?根本不能满足我实现一个"有一定规模"的虚拟生物世界。

所以,只能使用十字链表了。可那时想起十字链表就有点害怕。多么麻烦的结构呀,每个元素有4个指针,还要有两个一维的表头链表,一个将所有的行的头连接起来,一个将所有列的头连接起来。元素的插入、删除等都巨麻烦无比。当初学数据结构,练习用Pascal编程实现十字链表时,我就逃过了,找人Copy的,因为我觉得这些太麻烦了。而现在,我要用C++来"亲自"实现一回了。

麻烦的不止这些。现在,我的十字链表所链接的表元素,不再是简单的结构变量,而是各种类的实例。这些类是各不相同的,有的是捕食者类,有的是食物类,它们都由同一个基类继承得来。

下面就谈类的规划与实现。首先,设置一个基类,作为所有类的祖先,它是抽象中的所有生物的代表,具有一些生物最基本的属性,比如坐标位置、体重。也具有生物普遍具有的行为,比如,插入到十字链表中、从链表中删除,向邻居返回自己的类型(是食物、还是捕食者)和具体座标,在屏幕上显示自己(我的程序中是DrawMyself),等等。而且,有些方法往往要到具体的生物类型才能确定,现在仅仅是保留一个名头,但无法或不必在基类中实现。所以,干脆把这些方法定义为NULL(纯虚函数),这基类也成为了虚基类。

在这个虚基类上,继承出食物(被捕食者)类和捕食者类。食物类相对简单很多。而捕食者类要增加很多特性和方法,特别是对基因的处理。

还要提一提对显示的处理。按我原来的想法,这个程序主要解决的是实现一个虚拟的生物界,实现一些实在的生物、功能。而具体如何将这个生物界显示在屏幕上,供人眼观察倒不是主要的。只要改写或重载各个类中的显示方法,就能适应不同的显示界面。可以是简单的字符界面,也可以是简单的图形界面。等将来内部功能成熟稳定了,还可以考虑画漂亮的界面(特别是如果移植到Windows界面的话),比如,食物可以画成草或虫子,捕食者是一些体形较大,有锋利的爪子、牙齿的凶猛的怪物等,当然,这是后话了。由于当时用的是BC++3.1,所以当然就用它带的图形驱动Borland

BGI,图形功能凑合能用,但远没有Windows下的强大。画食物,就是简单的一个绿点;画捕食者,就是画个形状稍大的红点。

程序运行后,首先初始化,随机产生(new)些食物和捕食者。它们的状态也是随机的,根据其座标,插入到链表中去。然后是运行。每个单元都逐个动作一次,以决定它下一次的位置、状态等。其中捕食者要查询它的邻居,如果查询到食物,且在其捕食范围内,就根据其捕食成功率决定是否将这食物吃掉。饥饿久了的捕食者将死去,而积累了一定量食物的捕食者有可能会分裂为两个捕食者。后代基本上继承上代的基因,但可能会有些小的变异。这些变异会决定后代的行为与上代有所不同。

这么些复杂的概念、功能、数据结构,我又是借学C++的机会来实现,也是首次体会面向对象的编程概念,所以还是颇费了我些时间和周折的。比如,光是实现那个复杂的十字链表,完成初始化、动态插入、删除、查找等就是个让人头痛的部分。经常会"Null

pointer assignment

error.",经过排错,已经找出很多不恰当使用空指针的地方了,可有时程序正常退出时,屏幕上还是会给你来这么一句,真是扫兴。

最终程序还是编好了,看着屏幕上的那些蠕动着的小点子,还真有些成就感呢。呵呵,我是造物主,我就是上帝!

对一些参数,比如食物产生的速度,捕食者的对食物的需求、捕食能力、繁殖能力、基因的变化机率等,都需要细心调整。正如我所料的,本来行为差不多的捕食者,经过一段时间的演化和产生了分化。食物丰饶的地方,往往是那些行动迟缓、捕食距离短的捕食者待的地方。快速行动、捕食距离长的"游侠"们,则在整个屏幕上横冲直撞。

3 一些有关的设想

或是我太懒,或是其他事太多。我在这个程序上忙活了一阵后,就没有再搞下去。但不管是当时,还是其后,总还是积累了很多没有实现的设想的。

当时仅仅考虑了单体分裂生殖,如果能考虑性别的差异,实现两性生殖就更实际了。在此基础上就能模拟基因的交换、重组,以及这期间的变异等。

其实现在还只有一类捕食者,虽然每个捕食者的基因、行为都有差别。可以考虑各种有很大或本质差别的捕食者。在此基础上,就能考虑各种捕食者之间的捕食关系,从而实现复杂的食物链了。

在屏幕显示上太简单粗糙了。如果能画出卡通式的生物体,其颜色、肢体的各个部分还能因为各自的不同而在屏幕上有所不同,那视觉效果就好多了,但这样的实现也太费功夫了。

在程序实现上,现在也该考虑使用Windows下的编程工具了,毕竟要比DOS下的好得多。Delphi、Java、VC++等都是不错的选择。VB就免了吧,既不是真正面向对象的,程序效率又太差。还有,Windows下还能方便地设计控制面板,观察和更改程序中大量的参数,这在DOS下做都是很麻烦的。

另外,我一直在考虑,对这个生命游戏来说,十字链表是不是一种好的数据结构?我理想中的情况是,最好来个大大的改革。这些单元相互间是没有数据结构关系的,每产生一个单元,就动态地派生一个线程。生物单元之间的相互作用,完全靠这些进程间的消息来进行。

比如,由主程序(主线程)控制程序的前进,所有线程就绪后,发一个"准备进入下一步"的消息。然后,所有单元就根据自己的特有方法,准备好下一步的状态,但不马上更新,因为相邻生物可能还要查询自己的现有状态。准备完毕,向主控线程发出信号。主控程序收到所有线程都准备完毕的信号,就发"进入到下一步"的消息,所有单元就更新到刚才准备好的状态,并在屏幕上刷新自己的"形象",再发出"刷新完毕"的消息。主线程收到所有单元的"刷新完毕"信号后,再发"准备进入下一步"的消息,又是一个周期。

这其实也是一种更贴近自然的实现方法。

我认为,这个想法虽然不错,但必定对系统有很高的要求。大量的线程将耗用很多内存(虽然说线程要比进程省多了),并且使程序的执行速度大大降低,肯定要比十字链表、矩阵等常规数据结构慢得多。程序的编制、调试也将更为复杂。

(本文是我以前写的,现作少量修改之后贴于blog)

生命游戏的计算机程序,生命游戏,算法与实现相关推荐

  1. JavaScript实现跳跃游戏的贪婪方法的算法(附完整源码)

    JavaScript实现跳跃游戏的贪婪方法的算法(附完整源码) greedyJumpGame.js完整源代码 greedyJumpGame.test.js完整源代码 greedyJumpGame.js ...

  2. 拼图游戏和它的AI算法

    写了个拼图游戏,探讨一下相关的AI算法.拼图游戏的复原问题也叫做N数码问题. 拼图游戏 N数码问题 广度优先搜索 双向广度优先搜索 A*搜索 游戏设定 实现一个拼图游戏,使它具备以下功能: 1.自由选 ...

  3. 游戏开发中常用的算法

    内容会持续更新,有错误的地方欢迎指正,谢谢! 1.与数组相关的算法: 快速排序(分治思想的应用):不是任何情况都适用,数据量小的话,还不如冒泡快,但快排的确很优秀. 堆排序:可用于做游戏排行榜前多少多 ...

  4. 纸牌游戏洗牌发牌排序算法设计

    纸牌游戏洗牌发牌排序算法设计 本文提供纸牌游戏设计制作的基础部分,即洗牌,发牌,牌张排序排列显示的算法. 以及游戏开始时间使用时间的显示.我是用简单的C语言编译器MySpringC在安卓手机上编写的. ...

  5. 【Qt象棋游戏】07_人机博弈算法开端

    文章目录 01 - 人机博弈算法简述 02 - 相关成员与方法 03 - 获取电脑棋子能走路径 04 - 电脑走棋 05 - 总结 01 - 人机博弈算法简述   前面详细介绍了棋盘类的封装.棋子类的 ...

  6. C语言实现三子棋游戏 代码+思路+电脑下棋算法

    C语言实现三子棋游戏 代码+思路+电脑下棋算法 重点: 当检测到电脑已经两子连续时,将会尝试获得胜利. 在检测到玩家即将胜利(连城两子)时,进行拦截 尝试胜利的优先级高于拦截,意味着如果玩家不能再下一 ...

  7. 游戏与常用的五大算法---上篇

    前言: 什么时候,我们之间竟然变得这么生疏 什么时候,我想见到你,却又害怕见到你 什么时候,才能在我身边,告诉我.其实,你一直都在 -----------<仙剑奇侠传> PS:为了方便大家 ...

  8. c语言消消看算法,论消去游戏中的数据结构与算法.doc

    论消去游戏中的数据结构与算法 PAGE PAGE PAGE 32 摘要: 近年来,随着经济的日益发展,人们的生活水平不断提高,生活质量也在渐渐的改善.适当的游戏对人们的业余生活是不可必缺的.说到游戏, ...

  9. _28LeetCode代码随想录算法训练营第二十八天-贪心算法 | 122.买卖股票的最佳时机II 、55.跳跃游戏、45.跳跃游戏II

    _28LeetCode代码随想录算法训练营第二十八天-贪心算法 | 122.买卖股票的最佳时机II .55.跳跃游戏.45.跳跃游戏II 题目列表 122.买卖股票的最佳时机II 55.跳跃游戏 45 ...

最新文章

  1. Oracle中的系统权限管理
  2. 读书笔记:理论生态学原理及应用(一)——合作的机制
  3. 人工神经网络发现生物神经网络,智源超高清电镜图像分割挑战赛开赛
  4. [AHOI2005]COMMON 约数研究
  5. IPSEC的NAT兼容性
  6. socket编程学习笔记
  7. python pp模块_Python模块--Pexpect
  8. 乱中有变,云原生从“大爆发”说起 | CSDN人物志
  9. 为什么我们需要Q#?
  10. android调用webservice发送header身份验证不成功
  11. 仿复制粘贴功能,长按弹出tips的实现
  12. dsp c语言流水灯程序,DSP流水灯源程序
  13. MapGIS K9如何裁剪瓦片数据
  14. java阶段测试A卷含答案
  15. 银行利率bps是什么意思,贷款利率bps是什么意思
  16. 开源的驰骋工作流程引擎,工作流程管理系统,表结构与运行机制。
  17. 大学生如何合理利用计算机,大学生如何安排自己的课余时间?6招,学霸教会你正确使用手机...
  18. JS(JavaScript)中实现深浅拷贝的几种方式(详细阅读 非常重要)。
  19. Expert C Programming 阅读笔记(CH2)
  20. 网络维护类岗位做什么?

热门文章

  1. java timeunit_java11教程--类TimeUnit用法
  2. swoft2.x swoftCli 自动重启服务
  3. 2010星网锐捷软件方面笔试题
  4. simulink 储能二次调频,风储调频,风火水储联合二次调频
  5. (3.2)常用知识-字符串处理
  6. 在游戏中看状态机与状态模式
  7. 随机生成卡号,并要求唯一
  8. PS196 DP2.0转HDMI2.18K设计方案|替代PS196芯片|GSV6201可完全替代兼容 PS196
  9. java虚拟主机好少
  10. 神州数码易飞ERP上线总结