C#面向对象基于winform的扫雷游戏和简单的算法改进

来源:互联网 发布:安广网络池州分公司 编辑:程序博客网 时间:2024/06/08 12:26

时间过得很快啊,研三的都毕业离校了,实验室有一个硬件演示系统的上位机部分需要我继承下来,由于是用C#编写的,所以最近看了一下这方面的内容。光看不练效果自然不好,在六维上找来一个C#开发扫雷游戏的视频,边看编写,虽然视频有些部分没有讲到,但还是学到不少东西,也通过自己的理解对视频给出的部分算法还有界面做了改变。文章最后会附上源码,正文里会对改进的部分做一下分析。



1、面向对象设计与分析

一个扫雷游戏界面,以一个主窗体(Form)呈现在玩家面前,窗体中包含几个控制按钮、难度设置下拉菜单、计时器,最核心的部分还是扫雷的区域,这部分我们可以把它抽象成两个对象,每个对象对应一个类,一个是雷区(minefiled),另一个是布满雷区的方格(pane),这样就形成三个级别的对象:pane、minefield、Form,每一级对象都有自己的方法,并且相邻级别的对象通过调用这些方法来完成不同类之间的通信。

1.1 基本单元方格pane

pane继承自button,当点击某个pane时,该pane会调用Mark(),Open(),或者Reset()来改变自身的状态,如果该pane有雷(HasMine==true),则游戏结束,否则显示数字AroundMineCount。



1.2 雷区minefield

雷区继承自用户控件。

初始化雷区包括四个步骤:1、产生指定数目的pane,并添加鼠标点击事件;2、由于步骤1中生成的pane是重叠在一起的,需要对他们在雷区中重新布局;3、随机埋雷;4、计算每个pane周围有雷方格的数量。

DisPlayall()方法是在游戏结束,或者玩家需要帮助的时候调用的,它将打开所有方格(有雷显示雷,无雷显示数字AroundMineCount)。

DisplayAround()方法是在玩家点击某个方格pane后,为了降低游戏难度,会显示该方格周围方格的情况,我根据自己的理解对这个方法做了改变,下一部分会讲到。


2、算法改进

2.1 获取某个方格周围的方格集合(GetPanesAround(Pane p))

在程序中计算相邻方格雷数和显示周围方格的情况,均需要用到此函数。

视频中的代码为:

private List<CrlPane> GetPanesAround(CrlPane pane)        {            List<CrlPane> result = new List<CrlPane>();            // 通过递归明示当前方格四周的所有方格            int paneHeight = this.GetPaneSize().Height;            int paneWidth = this.GetPaneSize().Width;            foreach (CrlPane p in this.Controls)            {                // 以下四种判断分别是:                // 第一行:找出与当前方格平行的相邻方格(1或2)                // 第二行:找出与当前方格垂直的相邻方格(1或2)                // 第三四行:找出与当前方格斜对角的相邻方格(1或2或3或4)                // 综上,当前方格的相邻方格数可能为3~8个                if (Math.Abs(p.Top - pane.Top) == 0 && Math.Abs(p.Left - pane.Left) == paneWidth                    ||                    Math.Abs(p.Left - pane.Left) == 0 && Math.Abs(p.Top - pane.Top) == paneHeight                    ||                    Math.Abs(p.Top - pane.Top) == paneHeight && Math.Abs(p.Left - pane.Left) == paneWidth                    ||                    Math.Abs(p.Left - pane.Left) == paneWidth && Math.Abs(p.Top - pane.Top) == paneHeight)                {                    result.Add(p);                }            }            return result;}
我的代码为:

private List<CrlPane> GetPanesAround(CrlPane pane)        {           List<CrlPane> result = new List<CrlPane>();            foreach (CrlPane p in this.Controls)            {                if (Math.Abs(p.Left - pane.Left) <= pane.Width && Math.Abs(p.Top - pane.Top) <= pane.Height && p.Location != pane.Location)                {                    result.Add(p);                }            }            return result;        }
2.2 检查是否已完成扫雷IsAllMineSweeped()

用户每次点击鼠标,游戏不仅要对pane的状态进行修改,还要检查是否已完成扫雷。

视频中给出的代码为:

private bool IsAllMineSweeped()        {            int markedCount = 0;            int mineCount = 0;            foreach (CrlPane pane in this.Controls)            {                if (pane.HasMine)                {                    mineCount++;                }                if (pane.State == PaneState.Marked)                {                    markedCount++;                    if (!pane.HasMine)                    {                        //存在做了标记但没有地雷的方格,扫雷没有正确完成。                        return false;                    }                }            }            return mineCount == markedCount;        }
我的理解是这样的,雷区中的每个pane从两个角度各有两种状态,有雷、无雷,已标记、未标记,依次遍历雷区中的pane,如果某个pane有雷且未标记,或者无雷已标记,则就可以确定扫雷未完成,当所有pane都不是这两种情况时,扫雷成功。我的代码如下:

private bool IsAllMineSweeped()        {             foreach (CrlPane p in this.Controls)            {                if ((p.HasMine && p.State != PaneState.Marked) || (!p.HasMine && p.State == PaneState.Marked))                    return false;            }            return true;        }

2.3 显示某个pane周围有雷方格的数量DisPlayAround(Pane pane)

DisPlayAround(Pane pane)方法是在玩家点击某个方格pane后,显示该方格本身及周围方格内容的情况,采用递归来实现,此方法是整个游戏的核心和难点,需要特别注意避免循环递归造成的死循环。

视频中给出的代码为:

public void DisplayAround(CrlPane pane)        {            if (pane.State == PaneState.Opened || pane.HasMine)            {                return;            }             //明示当前方格本身            pane.Open();             //通过递归明示当前方格四周的所有方格            List<CrlPane> panesAround = this.GetPanesAround(pane);            foreach (CrlPane p in panesAround)            {                 //如果该方格四周没有相邻的地雷,则递归                if (this.GetMineCountAround(p) == 0)                {                    this.DisplayAround(p);                }                else                {                    if (p.State != PaneState.Opened && !p.HasMine)                    {                        p.Open();                    }                }                if (!p.HasMine && p.State == PaneState.Closed && this.GetMineCountAround(p)!=0)                {                    DisplayAround(p);                }            }                    }
我的理解是,DisPlayAround(Pane pane)显示该方格本身及周围方格内容的情况,但是显示周围方格不能无条件一直显示下去,我根据自己的理解,认为其边界应该为:有雷方格、内容为数字的方格(打开以后显示数字)、minefield本身的边界,也就是说,如果DisPlayAround(Pane pane)中的参数pane打开以后显示数字(亦即相邻方格中有雷),则不再递归该方法,只显示该pane本身的内容。我的代码如下。

public void DisplayAround(CrlPane pane)        {                        if (pane.State == PaneState.Marked || pane.State == PaneState.Opened)                return;            pane.Open();            if (GetMineCountAround(pane) == 0)            {                List<CrlPane> panes = this.GetPanesAround(pane);                foreach (CrlPane p in panes)                {                    if (GetMineCountAround(p) != 0 && p.State == PaneState.Closed)                        p.Open();                    else if (GetMineCountAround(p) == 0 && p.State == PaneState.Closed)                        DisplayAround(p);                }            }        }
3、 小结

通过这个小游戏学到以下东西:
a、UI中的面向对象的分析和设计的简单思路;

b、C#编程中的知识,如:事件的定义和使用、资源的使用,还有一些C# winform中控件和语法的一些内容;

c、体会到了逻辑在编程中的意义,几个算法改进均与此有关系。

完整代码下载地址:http://download.csdn.net/detail/long280310/7320233。

最后,感谢录制并发布源代码的王老师,附上他的博客地址:http://blog.sina.com.cn/jbwang。

                                             
1 0
原创粉丝点击