欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#带你玩扫雷(附源码)

程序员文章站 2024-02-11 21:04:46
扫雷游戏,大家都应该玩过吧!其实规则也很简单,可是我们想自己实现一个扫雷,我们应该怎么做呢? step1: 知晓游戏原理 扫雷就是要把所有非地雷的格子揭开即...

扫雷游戏,大家都应该玩过吧!其实规则也很简单,可是我们想自己实现一个扫雷,我们应该怎么做呢?

C#带你玩扫雷(附源码)

step1: 知晓游戏原理

扫雷就是要把所有非地雷的格子揭开即胜利;踩到地雷格子就算失败。游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷;如果点开的格子为空白格,即其周围有0颗雷,则其周围格子自动打开;如果其周围还有空白格,则会引发连锁反应;在你认为有雷的格子上,点击右键即可标记雷;如果一个已打开格子周围所有的雷已经正确标出,则可以在此格上同时点击鼠标左右键以打开其周围剩余的无雷格。

1代表1的上下左右及斜角合计有一颗雷,依次轮推,2则有2颗,3则有3颗..

在确实是炸弹的方格上点了旗子,就安全了,不是炸弹的被点了旗子,后面会被炸死的..问号就先不确定这里有没有炸弹,不会存在点错了被炸死的状况..

step2: 由step1可知,游戏由格子组成,翻译成代码语言就叫做数组,也就是游戏地图就是一个二维数组。格子对象,格子的值即当前雷的数量,那么此时我们暂定雷的数字标识为-1。除此之外,格子对象还有是否被显示,显示当前雷数量等属性,那么我们大概可以定义这样一个类:

 public class cellblockrole
  {
    /// <summary>
    /// 位于游戏地图中的坐标点x
    /// </summary>
    public int x { get; set; }

    /// <summary>
    /// 位于游戏地图中的坐标点y
    /// </summary>
    public int y { get; set; }

    /// <summary>
    /// 是否展示最后格子所代表的结果
    /// </summary>
    public bool isshowresult { get; set; } = false;

    /// <summary>
    /// 是否计算数字结果
    /// </summary>
    public bool iscomputeresult { get; set; } = false;

    /// <summary>
    /// 是否已经展示过计算结果了
    /// </summary>
    public bool ishasshowcomputed { get; set; } = false;

    /// <summary>
    /// 当前的格子的角色数字, -1:地雷,其他当前雷的数量
    /// </summary>
    public int number { set; get; } = 0;

    /// <summary>
    /// 是否被flag标识
    /// </summary>
    public bool isflag { get; set; } = false;

    /// <summary>
    /// 是否是雷
    /// </summary>
    public bool isboom => number == -1;

  }

绘制游戏ui画面,见代码:

using system;
using system.collections.generic;
using system.componentmodel;
using system.drawing;
using system.data;
using system.linq;
using system.text;
using system.threading;
using system.threading.tasks;
using system.windows.forms;
using sweeperlibrary.properties;
using timer = system.threading.timer;

namespace sweeperlibrary
{
  public delegate void ongameoverdelegate();

  public delegate void onshowanumberdelegate();

  public delegate void onpublishgametimedelegate(string timedescription);

  public partial class gameview : usercontrol
  {

    /// <summary>
    /// 游戏结束事件
    /// </summary>
    public event ongameoverdelegate ongameoverevent;

    /// <summary>
    /// 当一个格子被点击时,显示当前数字的事件
    /// </summary>
    public event onshowanumberdelegate onshowanumberevent;

    /// <summary>
    /// 发布当前游戏的时间
    /// </summary>
    public event onpublishgametimedelegate onpublishgametimeevent;

    /// <summary>
    /// 游戏绘制地图的每个格子的大小
    /// </summary>
    public static readonly int cellsize = 40;

    /// <summary>
    /// 游戏规模n*n
    /// </summary>
    public static readonly int gamecellcount = 10;

    /// <summary>
    /// 移动方向坐标点改变的数组
    /// </summary>
    public static readonly int[][] movedirectionpoints = {
      new[]{-1, -1},
      new[] {0, -1},
      new[] {1, -1},
      new[] {1, 0},
      new[] {1, 1},
      new[] {0, 1},
      new[] {-1, 1},
      new[] {-1, 0}
    };

    /// <summary>
    /// 随机数雷生成对象
    /// </summary>
    private static readonly random random = new random(guid.newguid().gethashcode());
    /// <summary>
    /// 游戏地图标识数组
    /// </summary>
    private cellblockrole[][] gamemap = new cellblockrole[gamecellcount][];

    /// <summary>
    /// 雷的数量,默认为10
    /// </summary>
    public int boomcount { get; set; } = 10;

    /// <summary>
    /// 游戏开始时间
    /// </summary>
    private datetime gamestarttime;

    /// <summary>
    /// 计时定时器
    /// </summary>
    private system.windows.forms.timer gametimer = new system.windows.forms.timer();

    public gameview()
    {
      initializecomponent();
      setstyle(controlstyles.optimizeddoublebuffer, true);
      setstyle(controlstyles.allpaintinginwmpaint, true);
      initgame(); //默认游戏已经开始
      setgametimer(); //设置游戏定时器
    }

    private void gameview_paint(object sender, painteventargs e)
    {
      width = gamecellcount + 1 + gamecellcount * cellsize;
      height = gamecellcount + 1 + gamecellcount * cellsize;
      //绘制游戏界面
      graphics graphics = e.graphics;
      graphics.clear(color.whitesmoke);
      if (gamemap != null && gamemap.length > 0 && gamemap[0] != null && gamemap[0].length > 0)
      {
        for (int y = 0; y < gamecellcount; y++)
        {
          for (int x = 0; x < gamecellcount; x++)
          {
            int dx = x + 1 + x * cellsize,
              dy = y + 1 + y * cellsize;
            cellblockrole cellblockrole = gamemap[y][x];
            graphics.fillrectangle(new solidbrush(cellblockrole.isshowresult ? color.lightslategray : color.whitesmoke),
              dx, dy, cellsize, cellsize);
            graphics.drawrectangle(new pen(color.lightgray), dx, dy, cellsize, cellsize);
            if (cellblockrole.isshowresult && cellblockrole.number != 0)
            {
              switch (cellblockrole.number)
              {
                case -1: //雷
                  graphics.drawimage(image.fromhbitmap(resources.boom.gethbitmap()), new rectanglef(dx, dy, cellsize, cellsize));
                  break;
                default: //数字
                  string drawtext = cellblockrole.number.tostring();
                  font textfont = new font(fontfamily.genericsansserif, 12, fontstyle.bold);
                  sizef textsize = graphics.measurestring(drawtext, textfont);
                  graphics.drawstring(drawtext, textfont, new solidbrush(color.white),
                    dx + (cellsize - textsize.width) / 2, dy + (cellsize - textsize.height) / 2);
                  break;
              }
            }
          }
        }
      }
    }

    private void gameview_mousedown(object sender, mouseeventargs e)
    {
      int px = (e.x - 1) / (cellsize + 1),
        py = (e.y - 1) / (cellsize + 1);
      switch (e.button)
      {
        case mousebuttons.left: //鼠标左键
          if (!gamemap[py][px].isshowresult)
          {
            if (gamemap[py][px].isboom)
            {
              new thread(() =>
              {
                showallcellblockrolenumber();
                if (this.invokerequired)
                {
                  methodinvoker del = invalidate;
                  this.invoke(del);
                } else
                {
                  invalidate();
                }
              }).start();
              gametimer.stop();
              ongameoverevent?.invoke();
            } else
            {
              new thread(() =>
              {
                showneiborhoodcellrolesbyposi(px, py);
                if (this.invokerequired)
                {
                  methodinvoker del = invalidate;
                  this.invoke(del);
                } else
                {
                  invalidate();
                }
              }).start();
              onshowanumberevent?.invoke();
            }
          }
          break;
        case mousebuttons.right: //鼠标右键
          break;
      }
    }

    /// <summary>
    /// 初始化游戏
    /// </summary>
    private void initgame()
    {
      new thread(() =>
      {
        initgamemap();
        generatebooms();
        if (this.invokerequired)
        {
          methodinvoker del = invalidate;
          this.invoke(del);
        } else
        {
          invalidate();
        }
      }).start();
    }

    /// <summary>
    /// 设置游戏定时器
    /// </summary>
    private void setgametimer()
    {
      gametimer.interval = 1000;
      gametimer.enabled = true;
      gametimer.tick += (sender, args) =>
      {
        long dmillisecond = datetime.now.millisecond - gamestarttime.millisecond;
        long hour = dmillisecond / 60 / 60 / 1000;
        long minute = (dmillisecond - hour * (60 * 60 * 1000)) / (60 * 1000);
        long second = ((dmillisecond - hour * (60 * 60 * 1000)) % (60 * 1000)) / 1000;
        onpublishgametimeevent?.invoke((hour > 0 ? (hour > 9 ? hour.tostring() : "0" + hour) + ":" : "")
                        + (minute > 9 ? minute.tostring() : "0" + minute) + ":" + (second > 9 ? second.tostring() : "0" + second));
      };
    }

    /// <summary>
    /// 初始化游戏地图
    /// </summary>
    private void initgamemap()
    {
      for (int i = 0; i < gamecellcount; i++)
      {
        gamemap[i] = new cellblockrole[gamecellcount];
        for (int j = 0; j < gamecellcount; j++)
        {
          gamemap[i][j] = new cellblockrole
          {
            x = j,
            y = i
          };
        }
      }
      gamestarttime = datetime.now;
      gametimer.start();
    }

    /// <summary>
    /// 重置游戏地图
    /// </summary>
    public void resetgamemap()
    {
      new thread(() =>
      {
        for (int i = 0; i < gamecellcount; i++)
        {
          for (int j = 0; j < gamecellcount; j++)
          {
            gamemap[i][j].x = j;
            gamemap[i][j].y = i;
            gamemap[i][j].number = 0;
            gamemap[i][j].isshowresult = false;
            gamemap[i][j].iscomputeresult = false;
            gamemap[i][j].ishasshowcomputed = false;

          }
        }
        generatebooms(); //生成一些雷
        if (this.invokerequired)
        {
          methodinvoker del = invalidate;
          this.invoke(del);
        } else
        {
          invalidate();
        }
      }).start();
      gamestarttime = datetime.now;
      gametimer.start();
    }

    /// <summary>
    /// 随机生成一些地雷
    /// </summary>
    public void generatebooms()
    {
      for (int i = 0; i < boomcount; i++)
      {
        int boomnumberindex = random.next(0, gamecellcount * gamecellcount - 1); //生成随机数的范围
        int boomx = boomnumberindex % gamecellcount,
          boomy = boomnumberindex / gamecellcount;
        if (gamemap[boomy][boomx].number == 0)
          gamemap[boomy][boomx].number = -1; //-1表示雷
        else // 已经存在雷了,所以要重新处理
          i--;
      }
      makeallnumbercomputeincellrole(0, 0); //默认从坐标(0,0)开始
    }

    /// <summary>
    /// 显示所有的格子的信息
    /// </summary>
    private void showallcellblockrolenumber()
    {
      for (int i = 0; i < gamecellcount; i++)
      {
        for (int j = 0; j < gamecellcount; j++)
        {
          gamemap[i][j].isshowresult = true;
        }
      }
    }

    /// <summary>
    /// 显示某点周边所有格子的数字
    /// </summary>
    /// <param name="posix">x轴坐标</param>
    /// <param name="posiy">y轴坐标</param>
    private void showneiborhoodcellrolesbyposi(int posix, int posiy)
    {
      gamemap[posiy][posix].isshowresult = true;
      gamemap[posiy][posix].ishasshowcomputed = true;
      int boomcount = getboomcountinneiborhood(posix, posiy);
      if (boomcount == 0) //如果周围没有雷,则翻开所有8个方向的相关数字
      {
        for (int i = 0; i < movedirectionpoints.length; i++)
        {
          int[] itemposi = movedirectionpoints[i];
          int rx = posix + itemposi[0],
            ry = posiy + itemposi[1];
          bool isnotoutindexrange = rx >= 0 && rx < gamecellcount && ry >= 0 && ry < gamecellcount;
          if (isnotoutindexrange) //防止坐标溢出
          {
            gamemap[ry][rx].isshowresult = true;
            if (!gamemap[ry][rx].ishasshowcomputed && gamemap[ry][rx].number == 0)
              showneiborhoodcellrolesbyposi(rx, ry);
          }
        }
      }
    }

    /// <summary>
    /// 获取某点附近的雷数量
    /// </summary>
    /// <param name="posix">x轴坐标点</param>
    /// <param name="posiy">y轴坐标点</param>
    /// <returns></returns>
    private int getboomcountinneiborhood(int posix, int posiy)
    {
      int boomcount = 0;
      for (int i = 0; i < movedirectionpoints.length; i++)
      {
        int[] itemposi = movedirectionpoints[i];
        int rx = posix + itemposi[0],
          ry = posiy + itemposi[1];
        bool isnotoutindexrange = rx >= 0 && rx < gamecellcount && ry >= 0 && ry < gamecellcount;
        if (isnotoutindexrange && gamemap[ry][rx].isboom) //防止坐标溢出
        {
          boomcount++;
        }
      }
      return boomcount;
    }

    /// <summary>
    /// 计算每个格子的数字标识
    /// </summary>
    /// <param name="posix">x轴坐标</param>
    /// <param name="posiy">y轴坐标</param>
    private void makeallnumbercomputeincellrole(int posix, int posiy)
    {
      int boomcount = getboomcountinneiborhood(posix, posiy);
      if (boomcount != 0) //如果周围没有雷,则计算周围的8个方向的格子
      {
        gamemap[posiy][posix].number = boomcount;
      } else
      {
        if (!gamemap[posiy][posix].isboom)
          gamemap[posiy][posix].number = 0;
      }
      gamemap[posiy][posix].iscomputeresult = true;
      for (int i = 0; i < movedirectionpoints.length; i++)
      {
        int[] itemposi = movedirectionpoints[i];
        int rx = posix + itemposi[0],
          ry = posiy + itemposi[1];
        bool isnotoutindexrange = rx >= 0 && rx < gamecellcount && ry >= 0 && ry < gamecellcount;
        if (isnotoutindexrange && !gamemap[ry][rx].iscomputeresult && !gamemap[ry][rx].isboom) //防止坐标溢出
        {
          makeallnumbercomputeincellrole(rx, ry);
        }
      }
    }

  }

}

主要代码已经实现,现已知现有代码的定时器由问题,暂时不支持flag(旗子标识)。当然代码中还有其他不足的地方,游戏持续优化中。。。

源代码地址:minesweeper-cshape_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。