使用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个规则:
- 如果一个白色单元周围有3个单元为黑,则该单元为黑——生命的繁殖
- 如果一个黑色单元周围黑色单元少于2个或多于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开源。开源链接

浙公网安备 33010602011771号