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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。
相关文章
- 我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
- 这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
- 这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
- 本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
- 这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)
这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25- 这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
- 这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
- 最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
- 这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
- 这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
- 这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
- 这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
- 下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
- 这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25