C#带你玩扫雷(附源码)
作者:Cosecant 发布时间:2021-11-04 05:54:23
标签: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


猜你喜欢
- 由于最近通过SDK-Manager更新了build-tools,当要用到dx.jar这个包时,自动调用最新build-tools中dx.ja
- 一、泛型简介1.引入泛型的目的了解引入泛型的动机,就先从语法糖开始了解。语法糖语法糖(Syntactic Sugar),也称糖衣语法,是由英
- 前言因为最近的项目需要使用录音功能,开始的想法是Button+OnTouchListener+Dialog实现,在大部分手机中都没问题,只有
- 本文实例为大家分享了C++实现企业职工工资管理系统的具体代码,供大家参考,具体内容如下课程设计目的和要求工资管理要和人事管理相联系,生成企业
- 前言springmvc中有两种很普遍的AOP实现:1.过滤器(Filter)2. * (Interceptor)本篇面对的是一些刚接触spr
- 概述spirng-aop模块是Spring框架中的核心模块,虽然Spring Ioc container并不依赖AOP,但AOP给Ioc的实
- 源码:class T {int m = 8;}T t = new T();汇编码:0 new #2 <T>3 dup4 invo
- 缓存可以说是加速服务响应速度的一种非常有效并且简单的方式。在缓存领域,有很多知名的框架,如EhCache 、Guava、HazelCast等
- 打印Java程序的线程栈信息jstack可以得知当前线程的运行情况安装jstack等命令集,jstack是开发版本jdk的一部分,不是开发版
- 昨天使用mybatis-plus。使用自动填充后发现了两个问题。一个是填充数据为null,一个是当使用了mybatis-plus的乐观锁,自
- 本文实例为大家分享了java转树形结构工具类的具体代码,供大家参考,具体内容如下import com.alibaba.fastjson.JS
- 前言StringJoiner是Java里1.8新增的类,主要是帮助我们把一个列表拼接字符串, 或许有一部分人没有接触过. 所以本文将从使用例
- Spring @Async无法实现异步问题原因项目中存在2个配置文件:springMVC.xml和beanDefines.xml,它们都配置
- Android开发,触控无处不在。对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑。View事件的分发机制,不仅在做业务需求中会碰到
- SEATA概要seata 是alibaba 出的一款分布式事务管理器,他有侵入性小,实现简单等特点。我们能够使用seata 实现分布式事务管
- C# 程序的通用结构C# 程序可由一个或多个文件组成。每个文件都可以包含零个或零个以上的命名空间。一个命名空间除了可包含其他命名空间外,还可
- 公司经理把我拉出来,死马当活马医,做一个安卓app,作为刚毕业几个月且只是培训了几个月的小白来说,这无疑是一个非常大的挑战,当然最大的挑战不
- 1. 前言Spring最重要的一个概念当属Bean了,我们写的Controller、Service、Dao凡是加了对应注解交给Spring管
- 1 @SpringBootApplication自动配置原理@SpringBootApplication是一个组合注解,主要由@Compon
- 在hibernate5中,有了一些新的变动: 新引导 APISpatial/GIS 支持Java 8 支持扩展 AUTO