Dotcpp  >  编程教程  >  C/C++游戏类项目  >  C语言生命游戏教程与源码(easyX版)

C语言生命游戏教程与源码(easyX版)

点击打开在线编译器,边学边练

一、项目介绍

这是我曾经在某个科普书上看到过的内容。在那本书当中有一个关于世界本源的讨论,即我们目前发现的物理定律,是否可能是更基础的模块的宏观呈现?因此,那本书提到了这个游戏,即设定一个简单的世界法则,以及对应的初始条件,然后观察它的演化,看看是否能呈现出某种更复杂的东西。

这个世界的法则十分简单。所有物质分为活细胞和死细胞,分别表示为绿色以及灰黑色。如果上一刻,一个活细胞周围8格的活细胞多余3个或少于2个,那么它就会死去;如果一个死细胞周围恰好有3个活细胞,那么就会在此诞生一个新的活细胞。设定了这样的法则之后,我们就可以观察这个世界的演化了。

 编译环境:visual c++ 6.0

第三方库:Easyx2022  注意需要提前安装easyX,如没有基础可以先了解easyX图形编程

 

二、运行截图


 

游戏画面1


游戏画面2


三、源码解析

程序的整体逻辑十分简单。

初始化;

然后开始循环;

如果按下1-9,调节游戏速度;

如果按下s,产生一个方形分布世界,重新开始;

如果按下r,产生一个随机分布世界,重新开始;

进行一回合的演化,绘制图像;

睡眠一定时间,继续循环;

游戏结束。

 

变量和函数也不多:

// 定义全局变量
__int8 world[102][102] = {0};          // 定义二维世界
IMAGE imgLive, imgEmpty;              // 定义活细胞和无细胞区域的图案
 
 
// 函数声明
void Init();                                             // 初始化
void SquareWorld();                           // 创建一个细胞以方形分布的世界
void RandWorld();                              // 创建一个细胞随机分布的世界
void PaintWorld();                              // 绘制世界
void Evolution();                                  // 进化

接下来我们看每个函数的实行:

// 初始化
void Init()
{
         // 创建绘图窗口
         initgraph(640,480);
 
         // 设置随机种子
         srand((unsigned)time(NULL));
 
         // 调整世界图案的大小
         Resize(&imgLive,  4, 4);
         Resize(&imgEmpty, 4, 4);
 
         // 绘制有生命世界的图案
         SetWorkingImage(&imgLive);
         setcolor(GREEN);
         setfillstyle(GREEN);
         fillellipse(0, 0, 3, 3);
 
         // 绘制无生命世界的图案
         SetWorkingImage(&imgEmpty);
         setcolor(DARKGRAY);
         rectangle(1, 1, 2, 2);
 
         // 恢复对默认窗口的绘图
         SetWorkingImage(NULL);
 
         // 输出简单说明
         setfont(24, 0, "黑体");
         outtextxy(254, 18, "生 命 游 戏");
         RECT r = {440, 60, 620, 460};
         setfont(12, 0, "宋体");
         drawtext("生命游戏简介:\n,
                  &r, DT_WORDBREAK);
 
         // 产生默认的细胞以方形分布的世界
         SquareWorld();
}

initgraph用于初始化绘图窗口。在头文件Easyx当中,后面的函数如果没有说明,默认也是在Easyx当中的。

Resize用于调整指定绘图设备的尺寸,避免绘制图像过大。

然后绘制有生命的图案和无生命的图案,其原理类似:

SetWorkingImage用于设定当前的绘图设备,将数据写入对应地址位置。

setcolor用于设置绘图前景色,setfillstyle用于设置当前设备填充样式。

fillellipse这个函数用于画有边框的填充椭圆。

接下来输出说明:

setfont设置字体

outtextxy用于在特定位置输出文字

RECT r用于为drawtext设定一个指定矩形区域的指针

drawtext这个函数用于在指定区域内以指定格式输出字符串。

 

然后是两个创建世界的函数。

// 创建一个细胞以方形分布的世界
void SquareWorld()
{
       memset(world, 0, 102 * 102 * sizeof(__int8));
 
       for(int x = 1; x <= 100; x++)
              world[x][1] = world[x][100] = 1;
 
       for(int y = 1; y <= 100; y++)
              world[1][y] = world[100][y] = 1;
}
// 创建一个细胞随机分布的世界
void RandWorld()
{
       for(int x = 1; x <= 100; x++)
              for(int y = 1; y <= 100; y++)
                     world[x][y] = rand() % 2;
}

利用二层循环遍历world[x][y]当中每个数据,将其调整到想要的值。随机世界将每个细胞的状态设置为随机数除以2的余数,保证每次产生的都是新的世界。

 然后是绘制世界图像。

// 绘制世界
void PaintWorld()
{
       for(int x = 1; x <= 100; x++)
              for(int y = 1; y <= 100; y++)
                     putimage(16 + x * 4, 56 + y * 4, world[x][y] ? &imgLive : &imgEmpty);
}

利用双层循环,将每个点的状态所对应图像打印出来。

 最后是世界的演化函数,从世界的当前状态推出世界下一刻。

// 进化
void Evolution()
{
       __int8 tmp[102][102] = {0};        // 临时数组
       int sum;
 
       for(int x = 1; x <= 100; x++)
       {
              for(int y = 1; y <= 100; y++)
              {
                     // 计算周围活着的生命数量
                     sum = world[x+1][y] + world[x+1][y-1] + world[x][y-1] + world[x-1][y-1]
                            + world[x-1][y] + world[x-1][y+1] + world[x][y+1] + world[x+1][y+1];
 
                     // 计算当前位置的生命状态
                     switch(sum)
                     {
                            case 3:           tmp[x][y] = 1;                      break;
                            case 2:           tmp[x][y] = world[x][y];        break;
                            default:   tmp[x][y] = 0;                      break;
                     }
              }
       }
 
       // 将临时数组恢复为世界
       memcpy(world, tmp, 102 * 102 * sizeof(__int8));
}

我们只需要定义一个临时数组,根据当前的世界状态推断就可以了。计算某个坐标周围8格活着的生命数量,得出该点的状态。注意数组的边界。

之后用memcpy将临时数据输入world变量当中。

memcpy是<string.h>当中的内存拷贝函数,可用于复制任何种类的数据。

// 主函数
int main()
{
       Init();
       int Speed = 500;                // 游戏速度(毫秒)
 
       while(true)
       {
              if (kbhit() || Speed == 900)
              {
                     char c = getch();
 
                     if (c == ' ' && Speed != 900)
                            c = getch();
 
                     if (c >= '0' && c <= '9')
                            Speed = ('9' - c) * 100;
 
                     switch(c)
                     {
                            case 's':
                            case 'S':
                                   SquareWorld();      // 产生默认的细胞以方形分布的世界
                                   break;
 
                            case 'r':
                            case 'R':
                                   RandWorld(); // 产生默认的细胞随机分布的世界
                                   break;
 
                            case VK_ESCAPE:
                                   goto END;
                     }
              }
 
              Evolution();                  // 进化
              PaintWorld();                // 绘制世界
 
              if (Speed != 900)         // 速度为 900 时,为按任意键单步执行
                     Sleep(Speed);
       }
 
END:
       closegraph();
       return 0;
}

最后放一个主函数。

函数 kbhit()函数的作用是检查控制台窗口的按键是否被按下。

closegraph()用于关闭绘图区域。

 

四、完整源码

生命游戏C语言完整源码


本文固定URL:https://www.dotcpp.com/course/1231

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

趣味项目教程
第一章 C/C++游戏类项目
第二章 C/C++工具及其他类项目
第三章 Python趣味项目
Dotcpp在线编译      (登录可减少运行等待时间)