[C#] [作品]  小游戏之扫雷 (Mine Sweeper) 开发过程及算法分析 扫雷原理

前几天突然想到做一个扫雷游戏来练练手,记得我刚接触程序的时候,就有一个同学做了个扫雷作为期末作品交上去的,当时还觉得这个扫雷还是比较复杂的,但到了现在这个时候就感觉是很简单的一个小游戏。

扫雷游戏大概流程如下:

  1. 创建地图。可以用控件的方式来做小格子,也可以用GDI+,但是对于绘图方面不太熟,就采用控件的方式。
  2. 根据地图的大小来决定地雷数,随机生成地雷。如果不好决定地图大小与地雷数之间的关系,也可以固定地雷数目。
  3. 计算每个地雷周围格子的数字。这个简单,遍历每个地雷,然后在周围8个格子上各加1就好。
  4. 事件绑定。处理鼠标左右键的点击,右击标记,左击打开格子。还要处理左右键同时按下的情况,自动打开格子。
  5. 胜负的判断

还有其它的一些杂项:

  1. 格子几个状态下的样式处理(按下、踩中的地雷、标记错误的地雷、旗子、关闭、打开,打开状态下又分为地雷,空白和数字,还有不同数字的颜色处理)。
  2. 打开格子的处理,数字和地雷直接打开,空白的话自动打开周围8个格子,8个格子中有空白的话继续打开,如此反复。
  3. 计时。

还有这些由于时间关系还没做:

  1. 第一次按下时一定不是地雷。这个可以在第一次点击之后再渲染地图,并且在生成地雷时避开点击的坐标。
  2. 自定义地图及地雷数。
  3. 重置地图。


使用C#面向对象编程:

MineCell.cs 格子对象

属性:格子底下的数字,-1代表地雷;坐标;格子当前状态。

核心方法:

ChangeStatus 改变格子状态

C#

//改变状态(打开,关闭,按下,旗子,问号,错误标记,踩中地雷)
public void ChangeStatus(string status)
{
    this.Status = status;
    if (status == "Open")
    {
        FlatAppearance.MouseDownBackColor = Color.LightGray;
        FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
        BackColor = FlatAppearance.MouseDownBackColor;

        if ( Number>0 ) ForeColor = MineMap.NumColors[Number-1];

        if (Number == -1)
        {
            Image = boom;
        }
        else if (Number == 0)
        {
            Text = "";
        }
        else
        {
            Text = Number.ToString();
        }
    }
    else if(status == "Close")
    {
        FlatAppearance.MouseDownBackColor = Color.LightGray;
        FlatAppearance.MouseOverBackColor = Color.FromArgb(250, 250, 250);
        BackColor = FlatAppearance.MouseOverBackColor;
        Text = "";
        Image = null;
    }
    else if(status == "Down")
    {
        BackColor = Color.LightGray;
    }
    else if(status == "Flag")
    {
        FlatAppearance.MouseDownBackColor = Color.FromArgb(250, 250, 250);
        FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
        BackColor = FlatAppearance.MouseDownBackColor;
        Image = Properties.Resources.flag;
    }
    else if(status == "Unknown")
    {
        FlatAppearance.MouseDownBackColor = Color.FromArgb(250, 250, 250);
        FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
        BackColor = FlatAppearance.MouseDownBackColor;
        Image = Properties.Resources.boom;
    }
    else if(status == "Error")
    {
        FlatAppearance.MouseDownBackColor = Color.LightGray;
        FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
        BackColor = FlatAppearance.MouseDownBackColor;
        Image = Properties.Resources.error;
    }
    else if(status == "Hit")
    {
        FlatAppearance.MouseDownBackColor = Color.Red;
        FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
        BackColor = FlatAppearance.MouseDownBackColor;
    }
}



MineMap.cs 地图对象

属性:地图数据,地雷总数,地雷坐标,错误标记坐标,以及一些初始化的数据:地图大小,格子大小,标记总数,未打开格子总数,数字对应的颜色,鼠标状态:左键按下,右键按下,左右键同时按下,左右键曾经同时按下。

核心方法:

计算地雷周围的数字

C#

Point[] aroundPoints = GetAroundPoint(mine);
foreach (Point aroundPoint in aroundPoints)
{
    MineCell cell = Map[aroundPoint.X, aroundPoint.Y];
    if (cell.Number != -1) cell.Number += 1;
}


GetAroundPoint 获取周围8个点

使用Where过滤边界情况

C#

public Point[] GetAroundPoint(Point p)
{
    Point[] points = new Point[8];
    points[0] = new Point(p.X - 1, p.Y - 1);
    points[1] = new Point(p.X    , p.Y - 1);
    points[2] = new Point(p.X + 1, p.Y - 1);
    points[3] = new Point(p.X - 1, p.Y    );
    points[4] = new Point(p.X + 1, p.Y    );
    points[5] = new Point(p.X - 1, p.Y + 1);
    points[6] = new Point(p.X    , p.Y + 1);
    points[7] = new Point(p.X + 1, p.Y + 1);

    points = points.Where(point => point.X >= 0)
        .Where(point => point.X < mapWidth)
        .Where(point => point.Y >= 0)
        .Where(point => point.Y < mapHeight).ToArray();
             
    return points;
}


左右键同时点击并释放时自动打开周围格子

C#

if (isBothButtonDowned)
{
    int aroundFlags = 0;
    Point[] aroundPoints = GetAroundPoint(((MineCell)sender).Point);
    foreach (Point point in aroundPoints)
    {
        MineCell aroundCell = Map[point.X, point.Y];
        if (aroundCell.Status == "Down") aroundCell.ChangeStatus("Close");
        if (aroundCell.Status == "Flag") aroundFlags++;
    }

    if (cell.Number == aroundFlags && cell.Status == "Open")
    {
        foreach (Point point in aroundPoints)
        {
            OpenCell(point);
        }
    }
}


胜利的判断

当标记的旗子数加上未打开的格子数刚好等于地雷数时,即可判断为完成。

C#

if(CloseCellTotal+FlagsTotal == MineTotal)
{

}


程序及源码下载:

Mine_Sweeper_20170116.rar

发表您的留言