软件编程
位置:首页>> 软件编程>> C#编程>> C#带你玩扫雷(附源码)

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

作者:Cosecant  发布时间:2021-11-04 05:54:23 

标签:C#,扫雷

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

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

来源:http://www.jianshu.com/p/1c6be950992a

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com