WinformGDI+入门级实例——扫雷游戏(附源码)
写在前面:

本文将作为一个入门级的、结合源码的文章,旨在为刚刚接触GDI+编程或对相关知识感兴趣的读者做一个入门讲解。游戏尚且未完善,但基本功能都有,完整源码在文章结尾的附件中。
整体思路:
扫雷的游戏界面让我从一开始就想到了二维数组,事实上用二维数组来定义游戏数据确实是最符合人类思维的方式。(Square类会在后面解释)
1 //游戏数据
2 private readonly Square[,] _gameData;
有了这个开头,接下来就是填充二维数组的数据了,对于数据,我最初的想法是用int或枚举,当然,这是可行的,但涉及一个问题就是高耦合,所有操作将都在高层执行,难以维护。
于是我们用一个Square类表示一个小方块区。
1 /// <summary>
2 /// 表示游戏中一个方块区
3 /// </summary>
4 public sealed class Square
...
以枚举表示方块区的状态:
1 /// <summary>
2 /// 方块区状态
3 /// </summary>
4 public enum SquareStatus
5 {
6 /// <summary>
7 /// 闲置
8 /// </summary>
9 Idle,
10 /// <summary>
11 /// 已打开
12 /// </summary>
13 Opened,
14 /// <summary>
15 /// 已标记
16 /// </summary>
17 Marked,
18 /// <summary>
19 /// 已质疑
20 /// </summary>
21 Queried,
22 /// <summary>
23 /// 游戏结束
24 /// </summary>
25 GameOver,
26 /// <summary>
27 /// 标记失误(仅在游戏结束时用于绘制)
28 /// </summary>
29 MarkMissed
30 }
用Game类来表示一局游戏,其中包含游戏数据、游戏等级、雷区数、布雷方法等。
1 /// <summary>
2 /// 表示一局游戏
3 /// </summary>
4 public sealed class Game : IDisposable
5 ...
难点攻破:
游戏不大,涉及的难点也就不多,但对于刚接触GDI+的读者,一些地方还是比较麻烦的。
逻辑难点1:布雷
扫雷游戏有一个附加规则,就是第一次单击不论如何都不会踩到雷区,由于这个规则的存在,我们不能将布雷操作做在第一次单击之前。所以我们在游戏开局时假设所有方块区都没有雷。
1 /// <summary>
2 /// 开始游戏
3 /// </summary>
4 public void Start()
5 {
6 //假设所有方块区均非雷区
7 for (int i = 0; i < _gameData.GetLength(0); i++)
8 for (int j = 0; j < _gameData.GetLength(1); j++)
9 _gameData[i, j] = new Square(new Point(i, j), false, 0);
10 }
随后,在开局后第一次单击时布雷。
1 /// <summary>
2 /// 布雷
3 /// </summary>
4 /// <param name="startPt">首次单击点</param>
5 private void Mine(Point startPt)
6 {
7 Size area = new Size(_gameData.GetLength(0), _gameData.GetLength(1));
8 List<Point> excluded = new List<Point> { startPt };
9
10 //随机创建雷区
11 for (int i = 0; i < _minesCount; i++)
12 {
13 Point pt = GetRandomPoint(area, excluded);
14 _gameData[pt.X, pt.Y] = new Square(pt, true, 0);
15 excluded.Add(pt);
16 }
17
18 //创建非雷区
19 for (int i = 0; i < _gameData.GetLength(0); i++)
20 for (int j = 0; j < _gameData.GetLength(1); j++)
21 if (!_gameData[i, j].Mined)//非雷区
22 {
23 int minesAround = EnumSquaresAround(new Point(i, j)).Cast<Square>().Count(square => square.Mined);//周围雷数
24
25 _gameData[i, j] = new Square(new Point(i, j), false, minesAround);
26 }
27
28 _gameStarted = true;
29 }
先创建雷区,再创建非雷区,以便我们在创建非雷区时可以计算出非雷区周围的雷数,枚举周围方块的方法我们用yield创建一个枚举器。
1 /// <summary>
2 /// 枚举周围所有方块区
3 /// </summary>
4 /// <param name="squarePt">原方块区</param>
5 /// <returns>枚举数</returns>
6 private IEnumerable EnumSquaresAround(Point squarePt)
7 {
8 int i = squarePt.X, j = squarePt.Y;
9
10 //周围所有方块区
11 for (int x = i - 1; x <= i + 1; ++x)//横向
12 {
13 if (x < 0 || x >= _gameData.GetLength(0))//越界
14 continue;
15
16 for (int y = j - 1; y <= j + 1; ++y)//纵向
17 {
18 if (y < 0 || y >= _gameData.GetLength(1))//越界
19 continue;
20
21 if (x == squarePt.X && y == squarePt.Y)//排除自身
22 continue;
23
24 yield return _gameData[x, y];
25 }
26 }
27 }
逻辑难点2:当单击区周围无雷区(空白)时,自动批量打开周围所有非雷区
1 //如果是空白区,则递归相邻的所有空白区
2 if (_gameData[logicalPt.X, logicalPt.Y].MinesAround == 0)
3 AutoOpenAround(logicalPt);
1 /// <summary>
2 /// 自动打开周围非雷区方块(递归)
3 /// </summary>
4 /// <param name="squarePt">原方块逻辑坐标</param>
5 private void AutoOpenAround(Point squarePt)
6 {
7 //遍历周围方块
8 foreach (Square square in EnumSquaresAround(squarePt))
9 {
10 if (square.Mined || square.Status == Square.SquareStatus.Marked || square.Status == Square.SquareStatus.Opened)
11 continue;
12
13 square.LeftClick();//打开
14 //周围无雷区
15 if (square.MinesAround == 0)
16 AutoOpenAround(square.Location);//递归打开
17 }
18 }
绘图难点1:双缓冲以克服闪烁
从二维数组的结构来看,我们需要遍历整个二维数组,然后把每个Square绘制到winform上,但这会造成强烈的闪烁效果。因为是实时绘图,绘制的每一步都会实时显示在窗口上,所以我们看到的效果就是一个方块区一个方块区的出现在窗口上。
为了克服这种不友好的闪烁,双缓冲出现了,思路就是创建一个缓冲区(通常是一个内存中的位图),先将所有方块区绘制到这张位图上,绘制完成后,将位图贴到窗体上,最终效果将不再出现闪烁的情况。
1 //窗口图面
2 private readonly Graphics _wndGraphics;
3 //缓冲区
4 private readonly Bitmap _buffer;
5 //缓冲区图面
6 private readonly Graphics _bufferGraphics;
1 /// <summary>
2 /// 绘制一帧
3 /// </summary>
4 public void Draw()
5 {
6 for (int i = 0; i < _gameData.GetLength(0); i++)
7 for (int j = 0; j < _gameData.GetLength(1); j++)
8 _gameData[i, j].Draw(_bufferGraphics);
9
10 _wndGraphics.DrawImage(_buffer, new Point(_gameFieldOffset.Width, _gameFieldOffset.Height));
11 }
总结:
至此,所有难点基本攻破,完整代码大家参考附件,代码基于Windows XP版扫雷做的模仿,笔者能力有限,不足之处请大家多多指点。
附件:
WinformGDI+入门级实例——扫雷游戏(附源码)的更多相关文章
- (转载)WinformGDI+入门级实例——扫雷游戏(附源码)
本文将作为一个入门级的.结合源码的文章,旨在为刚刚接触GDI+编程或对相关知识感兴趣的读者做一个入门讲解.游戏尚且未完善,但基本功能都有,完整源码在文章结尾的附件中. 整体思路: 扫雷的游戏界面让我从 ...
- 业务类接口在TCP,HTTP,BLL模式下的实例 设计模式混搭 附源码一份
业务类接口在TCP,HTTP,BLL模式下的实例 设计模式混搭 附源码一份 WinForm酒店管理软件--框架这篇随笔可以说是我写的最被大家争议的随笔,一度是支持和反对是一样的多.大家对我做的这个行业 ...
- Python:游戏:扫雷(附源码)
这次我们基于 pygame 来做一个扫雷,上次有园友问我代码的 python 版本,我说明一下,我所有的代码都是基于 python 3.6 的. 先看截图,仿照 XP 上的扫雷做的,感觉 XP 上的样 ...
- C#共享内存实例 附源码
原文 C#共享内存实例 附源码 网上有C#共享内存类,不过功能太简单了,并且写内存每次都从开头写.故对此进行了改进,并做了个小例子,供需要的人参考. 主要改进点: 通过利用共享内存的一部分空间(以下称 ...
- (转)干货|这篇TensorFlow实例教程文章告诉你GANs为何引爆机器学习?(附源码)
干货|这篇TensorFlow实例教程文章告诉你GANs为何引爆机器学习?(附源码) 该博客来源自:https://mp.weixin.qq.com/s?__biz=MzA4NzE1NzYyMw==& ...
- 超详细的php用户注册页面填写信息完整实例(附源码)
这篇文章主要介绍了一个超详细的php用户注册页面填写信息完整实例,内容包括邮箱自动匹配.密码强度验证以及防止表单重复等,小编特别喜欢这篇文章,推荐给大家. 注册页面是大多数网站必备的页面,所以很有必要 ...
- HTML5与CSS3实例教程(第2版) 附源码 中文pdf扫描版
HTML5和CSS3技术是目前整个网页的基础.<HTML5与CSS3实例教程(第2版)>共分3部分,集中讨论了HTML5和CSS3规范及其技术的使用方法.这一版全面讲解了最新的HTML5和 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)
前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...
随机推荐
- 买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 两笔 交易. 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的 ...
- 不吹不黑,jupyter lab 3.0客观使用体验
1 简介 jupyter lab于近期发布了其具有里程碑意义的3.0版本,随之带来的一些重要新特性,想必广大读者朋友已在各大公众号所翻译转载的jupyter lab团队官方介绍文章中知晓了很多. 图1 ...
- 算法设计与分析 - 主定理Master theorem (分治法递推时间复杂度)
英文原版不上了 直接中文 定义 假设有递推关系式T(n)=aT(n/b)+f(n) 其中n为问题规模 a为递推的子问题数量 n/b为每个子问题的规模(假设每个子问题的规模基本一样) f(n)为递推以外 ...
- 面试官:你真的了解Redis分布式锁吗?
什么是分布式锁 说到Redis,我们第一想到的功能就是可以缓存数据,除此之外,Redis因为单进程.性能高的特点,它还经常被用于做分布式锁. 锁我们都知道,在程序中的作用就是同步工具,保证共享资源在同 ...
- Python列表推导式玩法
前言 列表做为python的基础,是必须学习的语法之一.一些基础的之前已经是反复温习和使用了,今天我们来学习它的进阶版-->列表推导式. 列表推导式: 优点:是将所有的值一次性加载到内存中,相比 ...
- CICD基础概念
windows下搭建jenkins:安装方法一:1.安装JDK,配置好环境变量2.下载安装最新版本Jenkins:登陆 http://mirrors.jenkins-ci.org/ 下载windows ...
- 利用iptables防火墙保护web服务器
实例:利用iptables防火墙保护web服务器 防火墙--->路由器-->交换机-->pc机 配置之前,清空下已有的规则,放在规则冲突不生效 工作中,先放行端口写完规则,再DROP ...
- [工作札记]03: 微软Winform窗体中ListView、DataGridView等控件的Bug,会导致程序编译失败,影响范围:到最新的.net4.7.2都有
工作中,我们发现了微软.net WinForm的一个Bug,会导致窗体设计器自动生成的代码失效,这个Bug从.net4.5到最新的.net4.7.2都存在,一直没有解决.最初是我在教学工作中发现的,后 ...
- spring boot 集成 websocket 实现消息主动
来源:https://www.cnblogs.com/leigepython/p/11058902.html pom.xml 1 <?xml version="1.0" en ...
- 02_Python基础
2.1 第一条编程语句 print("Hello, Python!") print("To be, or not to be, it's a question." ...