桌面山寨版2048—逻辑篇之缓缓出现的细节像枫叶
既然最终目的是能够让博客成为我的主平台,那么必然要采用一些方式方法,所以,如果想看第三部分--优化篇,请猛戳我的博客吧。
二、桌面山寨版2048—逻辑篇之缓缓出现的细节像枫叶
上一篇已经介绍了如何靠键盘控制游戏方格的移动合并,看起来貌似这个被实现了就一切都搞定了,但是当我编译测试之后就发现了,离一个合格的山寨品还有很大的距离。让我从最简单的开始。
细节里最简单的就是分数如何增长的问题了,这个问题不难,只要稍微玩一下原来的逻辑就会发现,这个游戏的分数增长规则是这样的:当发生合并的时候才能取得得分,得分规则是两个方块的”数字值”之和,比如两个2合并,那么得分就会增长4分。得知这个规则之后,实现起来并不难,只要在昨天合并的代码里面按规则更新分数的值并且刷新界面就可以了。至于最高分,就更简单了,在每次移动的最后,将当前最高分和目前得分进行比较,如果当前得分高于最高分,则进行更新,反之,则不动。在代码里,这一段是这样的。
if(nChar == VK_DOWN) { for(int i=nTotalGrids-1;i>=0;i--) { if ( it.nRowIndex < m_nRowAndCol -1 ) { ItemBox itCurrent = m_itemBoxArray[GetIndexFromCoords(it)]; ++it.nRowIndex; ItemBox itNextRow = m_itemBoxArray[GetIndexFromCoords(it)]; if ( itNextRow.bShow && itNextRow.strItemText == itCurrent.strItemText && !itNextRow.bJoin ) { m_itemBoxArray[GetIndexFromCoords(it)].strItemText = m_arrStrItemTexts[GetIndex(itNextRow.strItemText)+1]; m_nScore += 2*pow(2.0, GetIndex(itNextRow.strItemText)+1 ); // 更新分数 m_itemBoxArray[GetIndexFromCoords(it)].bJoin = true; --it.nRowIndex; m_itemBoxArray[GetIndexFromCoords(it)].bShow = false; m_itemBoxArray[GetIndexFromCoords(it)].strItemText = _T(""); isMoved = true; } } } } }
第二个明显可以发现的问题是,我们还缺少如何能够判断游戏结束了。我最初的幼稚想法是直接判断是否全部游戏格子被填满了,若被填满了,则游戏结束。当我信心满满的实现然后测试以后, 又一次发现了自己的幼稚,如果仅仅是用上面叙述的逻辑,当所有方格被填满之后,不能保证相邻两个方格元素是不相同的。这样会导致游戏其实还可以移动,但是在判断上,你告诉用户游戏已经结束了。只要闭上眼睛稍微思考一下这个景象,并不难明白,如果不想闭上眼睛思考,就看下面我随手画的图吧:
这样,就不能仅仅依靠方格是否全部被填满这一标准来判断游戏是否结束,除了是否被填满,还要判断是否所有方格都不能移动,也就是对于每一个方格,其上下左右相邻方格元素都和当前方格不一样。我写了个函数,对于这个逻辑的实现大体是这样的。
bool CChildView::IsGameOver() { bool leftSame,rightSame,upSame,downSame; leftSame = rightSame = upSame = downSame =false; bool isFull = true; for(int i=0;i<m_nRowAndCol*m_nRowAndCol;i++) { isFull = isFull && m_itemBoxArray[i].bShow ; } if(!isFull) return false; for(int i=0; isFull && i<m_nRowAndCol*m_nRowAndCol;i++) { if ( !m_itemBoxArray[i].strItemText.IsEmpty() ) { leftSame = rightSame = upSame = downSame =false; if( i%m_nRowAndCol != 0 ) leftSame = ( m_itemBoxArray[i].strItemText == m_itemBoxArray[i-1].strItemText ); if( i%m_nRowAndCol != m_nRowAndCol-1 ) rightSame = ( m_itemBoxArray[i].strItemText == m_itemBoxArray[i+1].strItemText ); if( i/m_nRowAndCol !=m_nRowAndCol-1 ) downSame = ( m_itemBoxArray[i].strItemText == m_itemBoxArray[i+m_nRowAndCol].strItemText ); if( i/m_nRowAndCol != 0 ) upSame = ( m_itemBoxArray[i].strItemText == m_itemBoxArray[i-m_nRowAndCol].strItemText ); if (leftSame||rightSame||upSame||downSame) return false; } } return true; }
除了这两个细节,下面一个细节就不那么容易被发现了,如果你仔细去玩一下原版游戏,就会发现,并不是任何时候都会出现新的游戏方块了。最开始的时候,我忽视了这一点,在实现上,每当用户按下方向键的时候就会出现一个方块。最后被我一个玩游戏特别专业的朋友发现,原版游戏出现方块的逻辑是,只有当目前游戏区域所有方块产生了移动之后,才会产生一个新的方块,比如说在下图所示的情况下,按下“上左右”方向键都不会产生新的方块,只有再按下“下”的时候才能产生新的方块。
按照这个逻辑,在移动的代码中,我使用了一个变量isMoved来判断在按下一个方向键后,是否所有方块发生了移动,如果有,那么将这个变量赋值为true并产生一个新的方块。对这个变量进行判断,只有当前画面发生了移动之后才产生一个新的游戏方块。
下一个我其实不知道能不能称之为细节,因为它也是这个游戏逻辑本身的一部分。如果稍微注意一点,就会发现,其实游戏方块的随机出现不光是位置,还有就是随机出现第一级方块和第二级方块(2和4),但是出现2的概率远远大于4,所以说如何设计这样一个逻辑如果深入探讨还是很有意思的。其他的我不知道,但是关于随机数的生成本身在顺论里面就是一个很多人研究的分支,在我的设计里,我用了一个简单的不能再简单的办法。我使用了一个计数器,记录2出现的次数,如果已经连续超过10次,那么产生一个0-100之间的随机数,如果这个随机数能够被5整除,那么随机出现的方块就是“4”。
这些细节确实都很微小很微小,但是作为一个给别人玩的游戏,不注意这些细节,那么游戏肯定就不能被玩起来,因为规则不完整。而且,这样一个逻辑简单,规则并不复杂的小游戏,这些个细节显得尤为重要,所以要开发出一个完美的产品,绝对不是那么简单的。
除了这些,还有一个算不上细节的问题,就是如何部署你的游戏到别人的电脑里?最简单的办法是采用一个安装包,将需要的库,文件等等全部安装到别人电脑之中。但是这样貌似麻烦了点,如果只是单纯想给朋友帮忙测试测试,怎么办呢?将release方式编译出来的程序打个包直接拷给别人?你会发现很多时候别人都不能运行,哪怕是你把所有需要的dll都放在里面也不行,这个我推荐看看这一篇文章,可以基本保证你的程序在任何windows系统下都能运行。避免了交给别人之后,别人兴致冲冲的点击后不能打开。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述