桌面山寨版2048—游戏逻辑篇之移动方块的框架

         昨天看到博客(www.richinmemory.com)的流量统计,居然还有一位朋友评论了,感动的满眼都是泪啊!谢谢支持啊!为了使互动的朋友更方便的互动,今天我加了个能用微博等帐号登录评论的插件。需要源码的朋友可以直接发信到我的邮箱。猛戳之后(www.richinmemory.com)若觉得还过得去,可以尝试收藏啊,亲。有朝一日有幸流量稳定了,我就开始放弃这边更新了,不过这个肯定还要很久很久才能达到。

二、桌面山寨版2048—游戏逻辑篇之移动方块

         这个小游戏的基本逻辑就是:合并同类项。玩家通过上下左右键操纵游戏方块,然后操纵方向上所有相邻相同的数字的方块会被合并,合并之后方块上的数字会被更新并且改变颜色。至此我开始了逻辑方面的第一步尝试。

         对于逻辑推理这件事,我一直深深相信的就是“从1到2再到无穷”的方式,这还是我高中老师教会我的方法,对于任何一个有规则但是复杂的事务,先从简单的开 始总是不会错的。于是,首先考虑只有两个方块在一个方向进行操作(我选择的是“下”这个方向)的时候。根据在界面篇中的原则,所有的游戏方块都是被保存在 一个ItemBox的数组里并且在程序的一开始就进行了初始化,所以,现在要处理的逻辑就是如何控制每个方格里方块的显示与否。

         如果只有两个游戏方块时,用户按下了“下”方向键,要考虑的情况有两种:一是两个游戏方块不在一列,二是两个游戏方块位于一列。首先思考一下情况一的行为 模式应该是:两个游戏方块都应该向下移动直到它们碰到了游戏区域的边框。这个逻辑并不难实现,最简单的办法,采用一个循环,遍历所有的方格,如果当前方格 的bshow属性是false,那么不进行操作,否则,获取当前方格的位置信息(坐标),文本,颜色。将当前列的最后一行的方格赋予相同的文本和颜色,同 时将当前游戏方块的信息清空(方块颜色设置为背景色,文本清空),刷新界面,这样就可以造成当前游戏方格“移动”到最后一行的假象。当一个方向做成功之 后,另外三个方向只要以此类推就可以了。虽然说就这个逻辑和最终的逻辑还相差十万八千里,甚至对于整个游戏逻辑来说甚至是不正确的。但是如果没有学会加 法,那么你怎么也不会学会三重积分吧。

         忘记上面一段扯的废话,现在来考虑情况二,如果两个方块位于一列,就要考虑到合并的情况(先假设两个方块的文本是一样的)。这时按下方向键,正确的期望结 果是两个方块合并并且位于当前列的最后一行,这里的做法思路有太多了。我最最最开始的第一脑想法就是从(0,0)位置的方块开始遍历,如果bshow是 false,就不用做任何操作,如果不是,首先检查当前列最后一行的方块bshow的属性,如果是false,那么和上面情况一样,设置当前列的最后一行 的方块和当前方块信息一致并清空当前方块信息。否则,则说明需要合并,这时只要将当前列最后一行方块升一级(比如”2->4”)并清空当前方块信息 就可以了。如此,两个方块的移动和合并已经做完了。可以按照这个思路先先出个代码试试。

         现在已经考虑完“1”的情况了,下面要开始进一步,考虑“2”的情况了。当两个初始方块合并完毕以后,开始思考又多生成了一个新的方块该怎么办。这里也有好几种情况需要被考虑。

         第一种,第一次出现的两个方块已经合并,新的方块出现并且和被合并的方块不在一列上(还是先只考虑一个方向上的情况)。这种最简单,也就是两个不在一列的方块而已,上面已经说过了。

         第二种,同样是最初出现的两个方块已经合并,新的方块与旧的方块在同一列中。这时新出现的方块和已经合并的方块文字不一样,不可能发生合并(暂时先从最简 单的情况开始)。所以这时的思路是上面的一个扩展,也是从左上角的位置进行遍历,依次判断每个方块的bshow是否是true,如果是,那么这个移动方式 就有点不同了,由于目前已知最后一行是已经合并的方块,所以你知道当前这个方块应该在倒数第二行上出现。但是如果我们不知道当前列最后一个bshow为 false的行是多少该怎么办?怎么使用程序来找到呢?由于我们知道当前位置的纵坐标横坐标,所以从最后一行开始,依次向上遍历,如果遇到bshow为 false就立马退出循环并且记录下当前的行坐标。这样就能完成上面所说的任务,有了这个坐标就可以知道当前的游戏块应该放在哪个位置,其余的操作就和前 面所说的一致就可以了。

         第三种,最初的两个方块没有合并,那么这种情况肯定和前面只有两个方块之中的某一种是一样的。

         现在我们已经处理了”2”的情况,那么这种”2”的情况能不能推及到无穷呢?如果这时又出现了一个新的方块,那么情况能不能直接套用上面的思路呢?稍微想 一想,总感觉可能可以又可能不可以,因为“无穷”的情况实在是太多了。我总结了下,用一个图表示,虽然在实际情况中不可能同时出现这四列,但是四列单独出 来,都是可能出现的:

         第一列和第四列的情况最简单,直接移动合并就可以,具体步骤前面已经描述过了。

         第二列,需要判断出同一列的下一行的文字和当前的文字不相同,只能移动不能发生合并。

         第三列,有两个能合并的地方,列1和列2的“2”可以合并,列3和列4的“4”可以合并,而且合并后,两个“2”合并出的“4”要显示在第三行,“4”合并出来的“8”要出现在第四行。

         人脑思维分析完了,现在就要转换成为电脑的思维考虑如何用程序实现。按照前面的思维模式下来,从左上角第一个游戏方块开始进行遍历,如果遇到当前行的 bshow是true,取得当前游戏方块的列标,从当前列最后一行开始,依次判断当前的bshow是否是false,如果是,记录下当前的行序号并且返 回。获得这个返回的行序号之后,判断这个行序号的下一行(当然是同一列)和当前行的游戏方块文字是否一致(有点绕,意思你懂的就行),如果一致,那么要考 虑合并,并且重新设置信息。

         考虑到这里,貌似觉得逻辑都屡顺了,我当时反正就是这么想的。于是第一次我写的代码是这样的,GetCoordsFromIndex(i,it)是我写的一个函数,目的是根据当前游戏方块的序号得到游戏方块的纵坐标和横坐标,这个使用除法和取余操作很容易做到:

    if(nChar == VK_DOWN)
    {
        for(int i=0;i<nTotalGrids-1;i++)
        {
            if(m_itemBoxArray[i].bShow)
            {
                GetCoordsFromIndex(i,it); 
                for(int j=nTotalGrids - (m_nRowAndCol-it.nColIndex);j>i;j-=m_nRowAndCol)
                {
                    // 找到当前列bshow为false的最大行序号
                    if(!m_itemBoxArray[j].bShow)
                    {
                        m_itemBoxArray[i].bShow = false;
                        m_itemBoxArray[j].bShow = true;    
                        m_itemBoxArray[j].strItemText = m_itemBoxArray[i].strItemText;
                        m_itemBoxArray[i].strItemText = _T("");
                        GetCoordsFromIndex(j,it);
                        break;
                    }
                }
                //合并
                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 
                        )
                    {
                        m_itemBoxArray[GetIndexFromCoords(it)].strItemText = m_arrStrItemTexts[GetIndex(itNextRow.strItemText)+1];
                        --it.nRowIndex;
                        m_itemBoxArray[GetIndexFromCoords(it)].bShow = false;
                        m_itemBoxArray[GetIndexFromCoords(it)].strItemText = _T("");
                    }
                }
            }
        }
    }

         但是一测试,发现貌似情况没有向我想像的方向走啊。我观察到了一个现象,那就是按照这个思路,上图列3的行为完全不正确。然后我又回到了这个代码,毕竟走 到这一步,从代码的角度出发更容易找出问题所在,这也不违背数学归纳法的原则。如果按照这个代码,列3这种情况就会出现这样一种情况,由于我们是从左上角 开始遍历的,那么第一行的2和第二行的2合并之后成为第二行的4,遍历继续,当遍历到第三行的4时,决定与第四行的4进行合并,这样就形成了第四行的8, 但是此时,第二行的4应该移动到第三行。然而,按照我们的代码,我们已经遍历过了第二行,不可能再回去了,所以就造成了在错误,就会造成合并的4和合并的 8分别在第二行和第四行,第三行空出来了,这明显是不正确的。如何解决这个问题呢?通过分析可以发现,这个问题就是因为我们的遍历顺序有问题,那么就简单 了,在向下这方向上,只要从最后一个游戏方格向第一个方格进行遍历这个问题就迎刃而解,具体逻辑同志们可以再思考思考。

        这样之后,带着洋洋得意的心情再次编译,尝试逻辑是否正确,结果发现还是太naive,问题又出现了,如果同一列出现了4个”2“,这时按一下”下“方向 键,这4个”2“会直接合并成一个”8“,根据原游戏的规则,这也是不对的,毕竟我们是山寨,山寨就得有点诚意,不能乱窜改原先的游戏规则。这个问题又如 何解决呢?首先得找出这个问题出现的原因,仔细思考下不难发现,原来游戏的规则是一次操作中,合并过的游戏方块不可以再次合并,那么,这个问题很容易解 决,使用在封装中的另一个成员变量bJoin,标识已经合并的方块。所以最终”下“这个方向的代码如下所示:

    if(nChar == VK_DOWN)
    {
        for(int i=nTotalGrids-1;i>=0;i--) //从后向前遍历
        {
            if(m_itemBoxArray[i].bShow)
            {
                GetCoordsFromIndex(i,it); 
                // 查询同一列的bshow为false的最大行序号
                for(int j=nTotalGrids - (m_nRowAndCol-it.nColIndex);j>i;j-=m_nRowAndCol)
                {
                    if(!m_itemBoxArray[j].bShow)
                    {
                        m_itemBoxArray[i].bShow = false;
                        m_itemBoxArray[j].bShow = true;    
                        m_itemBoxArray[j].strItemText = m_itemBoxArray[i].strItemText;
                        m_itemBoxArray[i].strItemText = _T("");
                        GetCoordsFromIndex(j,it);
                        break;
                    }
                }
                // 合并
                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("");
                    }
                }
            }
        }
    }

          其余三个方向的代码依”下“方向类推,但要注意遍历顺序,具体代码可进入博客,那里有我的邮箱,若需要源代码,可以留言或者发信给我。到这里,对于一个完 整的游戏,逻辑只能说完成了一个框架,还不能成为一个合格的山寨品,所以请看下一篇”逻辑面之缓缓出现的细节像风叶“。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏phodal

「微信小程序」剖析(四):原生的实时DOM转Virtual DOM

在之前的几篇文章里,我们讨论了MINA的一些原理。晚上在想着怎么结合Vux + Virtual Dom实现一个名为WINV框架的时候,在探索WCC功能才发现:自...

24260
来自专栏mathor

概率论与数理统计(一)

11940
来自专栏GreenLeaves

FactoryMethod工厂方法模式升级成AbstractFactory抽象工厂模式

具体参考抽象工厂(AbstractFactory)模式-创建型模式,本文在FactoryMethod工厂方法模式(创建型模式)的基础上进行业务进一步抽象,不做详...

10030
来自专栏华章科技

技巧:Excel用得好,天天没烦恼

分析公司DarkHorse Analytics 从美国劳工统计处获得数据,并制作了这张二十四小时会唿吸的地图,显示曼哈顿的工作与在宅人口。

12240
来自专栏javascript趣味编程

3.1.1 借助显卡GPU绘制Contour

如何绘制标量场呢?我们常用诸如商业软件Tecplot,或者基于Python的开源软件包matplotlib中的contour绘制Contour图形(等值线)。这...

17700
来自专栏小狼的世界

YUI3 CSS框架学习

改变最大的我觉得是Grids部分,YUI2中以模版的方式提供给我们调用,功能中多选项也很多,而且配合Grid Build Tool,可以快速的生成复杂的页面结构...

11730
来自专栏前端新视界

CSS 特殊属性介绍之 pointer-events

首先看一下 MDN 上关于 pointer-events 的介绍: CSS属性 pointer-events 允许作者控制特定的图形元素在何时成为鼠标事件的 t...

213100
来自专栏全沾开发(huā)

使用JavaScript实现一个俄罗斯方块

使用JavaScript实现一个俄罗斯方块 清明假期期间,闲的无聊,就做了一个小游戏玩玩,目前游戏逻辑上暂未发现bug,只不过样子稍...

39360
来自专栏HTML5学堂

JavaScript | 动画显示比例的投票效果

HTML5学堂(码匠):一个简洁实用的投票效果如何使用原生JS来进行实现呢?同时动画显示比例的形式又需要依靠哪些技术来实现?是数学对象还是字符串操作,又或者是计...

36560
来自专栏lonelydawn的前端猿区

一款不可多得的火柴时钟

? 火柴时钟 一款有意思的时钟玩具,原生代码编写,使用 CSS 渲染过渡动效,引入 base64 格式 data url 图片。 引用 <link rel=...

31070

扫码关注云+社区

领取腾讯云代金券