最近因为实验室需要,需要详细再回顾一下labview的知识,于是就想做个小游戏,可是发现自己水平太差,做不出来,于是就网上找了找看看有没有有关教程,发现好代码倒是有(一模一样的代码界面,好多人都说是自己编的...),注释很少,就是没人告诉我们编程的思路和内部是怎么实现的,网上能找到的2048的报告都只有几页,我估计是大家上课要交作业抄来抄去随便改改。
非常感谢网上的代码,让我有了学习的机会,我下载的是CSDN上u010515100这个网友上传的源文件,地址http://download.csdn.net/download/u010515100/8091283,测试可用。
然后我就开始慢慢琢磨,它是怎么实现的,现在记录下来实现过程,内部很多数组的运用,实在太巧妙了,感觉我自己编要几百年才能写出来。给小白们分享,大神就不太用看了,欢迎批评指正。
首先撸一撸思路,整个游戏其实就是一个状态机,初始化、用户操作、出现2或4、等等这些分别是不同的状态,整个游戏运行过程中,无非就是这几个状态跳来跳去,状态机其实是labview比起来其他代码编写优势的地方。几个状态之间的互相转化的过程,我简单的画了一下:
然后就可以先构建一个状态机的大框架,然后分别编写每个状态内部的代码,
最外部肯定是一个大的while循环,内部就是一个条件结构,用来改变状态。
这个大的条件结构就可以有一下状态:初始化、用户操作、上下左右移动、生成24、失败、成功、结束。
然后就可以分别编写每个状态内的代码了。
首先说明一下,整个4*4的数是通过一个二维数组来实现的,数组内的元素是一个枚举类型,所以一共有:空2 4 8 16 32 64 128 256 512 1024 2048共12种枚举元素。这12种元素的在数组中的数就是0 1 2 3 4 5 6 7 8 9 10 11,所以我们只需要改变这个0-11共12个数的大小就可以显示出空2 4 8 16 32 64 128 256 512 1024 2048 来了。
就像我下面的测试一样:
所以我们一直改变的就是一个4*4的二维数组即可。
初始化:
然后开始写内部代码,首先简单的初始化,大家可以看到源代码中有一个save文件,这是其实是一个初始化文件,大家可以用记事本打开,发现里面是一个4*4矩阵,其中有两个1,其他都是0,这其实就以为这刚开始的时候显示的界面上会有两个2,然后用户开始操作。它这个初始化有点复杂,其实想改简单一点非常容易。
首先读取save中的数据,如果其中的数据有两个2或者4了,那就直接进行下一个状态了。如果全是0的话,就进行生成两次2和4的操作,即循环2次的for循环,每次生成一个24,这生成24的过程是两个子函数,在生成24状态中也是一样的这两个韩函数,
这两个函数输入和输出都是那个代表结果的二维数组。
第一个是搜索二维数组并输出索引,第二个就是生成24。
先看第一个搜索二维数组并输出索引,其实就是找出输入的数组中0的位置,输出的是0的行和列的位置,这个输出的实际是一个一维数组,一维数组中的每个元素是一个簇,一个簇中有两个数,形式是这样的:
实现过程如下:
这里首先说一个非常重要的东西:关于for循环
For循环,输入为二维数组,进去之后就会按照每行分解为一维数组,循环次数就是数组的行数,输出又可以是二维数组。
而图中是两个for循环,所以就提取出了这个二维数组中的每个元素,判断提取出来的值和要搜索的值(这里是0)是否一致,如果一致就说明数组中的是0,然后把它的位置保存下来(就是两个for循环的技术端子),为下一函数在0的位置生成24做准备。
然后就开始生成24了,找出了输入数组中0的位置了,那就随机找一个0的位置,生成1个24,输出即可。
大家注意看别人代码的时候,ctrl+h可以调出来实时帮助,就可以看到各个函数是怎么用的,接口是什么样的。
用户操作:
初始化之后,就应该用户操作了,通过移位寄存器传走,用户操作里有一个事件结构来检测事件,它把用户可能的操作大都分别考虑了。关闭、清空、停止都很简单。就是停止大循环或者给输出的数组一个新的固定值。
主要是正常情况下,用户按键盘上的上下左右,就会进入对应的上下左右的状态。扫描键盘上按键的方式是时间结构左边有一个扫描代码,可以扫描出键盘上哪个按键按下了。
上下左右移动:
分别定了上下左右移动vi,这是个不同的vi的程序框图拿出来对比一下:
差别就是最外面的二维数组转秩,还有里面的翻转一维数组。很巧妙。向下移动具体写出来的子vi作用。
这里就用到我刚才说的for循环内的维数变化问题,外面本来是二维数组,进入for循环就变成了一维数组,其实就是把二维数组的每一行作为一个新的一维数组,分别进去处理。所以循环次数就是二维数组的行数。
转秩就是把本来的一列数变为第一行。
反转一维数组就是把数组内的数反过来喽。
最简单的情况是向左移动的时候,不需要转秩翻转,直接删除二维数组中的0,然后再相邻的相加,再把空缺的0补上。
向左移动不需要转秩等等的原因:是因为第一个函数删除数组的0,是按照一行一行的删除的,删除后剩下的自然就靠在了左边。然后相加之后也是靠在左边,补全0即可。
而向右移动的话,自然就是把去掉0之后的数,左右换一下顺序,然后补完0再换回来,这样大的数就返回到右边了。
上下同理。在纸上画一画就很清晰了。
生成2和4:
和初始化中生成24一样的子函数。
胜利的判断:
判断胜利与否,胜利了就显示胜利,没有胜利就继续用户操作。
判断胜利如下:搜索一维数组。本来是二维数组,进入for循环就会编程行个(这里是4个)一维数组。搜索是不是出现了11(就代表2048),如果能搜索到11,就会返回这个11所在的位置索引,如果搜索不到,就会返回-1,所以下面和-1比较,如果不是-1就说明出现11了,就会返回一个true的布尔类型,从for循环中出来就会变成一个4个布尔类型(每行出现一个布尔)再看看是不是真的有true,有的话就是真的出现11了,胜利了。
失败的判断:
在用户操作中,上下左右移动之后,如果发现移动前后的数没有变化,那么久有可能是满了,失败了。就转到失败中。如果变化了,说明正常的运算,继续进行下一步生成新的24。
如果移动前后一致,就一定可以判断是失败了吗,显然是不一定的,比如向下移动,刚好不能消除什么。所以还需要在失败处理中进一步判断是不是真的的失败了。判断的代码如下:
很有意思的是,一开始的地方,把原来的数组和转秩后的数组结合起来,这样其实就变成了一个三维数组,行列和页的三维数组,所以后面来了3个循环,经过第一个循环之后,就变成了两个二维数组,再经过一个循环,就变成了2*4个一维数组,在经过最内部循环就变成了2*4*4个元素了。最内部的循环就是使所有的两个相邻的数比价一下,看看是不是还有相等的,要是没有的话,那就判定失败了。为什么要转秩相加呢,就是因为内部循环的比价都是横着比的,上下没法比,所以要转秩一下,这样上下就变成左右,就能把上下左右全比较一遍了。
结束:
结束就简单了,它上面就是停止大循环,然后把当前的输出是数组的值写入到那个save文件中。这里用到了一个属性节点,的值。就是输出的那个数组的值再次被使用。
差不多了,就这样,当然几个状态之间的转化我觉得不一定按照它上面的来,可以思考实际情况改变。
对了最后说一下子vi调用的问题,每个子vi想要被调用必须定义接线口,就是我下面画的,鼠标点控件,然后点右上角就ok了。
还有一个tip:
上下移动中,前后比较的其实是数组,默认输出的是一个二维的布尔数组,不能连接到后面判断的,所以右击这个等号,改为比较集合就能输出一个布尔了。
看到这里,基本上就可以修改里面的东西了,还可以把里面的子vi保存下来为自己用。
然后我在看懂人家的2048的基础之上,自己改了改优化了一下。我改进的代码的地址在:http://download.csdn.net/download/hftqcmtavp/10011409
添加了功能一:记录游戏已经用的时间。
添加了功能二:设置了一个滚动条,滚动条慢慢减下来,如果降为0,也就显示失败了。如 果用户按动键盘上的上下左右,会让这个滚动条增加以延长时间。
改动一:不再导入文件,每次直接随机生成24
改动二:失败或者胜利本来显示的是对话框,我改成两个灯了,因为对话框出现的话,必须 用户点击一下才停止,影响我写程序,尴尬。
解释下我的主要改动程序的编写:
首先说一下:
事件结构:(关于等待和超时)
如果在循环中,默认先执行事件结构,在等待事件结构的时候,是不执行其他程序的。
事件结构左上角的沙漏可以设定等待时间,超过了等待时间还没有事件触发的话,程序就会运行那个超时内的。 不设定等待时间或者设为-1就默认永不超时,就永远也不会进入超时内。 比如我设置超时100,程序等待100ms之后就会,运行超时内的东西,之后就会跑出来运行看看外面有没有东西,然后又会等待100ms,如果等待期间有事件发生,那自然就去处理事件。
然后介绍添加的程序:
为了不和上面的人家写好的有大的改动,我下面重新来的一个循环,单独采集时间以及实现滚动条的减小和变大。下面的循环,中一个事件结构,分别对应停止、超时、按键按下
按键按下就给那个滚动条加1.
超时的情况下默认给滚动条减1,如果减到0,就失败了。
停止的过程很麻烦:就是给停止按钮的局部变量变为真。再把停止给端子。
为什么这么写,因为是上下两个循环,必须使上下同时停止,程序才停止。
所以给停止创建一个局部变量,初始化为F,等需要停止置为T,
下面的循环的局部变量置为T是为了让上面循环用,反之亦然。
注意:下图红框内,置了停止,还必须在当前分之内把停止给端子,要不然在while内,时间结构外的话,程序可能执行不到。
这里这个停止按钮创造了多个局部变量,用局部变量控制多个循环停止出现了下面错误:
布尔触发动作与局部变量不兼容
这个错误只需要把停止按钮右击属性中的触发方式改为:释放时转换即可。
然后在循环内,事件结构外加一个显示已用时间,显示类型为整形,就能按照整秒显示出程序运行时间了。(前提是事件结构必须设定超时时间,要不然永不超时的话,程序就执行不到时间结构外的东西,会一直等事件发生)
好了,先说这么多,小白小试牛刀,有问题大佬们多多批评指正。
第一次用csdn博客,上传图片好麻烦。。