首页 > 编程 > C# > 正文

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

2020-01-24 00:27:05
字体:
来源:转载
供稿:网友

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


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

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表