使用C++开发生命游戏

生命游戏,全称康威生命游戏。我不啰嗦,你自己百度。昨天看到知乎上的一篇文章,感觉里面提到的生命游戏挺好玩。生物老师之前总是感叹大自然的神力、身体里结构的精妙,我当时就觉得如此有智慧的结构像是智慧生命设计出来的,其实这全是优胜劣汰的结果。

生命游戏也算是进化论的佐证之一吧,它说明了精密的生命机器是完全可以自行演化形成的。我回头看了看自己那点可怜的C++知识,突然有了一种我可以做出生命游戏的迷之自信。

好了废话说完了答辩开始了。

首先是头文件,我讨厌头文件,因此只有两个:万能头和能加载用来延时的Sleep()函数的头文件。

#include <bits/stdc++.h>
#include <windows.h>

生命游戏在一个矩阵内进行,每个单元有两种状态:有生命/无生命。

为了好看地输出上述画面,我还真去看了一眼有没有相关的GUI,然后因为不愿付出相关的学习成本就直接放弃。

怎么办?请出老朋友命令行呗。我用█(白)表示有生命,两个空格(黑)表示没生命,每次输出前调用cmd命令system("cls");来清空输出,这样一来就能给人一种GUI的错觉。但实际效果不怎么好,还是有逐行输出的感觉,晃眼睛。但起码能用:

主程序效果

当然我并不打算直接把字符存起来,而是用二维数组,1表示有生命,0表示没生命,数组的起点是(1,1),这样可以防止探测周围单元死活时越界。输出函数代码如下:

system("cls");
flag=0;
for(int i=1;i<=h;i++){
	for(int j=1;j<=l;j++){
		if(a[i][j]==0){
			cout<<"█";
		}
		if(a[i][j]==1){
			cout<<"  ";
			flag=1;
		}
	}
	cout<<endl;
}
return flag;

flag是用来判定死没死绝的。未来可能还要判定是否陷入死循环,怎么实现我没想好,不过所谓死循环不就是生态平衡吗?可没有进化的生态平衡是没有意义的。

然后就是判定生命死活的函数。因为生命的死活是在判定后执行的,我又开了个二维数组b,根据a来判定,用b储存判定结果,本次迭代结束时把B给A,为输出及下次迭代准备。

生命游戏有3个规则:

  1. 如果一个白色单元周围有3个单元为黑,则该单元为黑——生命的繁殖
  2. 如果一个黑色单元周围黑色单元少于2个或多于3个,则该单元为白——生命密度需要维持适中
  3. 其余情况单元维持原状不变——生命的延续

非常好写,具体实现如下:

int n,b[102][102];
memcpy(b,a,sizeof(a));
for(int i=1;i<=h;i++){
	for(int j=1;j<=l;j++){
		n=a[i-1][j-1]+a[i+1][j+1]+a[i-1][j+1]+a[i+1][j-1]+a[i-1][j]+a[i+1][j]+a[i][j-1]+a[i][j+1];
		if(a[i][j]==1&&(n<2||n>3)){
			b[i][j]=0;
		}
		if(a[i][j]==0&&n==3){
			b[i][j]=1;
		}
	}
}
memcpy(a,b,sizeof(b));

最后是初始值的定义了,一个生命也没有是玩不起来的。一个一个坐标赋值很麻烦,生命要随机生成。

生成随机种子,调用rand(),%10把概率分为10档,通过调整random变量还可以自定义有生命的概率。

srand(time(NULL));
	for(int i=1;i<=h;i++){
		for(int j=1;j<=l;j++){
			if(rand()%10<random){
				a[i][j]=1;
			}
		}
	}

程序设定了一些可供用户调整的值,同时也设置了默认值。但是如果不输入就按回车的话程序会执着地让你输入,就很烦。为了把回车也当成输入的一种,我使用了gets()函数,它是个C库函数,会把输入的内容存到括号里的字符串中,讨喜的是遇到回车它会停止然后返回一个NULL。然而它只能输入字符串,于是我请来了stringstream(),它可以把括号内的字符串变为整型然后存到用>>指向的变量里,具体工作原理和用法我不太了解也没有研究,就这么草草了事了罢。代码如下:

char n[9];
int x;
gets(n);
stringstream(n)>>x;
return x;

完事儿!


然后我看了看有关生命游戏的文章,脉冲星、滑翔者、轻量级飞船、滑翔者枪、繁殖者……有这么多有意思的初始值,但一个一个坐标赋值太tm麻烦了!懒惰如我,我决定开发一个图像化初始值自定义界面。

对了,我不是不愿付出GUI的学习成本吗?

但是我懂点前端啊~

首先是网格的绘制,我使用<table>标签。这个程序一开始就支持用户自定义网格,因为不同人电脑屏幕的大小不同,所以图像化初始值自定义界面也得自定义网格。我使用innerHTML函数,通过JavaScript生成网格,具体代码如下:

hang=document.getElementById("inHang").value;
lie=document.getElementById("inLie").value;
var str="<table>";
var temp;
for(i=1;i<=hang;i++){
str+="<tr>";
	for(j=1;j<=lie;j++){
		temp="\""+i+"a"+j+"\"";
		str+="<td><input type=\"checkbox\" id="+temp+"><label for="+temp+"></label></td>";
	}
str+="</tr>";
}
str+="</table>";
document.getElementById("box").innerHTML=str;

你可能要问里面的<input>标签和<label>标签是干什么用的?

<input>采用了checkbox样式,即复选框,可以让用户轻松选择单元。但是复选框默认的样式太难看了,不够简洁,为此我引入了<label>,它可以自定义样式,并把用户对它的所作所为转嫁到复选框头上,然后我们隐藏复选框,这样一种李代桃僵式的图像化选择机制就诞生了。

最后加一个我最喜欢的:hover定义鼠标滑过时的效果,舒服~

图像化界面

为了让js知道用户选中了哪个单元,我为每个单元附上了id即纵坐标+a+横坐标的形式,然后多个坐标之间用b连接形成坐标代码,用户选中需要的单元格后点击复制坐标代码就会自动复制。自动复制的逻辑是创建一个含有坐标代码的文本框,选中文本框,复制。这不是很好的逻辑。

document.getElementById("lifeCopy").innerHTML=result;
document.getElementById("lifeCopy").select();
document.execCommand("copy");

复制好之后用户粘贴到那个C++程序里,C++进行解码并为选中的单元附上代表着生命与希望的“1”。

当然,以后想尝试C++监听剪贴板自动输入,不过也没想好怎么实现。

但总之:定义初始值变得容易多了呢!

然后肯定有人要问“你为啥一开始不用JavaScript编写程序呢?”

啊这……

它不配[doge]

程序在Gitee开源。开源链接

posted @ 2021-08-06 11:25  苦力怕水  阅读(533)  评论(0)    收藏  举报