生命游戏,又称为康威生命游戏,是一个零玩家游戏,由英国数学家约翰·康威在1970年发明。这个游戏虽然简单,却能够展示出复杂而有趣的行为模式。本文将介绍如何使用Visual Studio进行生命游戏的编程实现,包括Direct2D的使用,以及在不同Windows版本上的兼容性处理。
要编译这个游戏,需要安装Visual Studio2019或更新的版本。运行编译后的二进制文件,则需要安装“Visual Studio 2015-2019 Runtime”的32位或64位版本。
这个应用程序具有以下特性:
维基百科上关于Conway's Game of Life
的文章描述了细胞生成的规则。游戏以JSON文件形式存储,基本上是文本,可以使用记事本创建自己的细胞形态,并测试它能存活多久。简单地编辑一个保存的游戏,然后重新加载并运行它。
Direct2D是微软的新型2D API,用于高性能的2D图形渲染。渲染由GPU使用3D原语完成,而GDI在Vista及以上版本中仅使用CPU。
快速计算新一代的基本思想是为内部和外部细胞范围使用不同的lambda表达式。因此,用于外部细胞范围的lambda表达式,由于需要额外的范围检查,只应用于一小部分细胞。
最快的方法可能是通过一个“不可见”的边界来扩大细胞区域。这样就不需要范围检查了,但是初始化和绘制的函数会更复杂,认为代码也会更不清晰。
下面,将解释函数及其工作原理。
auto generate = [&](int x, int y, int neighbours) -> BYTE {
BYTE cs = cells[y][x];
if (cs != Living) {
if (neighbours != 3) {
if (cs == RealDead)
return RealDead;
static const BYTE LifeChangeFades = CChildView::FadeSteps / 2;
static_assert(((int)FadeLast - LifeChangeFades) > Living,
"Last fade count must be greater than Living");
lifechange = lifechange || cs >= (FadeLast - LifeChangeFades);
return cs + 1; // next fade level
}
} else {
if (neighbours < 2 || neighbours > 3)
return FadeStart; // new dead ones
}
return Living;
};
这个generate
lambda用于应用康威规则,返回细胞的新状态:新状态可以是Living
(=0)或其中一个死亡级别FadeStart
...RealDead
。
使用行指针仍然具有二维数组访问的最佳速度。没有使用vector
来存储细胞数组。也没有使用大小为cx*cy
的vector
cellData,然后使用索引数学cellData[y*cx+x]
。分配了cellData
并使用行指针向量cells
来访问数据:
cellData.resize(size, RealDead);
cellData2.resize(size, RealDead);
cells.resize(cy);
cells2.resize(cy);
for (int y = 0; y < cy; ++y) {
cells[y] = cellData.data() + y*cx;
cells2[y] = cellData2.data() + y*cx;
}
在代码中,可以尝试不同的#define
定义,以实现以下可能性:
ChildView.cpp
中注释掉#define TIMING
,以测量创建新一代和绘制的时间,并将它们显示在状态栏上。ChildView.h
中注释掉#define USED2D
,以使用GDI而不是Direct2D进行绘制(未经充分测试,没有人口图表,且较慢!)。ChildView.cpp
中注释掉#define GDI_IMMEDIATE_DRAW
,仅在OnPaint
例程中使用GDI进行绘制(比上述方法更慢!)。AFX_WM_DRAW2D
时,绘制场景。AFX_WM_RECREATED2DRESOURCES
。Direct2D渲染目标丢失的情况可能发生在更换显卡时,但更重要的是,当锁定电脑然后解锁后。这意味着MFC已经重新创建了使用的所有Direct2D对象,因为它们与渲染目标相关联。但在此之后,必须触发重绘操作,简单地在消息处理程序中重绘并不起作用。必须发布一个用户消息,在处理这个消息时,重新绘制游戏场地。AFX_WM_RECREATED2DRESOURCES
处理程序,看看它是如何工作的。EndDraw()
返回D2DERR_RECREATE_TARGET
,也需要重新创建使用的Direct2D对象。CRenderTarget::Recreate(*this)
,MFC就会为重新创建资源。