算法 | 生命游戏 (Game of Life)

本文介绍生命游戏算法,通过C++代码模拟细胞自动机的演化过程,探讨不同初始条件下细胞群落的稳定形态。生命游戏由约翰·康威发明,通过简单规则揭示复杂动态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


概要: 本文将要介绍一个名为“生命游戏”的有趣算法并用C++进行简单的实现。
关键字: C++;生命游戏算法
 

1 背景说明

   生命游戏,又叫康威生命游戏(Conway’s Game of Life),或康威生命棋,是英国数学家约翰·康威(John Conway)在1970年发明的细胞自动机。它最初在1970年10月在《科学美国人》杂志上的“数学游戏”专栏出现。我大一暑假在上暑期课程时无意中接触到这个概念,并被它看似简单的外表、实则复杂的“内心”所吸引,于是出于好奇就自己用C++模拟了一翻,直到最近整理本科成果时又一次翻到了这个小巧有趣的代码。代码很短,只有一二百行,其中核心的判断和计算的部分只有十来行;其算法更是简单,几句话就可以描述清楚。下面我将做简要的介绍。

2 算法原理

2.1 概述

   生命游戏是一个零玩家游戏。它发生在一个二维矩形世界,这个世界的基本组成单位是一个一个等大的小方格。每个方格居住着一个活着的或死了的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的细胞的数量。一方面,如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;另一方面,如果周围活细胞过少,这个细胞会因太孤单而死去。而只有其周围的细胞数量适合时,这个方格中的细胞才能继续存活。若方格中原来的细胞已经死亡,在其周围细胞数量合适时,也会使得该死细胞“死而复生”,这可以认为是在模拟细胞的繁殖。玩家可以设定周围活细胞的数目怎样时才事宜细胞生存和繁殖。但是需要注意的是,如果这个数目设定过高,世界中的大部分细胞会因为找不到太多的活的邻居而死去,直到整个世界都没有生命;如果这个数目设定过低,世界中又会被生命充满而没有什么变化。

   实际游戏中,这个数目一般选取2或者3;这样整个生命世界才不至于太过荒凉或拥挤,而是出于一种动态平衡之中。如此一来,游戏的规则就是:当一个方格周围有2或3个活细胞时,方格中的活细胞在下一个时刻继续存活;即使这个时刻方格中没有活细胞,在下一个时刻也会“诞生”活细胞。否则,这个方格中的细胞会死亡,或不诞生新的活细胞。

2.2 规则

   在选取了合适的数据后,对具体规则作如下描述。

   生命游戏中,对于任意细胞:
   每个细胞有两种状态:存活或死亡。每个细胞与以自身为中心的周围八格细胞产生互动。
   1.当前细胞为存活状态时,当周围的活细胞低于2个时, 该细胞因孤独而死亡;
   2.当前细胞为存活状态时,当周围有2个或3个活细胞时, 该细胞保持原样;
   3.当前细胞为存活状态时,当周围有3个以上活细胞时,该细胞因资源匮乏而死亡;
   4.当前细胞为死亡状态时,当周围有3个活细胞时,该细胞变成存活状态(模拟繁殖)。

3 程序实现

   我用C++模拟了上述过程。此算法的核心在于计算下一代细胞的存亡图景,代码如下所示:

void NextG(int p[][MAXLIS],int q[][MAXLIS])               
{
	int i,j,x,y;
	int judge;
	for(i = 0;i < MAXROW;i++)
	{
		for(j = 0;j < MAXLIS;j++)
		{
			if(p[i][j] == ALIVE)
			{
				judge = -1;                         //每一轮结束judge都要复位
			}
			else if(p[i][j] == DEAD)
			{
				judge = 0;
			}
			for(x = MAX(i-1,0);x<=MIN(i+1,MAXROW-1);x++)     //开始这里的MAXROW和MAXLIS后面忘记-1,逻辑出错找了好久
			{
				for(y = MAX(j-1,0);y<=MIN(j+1,MAXLIS-1);y++)
				{
					if(p[x][y] == ALIVE)
						judge +=1;
				}
			}
			switch(judge)
			{
			case 2:q[i][j] = p[i][j];break;			// 周围有2个活细胞,保持原样
			case 3:q[i][j] = ALIVE;break;			// 周围有3个活细胞,生则继续,死则复活(模拟繁殖)
			default:q[i][j] = DEAD;break;			// 周围有1或4-8个活细胞,死(模拟拥挤而死或孤独而死)
			}
		}
	}
}

   代码本身可以说是相当简单了,学过数组的人都能写出来。剩下的就是一些装饰门面的活儿,比如说我当时写的测试代码——即main()函数——是长这个样子的:

int main()
{
	int CHOICE = 1,n = 0;
	int choice;
	int map[MAXROW][MAXLIS] = {DEAD},newmap[MAXROW][MAXLIS] = {DEAD};		//定义2个同维数组并初始化为全0数组	
	cout<<"_________________________________________"<<endl;
	cout<<"|                                        |"<<endl;
	cout<<"|    欢迎来到“生命游戏”!                  |"<<endl;
	cout<<"|                                        |"<<endl;
	cout<<"|    请选择初始化模式:                    |"<<endl;
	cout<<"|                                        |"<<endl;
	cout<<"|       1.自动模式A     2.自动模式B        |"<<endl;
	cout<<"|                                        |"<<endl;
	cout<<"|       3.随机模式      4.手动模式         |"<<endl;
	cout<<"|________________________________________|"<<endl;
	cin>>choice;
	switch(choice)
	{
	case 1:Init1(map);break;               //二维数组作为函数参数被调用时,参数是二维数组的起始位指针,也就是数组名
	case 2:Init2(map);break;
	case 3:Init3(map);break;
	case 4:Init4(map);break;
	default:
		{
			cout<<"默认为随机初始化模式."<<endl;
			Init3(map);
			break;
		}
	}            
	cout<<endl<<"父代细胞生存图:"<<endl;
	ShowMap(map);
	while(CHOICE)
	{
		NextG(map,newmap);
		CopyClc(map,newmap);
		cout<<endl<<"子"<<n++<<"代细胞生存图:"<<endl;
		ShowMap(map);
		cout<<"\n继续请按除0以外的任意数字键."<<endl;
		cin>>CHOICE;
	}
	cout<<"您已退出游戏."<<endl;
	return 0;
}

   这里我给出了4种初始化的条件供用户选择,同时为了让用户看清中间的每一个步骤,我让用户自己步进操作,也省了设置步进时长的麻烦,当然这些都是小细节。另外,很多人直接把这个例程写在了网页上,大家也可以去感受一下,推荐的网址有链接1链接2

4 结果分析

   代码跑通之后就可以随意玩耍了!运行的界面如图1所示:

图1 程序运行界面

 
   上图中,“#”表示活细胞,“-”表示死细胞。

   在测试的过程中,可以发现这样一些如下所介绍的稳定结果。

4.1 情形一

   当父代如图2所示时,子代运行到第24代即可保持稳定,如图3所示。

图2 情形一的父代细胞
图3 情形一的子代细胞至稳定时

4.2 情形二

   当父代如图4所示时,子代运行到第5代即可保持稳定,如图5所示。

图4 情形二的父代细胞
图5 情形二的子代细胞至稳定时

 
   值得注意的是,虽然子5代和子6代细胞的生存图不一致,但是以后所有子代的生存图都是子5代和子6代的循环,因此这也是稳定的。

4.3 情形三

   当细胞数量稍多一些,父代如图6所示时,子代运行到第9代即可保持稳定,如图7所示。

图6 情形三的父代细胞
图7 情形三的子代细胞至稳定时

 
   类似于情形一的“田”字型,这里的6个活细胞构成的空间结构也是稳定的。

4.4 情形四

   最后再尝试一次,父代如图8所示时,子代运行到第77代即可保持稳定,如图9所示。

图8 情形四的父代细胞
图9 情形四的子代细胞至稳定时

 
   情形四恰好包含了情形一到情形三的内容,当然也是稳定的。

5 后记

   “生命游戏”虽然是一个零玩家游戏,但却经久不衰。它一直吸引着大量的爱好者的视线,其中包含的乐趣当然不止我所描述的这些。只要上网一查就能找到许多有趣的讨论,比如知乎对生命游戏稳定图形的探讨、果壳网对于计算和宇宙的关系的专访报告以及哔哩哔哩YouTube上的一些有趣视频等,如果你有兴趣,此话题大有继续探讨的空间和价值。

   转载时务必注明来源及作者。尊重知识产权从我做起。

   代码已上传至网络,欢迎下载,密码是9hrg

<think>好的,我需要帮用户找到一个基于C++实现的康威生命游戏的代码示例,适用于DOCS环境。首先,我得回忆一下康威生命游戏的基本规则。规则是,每个细胞有存活或死亡两种状态,周围八个邻居中存活细胞的数量决定其下一个状态:存活细胞如果邻居数少于2或多于3则死亡,否则存活;死亡细胞如果正好有3个邻居则复活。 接下来,我需要用C++来实现这个逻辑。考虑到DOCS环境可能需要代码简洁且易于展示,所以应该避免复杂的图形界面,使用控制台输出比较合适。不过控制台是逐行输出的,传统的清屏可能会闪烁,可以用覆盖当前输出的方式,比如使用回车符`\r`或者移动光标,但不同终端支持可能不同,这里可能需要简化处理,直接打印新状态并换行。 然后,数据结构方面,使用二维数组来存储细胞的状态。为了方便计算邻居数量,可以创建一个辅助函数`countNeighbors`,遍历周围的八个位置,注意处理边界情况,比如使用取模运算让边界循环,或者固定边界。这里可能采用固定边界的处理,即边界外的细胞视为死亡,但用户可能希望循环边界,所以采用取模实现循环边界更符合传统规则。 接下来,实现更新函数`updateGrid`,根据当前状态生成下一代的状态。需要两个二维数组,一个存储当前状态,另一个存储下一代状态,避免在更新过程中覆盖当前状态的数据。 主函数部分,初始化一个示例的初始状态,比如滑翔机(glider)模式,然后进入循环,每次打印当前网格,更新状态,并添加适当的延时,比如使用`std::this_thread::sleep_for`来控制帧率。 然后测试代码是否符合DOCS环境的要求,确保没有依赖特定的图形库,只使用标准库。代码中使用`#include <iostream>`, `#include <vector>`, `#include <thread>`, `#include <chrono>`,这些都是标准库,兼容性较好。 最后,检查代码是否符合用户的格式要求,比如行内数学表达式是否用$...$,独立公式用$$...$$,但用户的问题主要是代码示例,所以这部分可能不需要数学公式,但需要确保代码正确性,语法正确,并附上相关问题和引用。 现在,我需要确保代码示例正确,没有语法错误,逻辑正确。比如,在打印网格时,正确遍历行和列,输出'■'代表存活,空格代表死亡。更新网格时,正确应用规则,并且使用两个网格来避免覆盖问题。此外,处理循环边界时,使用`(x + i + rows) % rows`来确保索引在有效范围内。 可能还需要考虑网格的大小是否合适,比如示例中的10x10网格,滑翔机初始位置是否正确。测试一下滑翔机是否会在网格中移动,每次迭代后状态是否正确更新。 最后,生成相关问题,比如规则的解释、性能优化、不同初始模式的影响,以及如何在图形界面中实现,确保问题与用户的需求相关,帮助用户深入理解生命游戏的实现和应用。</think>以下是一个基于C++实现的康威生命游戏控制台版本代码示例,适用于DOCS环境: ```cpp #include <iostream> #include <vector> #include <thread> #include <chrono> using namespace std; const int rows = 10; const int cols = 10; // 计算周围存活细胞数量 int countNeighbors(const vector<vector<bool>>& grid, int x, int y) { int count = 0; for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { if (i == 0 && j == 0) continue; // 跳过自身 int nx = (x + i + rows) % rows; // 循环边界处理 int ny = (y + j + cols) % cols; if (grid[nx][ny]) ++count; } } return count; } // 更新网格状态 void updateGrid(vector<vector<bool>>& grid) { vector<vector<bool>> newGrid = grid; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { int neighbors = countNeighbors(grid, i, j); if (grid[i][j]) { newGrid[i][j] = (neighbors == 2 || neighbors == 3); } else { newGrid[i][j] = (neighbors == 3); } } } grid = newGrid; } // 打印网格 void printGrid(const vector<vector<bool>>& grid) { system("cls"); // 清屏(Windows系统) for (const auto& row : grid) { for (bool cell : row) { cout << (cell ? '■' : ' ') << ' '; } cout << endl; } } int main() { vector<vector<bool>> grid(rows, vector<bool>(cols, false)); // 初始化滑翔机模式 grid[1][2] = true; grid[2][3] = true; grid[3][1] = true; grid[3][2] = true; grid[3][3] = true; while (true) { printGrid(grid); updateGrid(grid); this_thread::sleep_for(chrono::milliseconds(500)); // 延迟500ms } return 0; } ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值