突然心血来潮,想去做一个游戏项目,就去尝试做生命游戏,这是我的第一个游戏项目,看看成果。

目录

生命游戏的介绍

来源

生存定则

编写代码

首先是对于设计的选择

其次是定义

然后是初始化

之后是生存定律

最后是显示

总代码

问题

问题一:光标

问题二:闪屏

个人反思


生命游戏的介绍

来源

生命游戏(Life Game)是由3条规则构成的二维元胞自动机(2D Cellular Automata), 它最早被设计和发现于 1970年, 由是英国数学家约翰·何顿·康威发明,很多好奇的计算机爱好者都写过这个程序并让这些人造生物繁殖在自己的计算机上。

生命游戏事实上并不是通常意义上的”游戏”, 它没有游戏玩家各方之间的竞争, 也谈不上输赢,可以把它归类为“仿真游戏”。事实上,也是因为它模拟和显示的图象,看起来颇似生命的出生和繁衍过程而得名为“生命”。游戏在一个类似于围棋棋盘一样的,可以无限延伸的二维方格网中进行。例如,设想每个方格中都可放置一个生命细胞,生命细胞只有两种状态:“生”或“死”。用黑色的方格表示该细胞为“生”, 空格表示该细胞为“死” 。游戏开始时, 每个细胞可以随机地(或给定地)被设定为“生”或“死”之一的某个状态, 然后,再根据某种规则(生存定律)计算下一代每个细胞的状态:

生存定则

我们可以规定如下的生存定律

1.每个细胞的状态由该细胞及周围八个细胞上一次的状态所决定;

2. 如果一个细胞周围有3个细胞为生,则该细胞为生,即该细胞若原先为死,则转为生,若原先为生,则保持不变;

3. 如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;

4. 在其它情况下,该细胞为死,即该细胞若原先为生,则转为死,若原先为死,则保持不变

编写代码

(1)对于设计的选择

即是要怎么去玩这个游戏,是要主动一点还是要被动一点,或者有主动又有被动。最后迫于能力有限,选择了被动一点,也就是相当看一个动图的一样,看细胞的生与死的过程。

(2)定义

我定义一个细胞的二维空间,我用A和B表示长和宽的范围,但很快就发现它们代表二维的长和宽并不合适,因为空间是有限的,总会有边界,于是我打算在这个二维空间里创造一堵围墙,它们的值不会改变。另外为了方便计算,我将细胞的单位的数据类型定义为整型,用零和1去赋值,1代表活细胞,0代表死细胞。于是就有了如下定义

#define A 27#define B 27int cell[A][B]={0};

现在围墙的作用还没体现出来,要在后面的生存定则才能看到它的作用。

(3)初始化

有三个方案可以选,第一是产生随机数量的随机位置的活细胞,第二是每个细胞单位都有一定随机概率产生活细胞,第三是再创建一个细胞空间,里面全是活细胞,然后和之前创建的全是死细胞的空间进行随机交换。

我最后选择了第一个,因为以上方案都是存在随机性,都是要用到rand函数,但第一个比较简单,并且让最开始的细胞空间都是死细胞,然后再随机产生活细胞。也就是如下代码

void begin(){int num;srand(time(NULL));num = rand() % (A - 2) * (B - 2) + 1;//随机产生一个1到(A - 2) * (B - 2) + 1之间的数,闭区间,产生随机循环次数numfor (int i = 0; i < num; i++)//将循环随机num次随机赋值{cell[rand() % (A - 2) + 1][rand() % (B - 2) + 1] = 1;//两个随机产生的数的范围是细胞空间围墙内的长度和宽度}}

(4)生存定律

根据上面的介绍,可以知道,如果细胞周围的活细胞数是3,则该细胞一定是活细胞,如果该细胞周围的活细胞数是2,则该细胞状态不变,如果细胞周围的活细胞数为0,1,4,5,6,7,8,则该细胞一定是死细胞。然后就有个问题,要怎么计算某个细胞单位周围的活细胞数量?我选择了创建一个和细胞空间大小相同的二维空间,其中每个都一一对应,然后求每个以每个细胞空间为中心的周围也就是空心九宫格的活细胞的和的历遍,让新的空间都标记了对应细胞空间周围活细胞的数量,也就是像下面一样。

void rule(){int sum[A][B] = { 0 };int count;int i, j;for (i = 1; i < A- 1; i++){ for (j = 1; j < B - 1; j++){count = 0;count = cell[i + 1][j] + cell[i + 1][j - 1] + cell[i][j - 1] + cell[i - 1][j - 1]+ cell[i - 1][j] + cell[i - 1][j + 1] + cell[i][j + 1] + cell[i + 1][j + 1];sum[i][j] = count;//统计活细胞数量}}for (i = 1; i < A - 1; i++)for (j = 1; j < B - 1; j++)switch (sum[i][j])//对照标记,执行规则{case 0:case 1:case 4:case 5:case 6:case 7:case 8:cell[i][j] = 0; break;//变成死细胞case 3:cell[i][j] = 1; break;//变成活细胞}}

其中那围墙就其作用了,那围墙都是零,但变化的是围墙内发生的,若没有围墙,在统计每一个一个细胞单位周围的活细胞数力遍时候,当统计到最外围时会发生越界的情况,因为空心九宫格的中心定位在最外围的时候周围不是完整的八个细胞空间,而那围墙就是让它的越界的部分变成了零,也就让空心九宫格完整了,解决了越界的情况,但在显示的时候不要把围墙打印出来,因为那里并不会发生任何变化,看不出细胞的活的状态。

(5)显示

我选择了方格为一个单位,活细胞是白色,死细胞是蓝色,用\033这个转义符实现的。也就是

void show(){for (int i = 1; i < A - 1; i++){ for (int j = 1; j < B - 1; j++){if(cell[i][j]==0)printf("\033[36m■ \033[0m");//将死细胞变蓝打印,其中\033是一种转义符elseprintf("■ ");}printf("\n");}}

当然■是不能用键盘打出来的,但是在微软的输入法里面可以找到。再给■后面加个空格,因为换行的时候行与行之间有个空格的间隔,这样打印的完全之后整体就是正方形了,最后用system(“cls”)来刷新控制台和Sleep来让演化速度慢一点,好让我看清变化。这两个函数可以让画面舒服一点。

总代码

#include#include#include#include #define A 27#define B 27int cell[A][B] = { 0 };void begin(); void show(); void rule();int main(){begin();while (1){system("cls");rule();show();Sleep(250);}return 0;}void begin(){int num;srand(time(NULL));num = rand() % (A - 2) * (B - 2) + 1;for (int i = 0; i < num; i++){cell[rand() % (A - 2) + 1][rand() % (B - 2) + 1] = 1;}}void show(){for (int i = 1; i < A - 1; i++){for (int j = 1; j < B - 1; j++){if (cell[i][j] == 0)printf("\033[36m■ \033[0m");elseprintf("■ ");}printf("\n");}}void rule(){int sum[A][B] = { 0 };int count;int i, j;for (i = 1; i < A - 1; i++){for (j = 1; j < B - 1; j++){count = 0;count = cell[i + 1][j] + cell[i + 1][j - 1] + cell[i][j - 1] + cell[i - 1][j - 1] + cell[i - 1][j] + cell[i - 1][j + 1] + cell[i][j + 1] + cell[i + 1][j + 1];sum[i][j] = count;}}for (i = 1; i < A - 1; i++)for (j = 1; j < B - 1; j++)switch (sum[i][j]){case 0:case 1:case 4:case 5:case 6:case 7:case 8:cell[i][j] = 0; break;case 3:cell[i][j] = 1; break;}}

原本以为很顺利的,但是……

问题

问题一:光标

光标会出现在控制台上有点不美观。

我去找了一些解决办法,最后发现用如下代码可以隐藏光标

void HideCursor(){CONSOLE_CURSOR_INFO cursor_info = { 1,0 };SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);}

将其调用在main函数内第一行就行了。

问题二:闪屏

在运行的时候会莫名的闪屏,我去找了一些文章,出现闪屏的原因是因为system(“cls”)这个函数和循环语句产生的时间差,我看到有个双缓冲技术可以解决这个问题,奈何个人能力有限,并没有掌握这个技术,所以这个是我目前没有办法解决的问题。

个人反思

上面的代码设计其实个人是不满意的,首先是存在闪屏的问题不能解决,其次是范围有限,最后是功能单一且被动。但总体上可以看到生命游戏的过程,非常精彩,我其实还想过能不能将它三维化,但奈何本人能力有限,并不能实现我的想法。总之,还是要多多思考,创作和学习,未来还有很长的路要走。