千里冰封 发表于 2016-12-15 23:25

分享一个扫雷的易语言【源码】


声明:内容是转载的{:5_187:}
附件里有源码

教程如下:
不想看教程的可以直接略过{:5_123:}


1.7.3 [例]扫雷游戏       这里我们综合前面所讲的知识——自定义数据类型、数组和递归——来开发一个小游戏——扫雷。Windows 操作系统从95时代就自带这样一个扫雷游戏了,是个很小的益智类游戏,如果你还没有玩过它,那么建议你先玩一玩,这样编程时就有一个感性的认识,读代码时也更容易理解。       在编程之前,对我们要实现的功能仔细分析一下是很有好处的,这便于我们设计好的数据结构和算法,所以在此我对扫雷游戏作一个简要的介绍。
       在扫雷游戏中,默认情况下程序会提供一个9×9的方格雷区,并在其中的某些格子中随机分布10个雷。你需要通过单击鼠标左键来翻开雷块,如果该雷块下有雷,则你不幸“壮烈牺牲”,游戏结束;如果没有雷,则程序会自动显示该雷块周围8个雷块中雷数的总和,你要根据该数值来判断周围的布雷情况,进而找处所有有雷的雷块;如果你确定某个雷块下有雷,则在其上单击右键,插一面红旗,如果不能确定有没有雷,则再次单击鼠标右键,标记为“?”。在你扫雷的时候,程序还会提供一些便利:如果某个雷块周围雷的总数为0,那么显然周围都没有雷,程序会自动翻开周围的雷块而免去你用鼠标逐个点击。在游戏进行的时候,程序会自动计时,如果你在很短的时间内找出了所有的雷,那么你就上排行榜了!扫雷游戏的界面如图1.7.3-a 所示。游戏过程分析完毕,下面我们开始动手编程了。首先当然是设计界面啦。古人云:“三思而后行”,所以对于主界面上的九九八十一个按钮,这里我想了三个方案:       1.使用“按钮样式”为“真”的选择框,去掉标题,弄成正方形,然后复制粘贴排成矩阵。这样它们在鼠标按下时会自动呈现按下状态,好像很不错,是不是?但是很不幸,答案是不行,你想想吧,八十一个按钮,不光手动创建按钮麻烦,而且你还得为每个按钮写鼠标左键和鼠标右键事件,显然行不通。       2.在上面的想法的基础上,我们深入考虑一下:既然静态创建的控件不行,那么使用动态复制组件可以吗?我们知道,在易语言中可以通过“复制窗口组件()”函数和“取事件组件()”函数来简化大量的手动创建组件的工作量和代码的编写量,这个方案比上一个方案强些,可以行得通。但是还有一些问题:如何在按钮上画上旗帜?而且对于游戏来说,这样的灰按钮显得很呆板,不好看。比如说,我就不喜欢Windows自带的扫雷游戏的界面。       3.既然上面的方法有的行不通,有的不太理想,那么我们自己在画板来绘制这些按钮,然后根据鼠标在画板上的事件位置来判断单击的是哪个按钮如何?看上去很复杂,其实很简单的,我们只用写两个函数即可:一个画凸出的那种按钮,一个画被按下的那种按钮,根据其参数画在指定的地方。况且且我们自己画的按钮可以随心所欲地更改显示效果,因此会比系统默认的按钮要好看得多,虽然会多花点代码和时间,但绝对是值得的。
       既然决定自己画按钮,那么还得思考一些细节问题:对于按下的按钮,会存在显示雷数或画地雷两种状态;对于凸起的按钮,存在空白、有旗帜、有问号三种状态,而且这三种状态会在用户不断单击右键的时候循环显示,所以把这三种状态设计成连续的数字最好不过了,这样就可以通过递增取余数的方法来确定其状态。还有,跟Windows自带的扫雷游戏有点不同,我们决定做一个8×8的雷区(在计算机中,8这个数字好啊),每个雷块的宽度为20。既然如此,我们先定义了如图1.7.3-b所示的常量。雷块宽度确定了之后,窗口的宽度和高度也就定了。这里我将_启动窗口改动过的属性列在下表1.7.3-a中。
_启动窗口属性
宽度170
高度208
标题扫雷
边框镜框式固定边框
控制按钮真
最大化按钮假
最小化按钮真
                                                         表1.7.3-a 扫雷游戏中_启动窗口的属性
为_启动窗口设计如图1.7.3-c所示的菜单。


然后在_启动窗口上添加一个画板,设置属性如表1.7.3-b。
画板属性
名称画板
宽度160
高度160
自动重画真
字体Arial Black,12
可停留焦点真
                                                                  表1.7.3-c扫雷游戏中画板性        接下来就要写两个画按钮的函数了,一个画凸起按钮,一个画按下的按钮。画凸起按钮函数的具体实现代码如下:
.子程序 画凸起按钮.参数 参横坐标, 整数型, , 以数组索引为单位.参数 参纵坐标, 整数型, , 以数组索引为单位.参数 参按钮类型, 整数型, , 0:空白,1:旗帜,2:问号.局部变量 x, 整数型.局部变量 y, 整数型 x = #雷块宽度 × (参横坐标 - 1)y = #雷块宽度 × (参纵坐标 - 1)画板.画渐变矩形 (x, y, #雷块宽度, #雷块宽度, #从左上到右下, 取颜色值 (131, 192, 252), 取颜色值 (3, 92, 182))画板.画笔颜色 = 取颜色值 (1, 78,155)画板.刷子类型 = 0画板.画笔类型 = 0画板.画矩形 (x, y, x + #雷块宽度, y + #雷块宽度)画板.画渐变矩形 (x + 2, y + 2, #雷块宽度 -4, #雷块宽度 - 4, #从上到下, 取颜色值 (175, 216, 254), 取颜色值 (91, 173, 253))画板.画笔类型 = 1.判断开始 (参按钮类型 = #凸起按钮_旗帜)    ' 以下代码绘制旗帜、旗杆和基座    画板.画笔颜色 = #红色    画板.刷子颜色 = #红色    画板.刷子类型 = 1    画板.画矩形 (x + 9, y + 4, x + 16, y + 9)    画板.画笔颜色 = #黑色    画板.画笔粗细 = 1    画板.画直线 (x + 8, y + 3, x + 8, y + 15)    画板.画笔粗细 = 2    画板.画直线 (x + 5, y + 15, x + 13, y +15).判断 (参按钮类型 = #凸起按钮_问号)    画板.定位写出 (x + 4, y - 2, “?”)' 画问号.默认
    这里说明一下,在后续的章节中,因为整幅的图片不便于排版,对于大段的源代码,将直接以文字的方式编排出来,这对源代码的理解并没有影响。       在这段代码中,传递的“参横坐标”和“参纵坐标”参数是雷块的横纵编号,其范围为1到8之间,所以先要计算出实际的绘制坐标x和y,将编号值减一乘以每个雷块的宽度就是了。为了将雷块画得美观,这里我们使用了易语言提供的“画渐变矩形”方法:先从左上角往右下角画一个渐变矩形,然后画边框,再叠画一个小的渐变矩形,这样就形成了一个很漂亮的按钮。具体的图示如下:该函数还根据传递的“参按钮类型”来判断是绘制旗帜、问号或留空白。这些代码很简单,就懒得敲键盘详说了。
       画按下按钮的代码是这个样子的:

.子程序 画凹下按钮.参数 参横坐标, 整数型, , 以数组索引为单位.参数 参纵坐标, 整数型, , 以数组索引为单位.参数 参周围雷数, 整数型, , -1表示是雷,其他是雷的数目.局部变量 x.局部变量 y x = #雷块宽度 × (参横坐标 - 1)y = #雷块宽度 × (参纵坐标 - 1)画板.画笔类型 = 0' 空画笔画板.画笔颜色 = #深灰画板.刷子类型 = 1' 颜色刷子画板.刷子颜色 = 取颜色值 (224, 242,254)画板.画矩形 (x, y, x + #雷块宽度, y + #雷块宽度).判断开始 (参周围雷数 = -1)' 是地雷    ' 以下代码画地雷    画板.画笔颜色 = #黑色    画板.画笔类型 = 0    画板.刷子颜色 = #黑色    画板.刷子类型 = 1    画板.画椭圆 (x + 4, y + 4, x + #雷块宽度 - 4,y + #雷块宽度 - 4)    画板.画直线 (x + 2, y + #雷块宽度 ÷ 2, x + #雷块宽度- 2, y + #雷块宽度 ÷ 2)    画板.画直线 (x + #雷块宽度÷ 2, y + 2, x + #雷块宽度 ÷ 2, y + #雷块宽度- 2)    画板.画直线 (x + 4, y + 4, x + #雷块宽度 - 4,y + #雷块宽度 - 4)    画板.画直线 (x + #雷块宽度- 4, y + 4, x + 4, y + #雷块宽度 - 4)    画板.刷子颜色 = #白色    画板.画笔颜色 = #黑色    画板.画椭圆 (x + 6, y + 6, x + 10, y +10)    返回 ().判断 (参周围雷数 = 0)    返回 ().判断 (参周围雷数 = 1)    画板.文本颜色 = #蓝色.判断 (参周围雷数 = 2)    画板.文本颜色 = #深青.默认    画板.文本颜色 = #红色.判断结束画板.定位写出 (x + 4, y - 2, 参周围雷数)
同样地,这个子程序也需要接受按钮的横纵坐标编号并转换成实际的绘制坐标,按钮被按下后,绘制一个有边界的浅蓝色填充的矩形就完事,除此之外,还需要该雷块周围的雷的个数,根据个数设置文本的颜色;如果个数>0,则绘制文本;如果个数是-1,则表示要画一个地雷,画雷的次序是这样的:好了,绘制按钮已不成问题,但如何判断用户单击的按钮是否有雷? 又如何判断用户在一个按钮上有鼠标右击了几次?显然,我们还需要在内存中保存当前雷的分布情况和按钮的当前状态,并把这些信息和按钮一一对应起来,这就需要一个自定义数据类型和一个保存该数据类型的8×8的数组。
       为此,我们定义如下的数据类型:还需要在窗口程序集中添加图1.6.3-f所示的程序集变量。其中“集游戏已结束”用来判断游戏是否已结束,如果已结束,按钮就不能再点击了。“集雷块阵列”是一个雷块数组,初始长度设为0,我们需要重新定义其为一个8×8的数组。为此,有一个子程序是必须要写的,那就是“新游戏”。程序每次起动时都要调用此子程序,用户单击“新游戏”菜单项的时候也需要调用它,在此子程序中,我们需要重设游戏结束标志、重画所有按钮、重新布雷,所以其代码如下:

.子程序 新游戏.局部变量 i, 整数型.局部变量 j, 整数型 集游戏已结束 = 假重定义数组 (集雷块阵列, 假, 8, 8)' 定义一个8×8的雷块数组置随机数种子 ()' 先重画所有按钮.计次循环首 (8, i)    .计次循环首 (8, j)      画凸起按钮 (j, i, #凸起按钮_空白)    .计次循环尾 ().计次循环尾 ()' 再设置雷区数组.计次循环首 (10, )' 随机放10个雷    .循环判断首 ()      i =取随机数 (1, 8)' 随机取一个地方      j =取随机数 (1, 8)      ' 已放雷的地方就不再放,直到找到空地方    .循环判断尾 (集雷块阵列 .有雷)    集雷块阵列 .有雷 = 真.计次循环尾 ()
在这段代码中,程序先把数组重新定义成了一个8×8的数组,这样每个数组成员就可以很方便地与按钮对应起来了。我们还看到在布雷的时候,使用了一个循环判断。这个循环的目的是防止把两个或两个以上的雷放到了一个雷块中,否则雷的总数就不是10了。       在“__启动窗口_创建完毕”事件中,添加一句代码“新游戏()”,再次运行程序,我们得到了如下的结果: 这些按钮可比Windows自带的扫雷游戏的按钮漂亮多了,但悲惨的是,它们都还是花架子——不能响应鼠标点击。
       接下来,我们需要给画板添加响应鼠标事件的子程序。在这些子程序中,首要要把鼠标的位置转换成数组的索引,然后根据点击的鼠标键和周围的布雷情况重画该按钮。在这里,点击鼠标左键的情况要复杂些:1. 按下后要立即算出周围的雷数;2.如果周围的雷数为0,则还要自动按下周围的按钮,如果周围的雷块中有的周围的雷数也是0,则还要自动按下该按钮周围的按钮……显然这里要用到递归。而要自动点按周围的按钮,则需要遍历周围的8个按钮,如果该按钮已被按下或以被右键作了标示,就跳过,否则就计算该按钮的中心坐标,用鼠标去点击画板的那个位置,如此循环往复;3. 如果有雷,则需要画出所有的雷,同时在用户标了旗帜但没有雷的按钮上画上红叉叉,同时宣布则游戏结束。如果是点击右键,则把该按钮对应的数组成员“右击索引”加1,并求与3的余数,这样其值刚好与凸起按钮的三种类型相对应,从而绘制出不同的状态。不论是鼠标左键按下和右键按下,都要判断用户是否已标出了所有的雷,这个代码由子程序“判断胜负”来完成。具体的实现代码如下:
.子程序 _画板_鼠标左键被按下, 逻辑型.参数横向位置, 整数型.参数纵向位置, 整数型.参数功能键状态, 整数型.局部变量 m, 整数型.局部变量 n, 整数型.局部变量 i, 整数型.局部变量 j, 整数型.局部变量 x, 整数型.局部变量 y, 整数型.局部变量周围雷数, 整数型 .如果真 (集游戏已结束)   返回 ().如果真结束' 将横纵坐标映射为数组的索引值m =横向位置 ÷ #雷块宽度 + 1n =纵向位置 ÷ #雷块宽度 + 1.如果真 (集雷块阵列 .已按下)   返回 ().如果真结束集雷块阵列 .已按下 =真集雷块阵列 .右击索引 = 0.如果 (集雷块阵列 .有雷)   ' 有雷则显示所有的雷,同时弹出信息框   .计次循环首 (8, j)         .计次循环首 (8, i)            .如果 (集雷块阵列 .有雷)                画凹下按钮 (i, j, -1)            .否则                ' 没有雷却标了旗帜的地方则画叉叉                .如果真 (集雷块阵列 .右击索引 = #凸起按钮_旗帜)                  画红叉叉 (i, j)                .如果真结束            .如果结束         .计次循环尾 ()   .计次循环尾 ()   集游戏已结束 = 真   信息框 (“你踩雷了!”, #错误图标, )   返回 ().否则   ' 无雷则显示周围雷的数目,数目是0则不显示且递归按周围的按钮   ' 遍历周围的雷块   .变量循环首 (n - 1, n + 1, 1, j)         .变量循环首 (m - 1, m + 1, 1, i)            .如果真 (i ≥ 1 且 i ≤ 8 且 j ≥ 1 且 j ≤ 8)                ' 确保在数组索引范围内,以免导致数组下标越界错误!                .如果真 (集雷块阵列 .有雷)                  周围雷数 = 周围雷数 + 1                .如果真结束             .如果真结束          .变量循环尾 ()   .变量循环尾 ()   画凹下按钮 (m, n,周围雷数)   .如果真 (周围雷数 = 0)         ' 如果周围雷数是0,则还需要自动向周围扩展。(递归)         .变量循环首 (n - 1, n + 1, 1, j)            .变量循环首 (m - 1, m + 1, 1, i)                .如果真 (i < 1 或 i > 8 或 j < 1 或 j > 8)                  ' 确保数组下标不会越界                  到循环尾 ()                .如果真结束                .如果真 (集雷块阵列 .已按下 或 集雷块阵列 .右击索引 ≠ #凸起按钮_空白)                  ' 已标为旗帜或问号的则不翻出来                  到循环尾 ()' 此处退出递归,否则会因无限递归而死机。                .如果真结束                x = i × #雷块宽度 - #雷块宽度 ÷ 2                y = j × #雷块宽度 - #雷块宽度 ÷ 2                _画板_鼠标左键被按下 (x, y, 功能键状态)' 此处递归调用自身            .变量循环尾 ()         .变量循环尾 ()   .如果真结束 .如果结束判断胜负 () .子程序 _画板_鼠标右键被按下, 逻辑型.参数横向位置, 整数型.参数纵向位置, 整数型.参数功能键状态, 整数型.局部变量 m, 整数型.局部变量 n, 整数型.局部变量 i, 整数型.局部变量 j, 整数型.局部变量周围雷数, 整数型 .如果真 (集游戏已结束)   返回 ().如果真结束' 将横纵坐标映射为数组的索引值i =横向位置 ÷ #雷块宽度 + 1j =纵向位置 ÷ #雷块宽度 + 1.如果真 (集雷块阵列 .已按下)   返回 ().如果真结束集雷块阵列 .右击索引 = (集雷块阵列 .右击索引 + 1) % 3画凸起按钮 (i, j,集雷块阵列 .右击索引)判断胜负 ()
在递归调用“_画板_鼠标左键被按下”的时候,程序需要计算周围的雷块的中心位置,用该按钮的坐标索引值乘以按钮的宽度,然后减去按钮宽度的一半,刚好就是该按钮的中心位置了。具体算法如图 所示。

程序的核心算法和代码就是这样的了,至于计算用户排雷所花的时间,以及排行榜的处理等,就留给有兴趣的读者自己来完成了。——嗯,还有一点,如果用现在这个扫雷程序和你的朋友比赛,你说谁会赢?这恐怕就难说了,也许他是位扫雷高手呢?既然游戏是我们自己写的,我何不在里面加个后门?这样谁都胜不了我!哈哈,说干就干,于是,我在游戏中加了一个“_画板_按下某键”事件,代码如下所示。
.子程序 _画板_按下某键, 逻辑型.参数键代码, 整数型.参数功能键状态, 整数型.局部变量 j, 整数型.局部变量 i, 整数型.局部变量 x, 整数型.局部变量 y, 整数型 ' 此处是绝技,不可告人哦 ^_^.如果真 (键代码 = #F12键 且 功能键状态 = 位或 (#Ctrl键状态, #Alt键状态))   画板.画笔颜色 = #黑色   .计次循环首 (8, j)         .计次循环首 (8, i)            .如果真 (集雷块阵列 .有雷)                x = (i - 1) × #雷块宽度                y = (j - 1) × #雷块宽度                ' 用黑点把有雷的地方标出来,哈哈!                画板.画直线 (x + 3, y + 3, x + 5, y + 3)            .如果真结束          .计次循环尾 ()   .计次循环尾 ().如果真结束
现在运行程序,按下++组合键,哪里有雷就一览无余了,如果动作块,你可以在3秒钟内就把所有的雷找出来,看谁还胜得了你!当然,你也可以把这个组合键更改成其他更隐秘的东西,比如连续按下字符串“zjs”、或鼠标横向快速晃动3次——一切全在于你的创意。 从这个扫雷游戏中,我们得到如下结论:1> Windows 系统中的所有控件都是“画”出来的,所以我们可以通过自己绘制这些控件来更改它们的外观,这就是换肤的基本思想,在后续的章节中将有专门章节讲述。2> 没有自己的核心技术是多么可怕,落后就要挨打啊!所以,我们要有自己的操作系统、自己的编译器!   支持易语言~!!       图1.6.3-k是这个扫雷游戏在运行中的界面。

最后指出易语言帮助提示里的1个错误:









余空丶 发表于 2016-12-16 21:58

{:5_116:}收藏,学习了!

123456zxc 发表于 2017-8-12 14:59

分享精神,是最值得尊敬的!赞你

2323233aa 发表于 2017-12-19 15:44

好像很好的样子

PA助手 发表于 2017-12-19 18:59

是个学习的好例子{:5_117:}

allbbs 发表于 2017-12-19 19:32

有源码学习起来更轻松
页: [1]
查看完整版本: 分享一个扫雷的易语言【源码】