二维元胞自动机和生命游戏(可自定义规则)

来源:互联网 发布:做淘宝代理需要多少钱 编辑:程序博客网 时间:2024/05/09 16:17

被公司劝退后这几天没事,把以前写的一个生命游戏的程序拿来优化了一下,去繁就简,把一些杂而不精的功能全部砍掉,而优化了主要部分

关于 元胞自动机 和 生命游戏 的概念:

http://baike.baidu.com/link?url=WytIcqFp2MuRgyTCX1BPsMiFlW8JFwqAQLpefKtobf6nzHVfoLEfOnG7yRSbj17K

http://baike.baidu.com/view/162057.htm?fr=wordsearch


以上的百科链接,我就不再赘言了

这个程序呢,最初也是我从网上考过来的,之前自己做的 用一个子窗口表示一个 cell,结果几百个就卡成狗了

后来看别人写才恍然大悟,就一个窗口,一张图,一个点(Rectangle)画不同的颜色表示该点 cell 的不同状态

整张图画好了后直接Bitblt 到客户区DC 中,那比我的快了几十万倍不止


不过最初的程序是链表结构的,链里存放的 活细胞 的坐标,流程是:

一个 m x n 的矩阵存放了所有 细胞, 一个长度为 L 的链表里存放了当前所有的 活细胞

Next:计算下一代

遍历链表,根据活细胞的坐标,将每个 活细胞 周围8个细胞 的相邻活细胞数加1

clear 链表, 然后

遍历矩阵,检查每个坐标上细胞的相邻活细胞数,符合 生存条件 的值为1(true), 符合 死亡条件 的值为0(false), 其余的保持原值不变

并且若该坐标细胞值为1,则push入链表

Display:显示

重置CImage 变量, 遍历链表,根据坐标画矩形,显示到客户端


根据这种算法,如果矩阵很大很大,但是只有一小部分 活细胞 的话,可以在计算下一代时节省性能(但是最多1/2)

而且因为链表结构不方便移除中间节点,所以我就改成用(m*n)大小的一维数组保存细胞了

并且数组的下标就是细胞的坐标

计算下一代时,遍历整个数组,若为 活细胞 则处理, 否则跳过(其实只遍历而不做其他计算的话,数组要快的多),代码如下:

void Automation::ToNextInCpu(uint times){static std::vector<int> tmpCells(m_CountOfCells, 1);static int *pTmpCells = &tmpCells[0];while (times--){for (uint i = 0; i < m_CountOfCells; ++i){if (m_pCells[i]){for (int j = 0; j < 8; ++j){pTmpCells[m_NeighbourTable[j + i * 8]] <<= 1;}}}for (uint i = 0; i < m_CountOfCells; ++i){if (pTmpCells[i] & m_ConditionOfLiving)m_pCells[i] = 1;else if (pTmpCells[i] & m_ConditionOfDeath)m_pCells[i] = 0;pTmpCells[i] = 1;}}}

 m_ConditionOfLiving,m_ConditionOfDeath 是细胞 存活\死亡的条件,其中:

m_ConditionOfLiving = 0 0000 1000 (二进制,表示若周围有三个 活细胞,则该细胞存活)

m_ConditionOfDeath = 1 1111 0011 (表示若周围有 0,1,4,5,6,7,8 个活细胞时,该细胞死亡)

其中周围有 2个活细胞时的情况表示维持原值不变


m_pNeighbourTable 是初始化时确定好了的存放每个细胞 8个相邻细胞位置的表,大小为(m x n x 8),这样就不用每次Next时都重复计算了:

void Automation::InitNeighbourTable(void){m_NeighbourTable = new uint[m_CountOfCells * 8];for (uint y = 0; y < m_CountOfVerticalCells; ++y){uint top = (y == 0) ? m_CountOfVerticalCells - 1 : y - 1;uint bottom = (y == m_CountOfVerticalCells - 1) ?  0 : y + 1;for (uint x = 0; x < m_CountOfHorizontalCells; ++x){uint* cell = &m_NeighbourTable[(x + y * m_CountOfHorizontalCells) * 8];uint left = (x == 0) ? m_CountOfHorizontalCells - 1 : x - 1;uint right = (x == m_CountOfHorizontalCells - 1) ? 0 : x + 1;cell[0] = top * m_CountOfHorizontalCells + left;cell[1] = top * m_CountOfHorizontalCells + x;cell[2] = top * m_CountOfHorizontalCells + right;cell[3] = y * m_CountOfHorizontalCells + left;cell[4] = y * m_CountOfHorizontalCells + right;cell[5] = bottom * m_CountOfHorizontalCells + left;cell[6] = bottom * m_CountOfHorizontalCells + x;cell[7] = bottom * m_CountOfHorizontalCells + right;}}}

然后弄完这些,我以为会性能大幅提高,结果根本没反应,搞的我还以为Next 的算法问题,还用C++AMP 并行算法改了一遍,结果还是没多大区别

而且GPU加速的话数据从显存复制回内存会不完善(BUG吧),无奈,放弃,但还源代码里还保存着,感兴趣的可以自己看(应该没有BUG,但是C++AMP的资料太少,我找不到原因所在)

结果后来吧程序开启的Aero特效一关,倒是提高了20%多,于是我想我知道性能瓶颈在哪了


一次 Next + Display 的花费的时间大概是10ms,但是单次Next的时间微乎其微,估计只有2ms左右吧,大部分时间是花费在Display 上

而且11ms 多一次连续不停的运行的话,人眼也分辨不清楚,于是我偷工减料的改成 N*Next + 1*Display,即计算几代才显示一次

这样画面有些许间隔,但是计算性能确实提升了很多很多

比如 17 * Next + 1 * Display  的话,平均下来的性能是 1200ms / 每千次, 比之前的11ms  提升了10倍

这样画面的间隔看起来就先是超高速运行的残像,而不会有什么破绽


不过随着 N 提升,性能提升也逐渐下降, 比如 33 * Next + 1 * Display 也才 1050 ms / 每千次, 提升有限

而且图像看起来别扭,得不偿失


程序的结构是主线程管界面, 工作线程主要负责 Next + Display

一开始本来想将Display 的任务交给主线程,后来发现性能提高不大,还要考虑重入问题,于是放弃

源代码在下面链接里, 我是win8 + VC2012 做的, 程序不能在xp运行,不过是静态链接的不需要MFC 的DLL

性能部分最理想的就是 1.3ms 左右 ,已经不能再快了,当然如果能再想办法再快一点又不损失太多的话,还请分享一下


如下图,矩阵是有限无界的,有各种辅助功能,翻转,平移等,加速,还有最重要的,有图标


附带的DLL里面内置了一些图形,支持不同方向,原理很简单,源码一看便知,另外选项对话框里可以自定义规则

下载链接:

http://download.csdn.net/detail/leeroe/7261059

0 0
原创粉丝点击