《面向对象程序设计》大作业--基于C#的一款扫雷游戏
- 格式:docx
- 大小:1.99 MB
- 文档页数:72
《面向对象程序设计》
大作业
项目名称基于C#的一款扫雷游戏班级
组员
一、开发环境
开发环境:vs2010
开发语言:C#
二、系统功能设计
1.创建项目功能实现
创建项目,找图片素材
2.界面设计
介绍关于扫雷游戏界面的设计
提出希望在对应级别(初级、中级、高级、自定义)的按钮的显示勾,以表示目前的游戏等级。
3.雷区绘制
界面中雷区的绘制方法
4.菜单操作
有两个时候需要对它进行操作,一个是加载上次游戏设置的时候,一个是在游戏过程中进行设置的时候,设计菜单中的相关方法
5.内部实现
内部埋雷,放雷,标旗子,标问号的方法
6.整体完善
写游戏的胜利条件,自定义游戏的初始化及相关调试任务
三、实现
1、创建项目功能实现
玩过扫雷的读者应该知道,扫雷需要用到三种图片素材——地雷、红旗、问号。
当右击一个方块的时候,会插上红旗,表示游戏者认为该方块内有地雷;再次右
击该方块,会变成问号,表示不确定该方块内是否有地雷;第三次右击该方块,问号消失,恢复到初始的状态。
我们前往Iconfinder寻找合适的图标,并且通过Photoshop或者Illustrator的处理得到我们所需要的图标。
文章中含有下载地址的图标资源可以使用右键—目标另存为进行下载。
通过一定的搜索以及图片的处理,得到了下面一组素材图标,下载地址:Doubbt、Flag、Mine。
除了这些素材以外,我们还需要找一个图标,使得整个程序看上去更加正式一点,这里使用图标如下,注意程序图标的后缀为ico,下载地址:favicon
接下来,我们打开Visual Studio 2013开始创建工程,界面如下:
创建好的项目如下图所示。
我们需要对这个窗口的几项属性进行修改,首先通过Text属性将它的标题修改为“Minesweeper”;通过FormBorderStyle属性将它的大小设置为“FixedSingle”,即为不可调节窗口大小;通过MaximizeBox属性将它的最大化按钮设置为“False”,即最大化按钮失效;通过Name属性将它的名称修改为“Form_Main”,修改这个属性主要用于后面书写代码的方便;通
过BackColor属性将它的背景修改为“White”。
如下图所示:
我们可以顺便修改一下这个窗口的代码文件的名称。
在右上方找到解决方案资源管理器,右击Form_1.cs—重命名—Form_Main.cs,程序会自动将下面包含的文件名也都改成一致的名称。
如图所示:
接下来我们需要修改一下程序的图标,将刚才下载的favicon文件放到Minesweeper/Minesweeper文件夹下,同时将三个图标资源放到Minesweeper/Minesweeper/Resources文件夹下(Resources文件夹需要自己新建),如下图所示:
在属性栏中找到Icon属性,并且定位到favicon.ico文件,如下图所示:
此时,我们按下快捷键Ctrl + S对整个工程进行保存,并且按下快捷键Ctrl + F5运行查看我们的程序,效果如下:
2、界面设计
这一节我们主要介绍关于扫雷游戏界面的设计,此处我们借鉴经典扫雷界面的设计方法,首先需要创建一个菜单栏。
具体方法在左边找到工具箱窗口,展开其中的菜单和工具栏,找到MenuStrip选项,如图所示:
只需要双击该条目就可以在窗口中新建一个菜单栏,如图所示:
对于这个控件(我们习惯上将窗口中的东西称作为控件),我们还是需要修改它的一些属性,对于个人习惯而言,我习惯于修改它的Name属性,这样编程的时候不至于将很多控件混淆。
我们将它的Name属性修改为“MenuStrip_Main”。
单击“请在此键入”,输入“Game(&G)”,其中&G整体会显示成G这个字母下面加一个下划线,这样在用户使用的时候就可以通过按下字母G来访问这个按键了。
(当然,顶层菜单还需要按下Alt键,例如Alt + G键。
)输入以后的界面如图所示:
使用同样的方法,创建如下图所示的一个菜单栏:
注意到,图中的分割线,只需要输入一个减号,即“–”,再按下回车键即可得到。
我们一次介绍一下各个选项的功能,以便读者对它们有一个基本的了解。
菜单一共有两组,一个为游戏(Game),一个为帮助(Help):其中游戏菜单下分别包含了新游戏(New Game)、初级(Beginner)、中级(Intermediate)、高级(Expert)、设置(Setting)、标记(Mark)、音效(Audio)、排行榜(Rank)、退出(Exit);而帮助菜单下仅包含关于(About)。
其中新游戏用来开始一场新的游戏;初级、中级、高级,用于选择不同的游戏难度;设置用于自定义扫雷区域的大小以及地雷的数目;标记用于设置是否启用红旗、问号这一类的标记;音效用于设置是否启用音效;排行榜用于对游戏时间进行排名(仅记录初级、中级、高级的结果,对于自定义游戏的
结果不进行记录);退出用于退出游戏。
最后,关于用来显示游戏的一些关于信息。
对于初级、中级、高级、标记、音效这几个菜单,我们需要在它的前面显示它的状态,即是否被选中,如果选中了就会在它的前面出现一个勾,如果没有选中,则没有,这是我们以后需要实现的功能,在此先进行说明。
至此,我们的菜单栏就制作完成了。
接下来我们需要制作一个用来记录地雷数目以及用时的功能。
我们选择工具箱中容器的TableLayoutPanel,双击该项目,会在窗口中创建一个TableLayoutPanel控件。
同样我们先将它的Name属性修改为“TableLayoutPanel_Main”。
展开它的Size属性,将Height属性修改为48。
同时修改它的Dock属性,选择Bottom,如图所示:
我们会发现它会自动吸附在底部,如果改成别的参数则会吸附在别的位置,这里我们设置为Bottom。
同时将RowCount属性设置为1,ColumnCount属性设置为9。
打开Columns属性对话框,按照下图进行修改:
这里进行一些解释,其中Column1、Column3、Column5、Column7、Column9为中心对称的,用来设置边距,不放置任何控件,纯粹为了排版需要。
接下来我们需要在Column2、Column8中分别放置地雷(Mine_Show.png)以及秒
表(Timer.png)的图标,表示剩余的地雷数目以及已用时长。
而Columns4、Columns6中分别放置用于显示地雷数目以及已用时长的文本。
下载地址:地雷、秒表。
在工具箱中展开公共控件,找到PictureBox,将它拖动到TableLayoutPanel 的第二个列中,同时将它的Name属性设置为“PictureBox_Mine”,Dock 属性设置为Fill(表示充满整个页面,此处的页面即TableLayoutPanel中Column2全部),BackgroundImage属性定位到Mine_Show.png文件(通过导入按钮),BackgroundImageLayout属性设置为Stretch(表示自动缩放图像大小)。
使用同样的方法加入一个PictureBox控件,将它放到Column8中,同时将Name属性设置为“PictureBox_Timer”,BackgroundImage属性定位到Timer.png文件,其余与PictureBox_Mine设置相同。
其中图片导入的方法如下图所示:
接下来我们需要添加两个用于显示内容的Label控件,展开工具箱中的公共控件,找到Label控件,分别拖动到Column4和Column6,并且将Dock属性设置为Fill,TextAlign属性设置为MiddleCenter(使文字显示在控件
中心),Font属性设置为“Consolas, 16.2pt”(即Consolas字体,字号三号)。
其Name属性及Text属性分别修改为“Label_Mine”和
“Label_Timer”。
同时将它们的ForeColor属性分别设置为“DarkRed”和“HotTrack”(这项属性用于设置文字显示的颜色)。
至此界面已经基本完成了,但是我们还需要增加一个控件——Timer,用来计时。
展开工具箱中的组件,双击Timer。
这个控件不会在界面上显示,但是会在后台进行计时功能。
同样,我们将它的Name属性修改为“Timer_Main”,Interval属性修改为“1000”(这里为计时间隔,以毫秒为单位,此处为1000毫秒,即1秒计时一次)。
最后按下Ctrl + F5进行编译查看结果,相较于第一节中的界面已经有了很大的改观,如图所示:
到这里或许读者会问,最为重要的扫雷区域怎么制作呢,关于这个区域,我们将会使用程序来生成,而不是使用控件。
我们将会在下一节中进行讲解。
3、雷区绘制
这一节我们主要涉及界面中雷区的绘制方法。
首先来考虑几个问题。
为了保存整个雷区的信息,我们需要哪些数据。
显然,除了要保存雷区的宽度和高度(分别定义为宽和高方向上方块的个数)外,还需要保存地雷的数目。
这样我们需要用到三个变量nWidth, nHeight, nMineCnt分别保存雷区的宽度、雷区的高度以及地雷的数目。
右击窗口,选择查看代码,也可以使用快捷键F7,如下图所示:
可以看到这样的代码界面:
在图示位置添加下图所示的代码,用来定义扫雷区域的基本变量,它们的含义在上文或者注释中都有提及:
为了方便设置这三个参数,我们可以定义一个新的函数SetGame,如下图所示:
这样,我们就可以通过调用SetGame函数来设置游戏的参数了。
为了方便阅读,我们可以为这个函数加上一个注释,在函数名上方输入”///“,程序会自动生成一个注释块,如下图所示:
输入相关信息,第二行的内容是用来对函数的作用进行说明,下面的三行分别用来对三个参数的作用进行说明,将它们修改如下:
这样当我们输入这个函数的时候(当然,我们目前还不需要调用这个函数。
),就会显示出来对应的注释,如下图所示:
到目前为止,我们的代码应该是这样的:
接下来我们可以定义几个辅助函数,分别表示设置游戏参数为初级、中级、
高级,如下图所示:
在我们开始游戏的时候,我们希望它自动获取上次的游戏设置,如果这是第一次开始游戏,那么将游戏设置为初级。
我们先来看一下第一个需求,自动获取上次的游戏设置,这就意味着我们需要在上次游戏关闭的时候将上次的游戏设置保存下来。
保存到哪里呢,我们在这里采用Setting文件来保存这些数据。
工程创建的时候,系统会自动生成一个Setting文件。
因此我们不需要自己创建,只需要使用原有的Setting 文件即可。
在右方解决方案资源管理器面板中展开Properties,右击Settings.settings,选择打开即可。
打开后的界面如图所示:
按照下图对它进行设置,设置完成后按Ctrl + S进行保存。
注意到第四栏值,我们将它初始化为初级的参数,也就完成了我们刚才的第二个需求——无法找到上一次设置的时候,我们将它置为初级模式。
就行了,我们需要通过代码将这些参数读入到定义的变量中去,加入如下图所示的代码:
有了这些参数我们就可以绘制雷区了,我们假定雷区为32×32的小方块,并且四周有一圈宽度为1的留白,用于与其它雷区区别,这样,每个雷区的实际大小为34×34。
接下去,我们将窗口切换到界面布局,选中主窗口,在左边的属性面板中,单击事件按钮,并找到Paint事件,双击该条目,系统会自动创建一个事件,我们将在这里绘制雷区。
如下图所示:
添加Paint事件以后,程序会自动跳到代码编辑窗口,并且会看到如下的代码:
将Paint函数中的内容修改如下:
然后,我们按下Ctrl + F5就可以看到下面的效果:
看上去感觉样式很奇怪,后边多出了很多空白的区域,而且下方还有一些部分没有显示出来,因为我们还没有根据游戏的参数调整窗口的大小。
新建一个名为UpdateSize的函数,输入下图的代码:
接下来我们在SetGame函数中调用这个函数,使得修改游戏参数的时候自动修改窗口大小,代码如下:
最后不要忘记了在初始化读入上次游戏参数后也需要修改窗口大小,代码如下:
最后按Ctrl + F5编译运行,得到最终结果:
到目前为止,扫雷的界面已经基本出来了,下一节我们主要介绍一下菜单的相关代码。
4、菜单操作
我们现在的程序单击菜单的时候不会有任何反应,这一节我们主要介绍菜单的相关代码,使得菜单能够正常使用。
在第二节中,我们曾经提出希望在对应级别(初级、中级、高级、自定义)的按钮的显示勾,以表示目前的游戏等级。
我们有两个时候需要对它进行操作,一个是加载上次游戏设置的时候,一个是在游戏过程中进行设置的时候,我们先介绍第一种情况。
按照下图修改代码:
注意到beginnerBToolStripMenuItem、intermediateIToolStripMenuItem、expertEToolStripMenuItem、settingSToolStripMenuItem分别表示四个等级的菜单的名称,通过修改它们的Checked属性来达到修改对应项目前面勾的状态。
我们还需要在初始化函数中调用这个函数:
这时候,我们运行就会发现Beginner菜单前面的勾被选中了:
下面我们来处理一下各个菜单按钮的事件,我们只需要双击菜单上的按钮,程序会自动创建对应的单击事件,我们只需要在其中书写代码即可。
我们首先来创建Beginner、Intermediate、Expert、Exit、About菜单项目对应的事件。
我们先介绍Beginner、Intermediate、Expert菜单项目对应的代码:
接下来,我们处理Exit事件,我们希望在退出游戏之前询问游戏者是否确认退出,代码如下:
然后,我们来处理About事件,我们希望得到类似Windows默认关于窗口的界面。
为此,首先我们需要引用一个类,在代码的开头部分加上下图高亮部
分的代码:
为了实现这样的功能,我们需要调用Windows系统内部的一个API,添加这样一个函数:
在About事件中添加如下代码进行调用:
运行以后查看结果如下图所示:
可能细心的读者会发现,虽然我们通过菜单修改了游戏等级,但是我们窗口中的雷区却没有发生变化,因为我们没有在菜单被按下的时候没有调用UpdateSize函数。
在三个按钮的事件中添加对UpdateSize函数的调用即可。
代码如下:
在此运行的时候,我们发现修改游戏等级的时候,游戏界面也会发生相应的变化。
但是还有一点瑕疵,就是在大规模变成小规模的时候,边上会有多余的雷区。
为了修正这个Bug,我们需要将原来Paint事件中代码放在一个新的函数PaintGame中,同时添加高亮区域的代码,并且在Paint事件中进行调用,代码如下:
除此之外,在UpdateSize最后也需要调用PaintGame函数,如下图所示:
再次运行的时候,就会发现边上多余的雷区已经消失了。
接下来我们处理Mark以及Audio两个事件,我们需要定义两个变量来记录它们的数据,因为这对于我们以后的开发有很大的关系。
如下图所示:
同样我们需要在游戏开始的时候读取上次的数据,如果没有则都设置为真。
我们首先看Setting文件:
同时加入以下代码:
同时为Mark以及Audio菜单项目添加如下的事件:
至此,我们还剩New Game、Setting、Rank三个菜单选项的事件没有涉及,因为它们需用用到更多的内容,我们将会在下一节中进行讲解。
5、菜单操作(续)
上一节中,我们还剩下Setting和Rank两个菜单项目没有设置事件,是因为它们都涉及到了弹出一个新的窗口。
这一节,我们将主要介绍创建窗口的方法,以及窗口之间的数据通信。
首先,我们新建一个窗口,在右侧找到解决方案资源管理器,右击Minesweeper 项目名,选择添加,如图所示:
选择新建项,如图所示:
在弹出的窗口中选择Windows 窗体,并将名称修改为Form_Setting.cs,单击确定,如图所示
将新建的窗口Icon属性修改为扫雷的图标,将MaximizeBox属性修改为False,将Name属性修改为Form_Setting,将BackColor属性修改为White,将Text 属性修改为Minesweeper,将FormBorderStyle属性改为FixedSingle。
在左边工具箱面板下公共控件中找到Label控件,双击添加到窗口中,将它
的Name属性修改为Label_Width,Text属性修改为“Width:”,并移动到合适的位置,再在工具箱面板中找到NumericUpDown控件,双击添加到窗口中,将它的Name属性修改为NumericUpDown_Width,Minimun属性修改为1,Maximun属性修改为30,(其中Minimum以及Maximun的属性表示为数字的
变化范围。
)TextAlign属性修改为Center。
其中Minimum以及Maximun的
属性表示为数字的变化范围。
如下图所示:
重复上述操作,添加雷区高度(Height),地雷数目(Mine)的控件,其中
高度的变化范围为1至16,数目的变化范围为1至99。
如下图所示:
接下来我们需要添加两个按钮,用来确定修改以及取消修改。
在左侧工具箱面板的公共控件中找到Button控件,双击添加到窗体中,将它的Name属性修改为Button_OK,Text属性修改为OK。
同时添加一个取消按钮(Cancel),如下图所示:
然后拖动窗口右下方的白点,调整窗口大小到合适的位置,如下图所示:
接下来我们用同样的方法和设置来创建一个排行榜的窗口(Form_Rank)。
放置三个标签,分别表示初级(Label_Beginner)、中级(Label_Intermediate)、高级(Label_Expert)的最高分,并且加入两个按钮,表示重新计分(Button_Reset)、确定(OK),如图所示:
接下来,我们就可以调用这两个窗口了。
打开Form_Main窗口,为Setting
和Rank分别添加单击事件(双击菜单项,程序自动添加该时间),并且写下如下代码:
这样,我们就完成了对窗口的调用。
接下去,我们先来实现较为简单的Rank 窗口。
每次读取最高分的数据,这也就要求我们每次记录得分。
打开Settings文件,添加三种等级的最高分,并且将初始值都设置为999,如下图所示:
打开排名窗口,双击窗口标题栏,程序会自动添加一个Load事件,我们在这里添加如下代码:
这样,我们调试一下,就可以看到排名的效果了:
接下去我们需要为Reset以及OK按钮添加事件。
首先考虑OK按钮,双击按钮,添加单击事件,并且添加如下代码:
对于Reset按钮,我们添加如下代码:
这样,我们就完成了排行榜的功能。
接下来我们来考虑设置窗口的功能。
我们首先为Cancel按钮添加如下代码:
我们考虑OK按钮,我们需要和Form_Main窗口进行通信,修改Form_Setting 的构造函数,如图所示:
接着将Form_Main中nWidth、nHeight、nMine变量设置为公有类型:
然后修改对Setting窗口的调用函数:
至此,我们就完成了Setting窗口和Main窗口的信息连接,双击Setting窗口的标题栏,修改它的Load事件如下,以实现对Main窗口中原有游戏参数的读取:
这样,我们运行的时候就可以看到Setting窗口自动获取了Main中的游戏参数:
接下来我们为OK按钮添加如下的事件:
这样,我们就完成了游戏规模的设置,有时候,我们发现弹出来的设置窗口以及排行窗口显示的位置都偏左上角,我们对它进行一些调整,修改它们的StartPosition属性为CenterParent。
最后,在调用完设置窗口后,还需要UpdateSize一下来实现对游戏规模的修改:
最终我们可以实现各种大小的雷区:
这一节暂且讲到这里,下一节我们将会介绍地雷数目以及时间秒数的显示,以及扫雷内部模型的建立。
6、内部实现
在进行内部实现之前,我们先来考虑一下扫雷的内部逻辑。
首先,我们需要保存每个点上是否有地雷,如果没有地雷,那么要显示与它紧邻的八个格子中一共有多少的地雷。
还需保存每个雷区的状态(点开,未点开,红旗,问号)。
我们考虑定义两个常量,表示地雷的最大范围:
接下来定义两个数组,用来保存上述两种数据。
对于第一类数据,我们使用-1表示该区域有地雷,使用数字表示与它紧邻的八个格子中一共有多少地雷;对于第二类数据,我们使用0表示未点开,1表示点开,2表示红旗,3表示问号。
如下图所示:
接下来我们需要初始化游戏数据,双击New Game菜单,输入游戏初始化代码:
其中涉及到了dx以及dy这两个偏移量常量数组的定义,如下图所示:
接下来我我们来实现一些特效,例如高亮当前鼠标悬浮位置的雷区,打开设计窗口,添加MouseMove事件:
为此,我们还需要定义一个变量MouseFocus来记录当前的高亮点,代码分别如下:
同时,我们需要在初始化的时候对它进行清零操作,将下面的清零代码加入到刚才的初始化函数中:
接下来我们需要修改PaintGame函数,添加对高亮点的绘制:
此时,我们可以尝试运行,然而却发现界面会一直在闪,使得游戏体验急剧下降,为了修复这个问题,我们只需要在构造函数中添加一句代码,开启双缓冲即可,代码如下:
然而我们发现效果并没有很大的提升,这是由于我们之前定义的PaintGame 函数所导致的,它不断的定义新的Graphics实例,使得这个过程变得很慢,我们可以直接使用Paint事件中的Graphics实例。
首先修改PaintGame函数的定义以及部分实现,如下图所示:
同时修改Paint事件中的调用函数:
对于其它非Paint函数调用的PaintGame函数,一律修改为this.Refresh();即可,如下图所示:
再次运行的时候,我们发现已经没有了之前的闪屏问题了。
然而我们却发现了新的问题,对于当前移动到的位置坐标获取不准确,我们进行下面的调整:
此时,我们运行程序的时候,就会发现界面基本已经完成了。
接下来我们需要为Setting菜单添加一个单击确定按钮以后就自动开始游戏的功能,为此我们只需要修改UpdateSize函数,代码如下:
接下来我们需要修改一下显示地雷数目以及计时的标签,在新建游戏的函数中修改如下:
运行后,我们发现计时器并没有显示出来,因为我们还没有为计时器摄制事件。
在设计窗口页面中双击计时器,程序自动创建计时器事件,输入下面的代码:
同时记得在设计页面中将记时器的Interval属性设置为1000,表示每秒执行一次计时器事件代码。
最后,我们运行程序,效果如下:
下一讲将会涉及具体的鼠标单击以及右击雷区时的逻辑事件的判定。
7、内部实现
这一节我们主要讲解扫雷时鼠标单击的事件。
我们首先介绍左键单击的事件,分为两种情况——遇到地雷,游戏结束;不是地雷,自动点开相邻的非地雷区域,并且显示对应地雷区域周围地雷的数目。
我们首先需要为Form_Main添加MouseDown以及MouseUp事件,分别用来监测鼠标按下以及弹起的事件信息。
再定义两个全局变量,用来标识鼠标左键以及右键是否被按下,代码如下:
在MouseDown事件中输入下面的代码:
在MouseUp事件中,我们需要获取鼠标弹起前按下的鼠标按键的状态信息,代码如下:。