简单的总结一下,如何利用C#进行WinForm 扫雷小游戏的开发:
扫雷游戏的主要设计的类有三个: Main、Pane 、MineField
1)Main 是主控窗体,负责项目的启动和关闭;并协调内部各个组建模块的协调工作。
2)Pane是一个方格的封装,是雷区的重要组建;它表示一个方格的当前状态,以及是否布雷等信息。
3)MineField是雷区的封装,是游戏的核心组建;它负责方格的布局以及地雷的分布;并控制玩家的基本操作以及正确的响应。
类的实现:
一、 Pane类
功能描述:Pane是一个方格的封装,是雷区的重要组建;它表示一个方格的当前状态,以及是否布雷等信息
One.它所具有的公共属性:
名称 |
可见性 |
返回值类型 |
功能描述 |
AroundMineCount |
public |
int |
获取或设置当前方块周围地雷的数量 |
HasMine |
Public |
bool |
获取或设置当前方块是否又雷 |
State |
Public |
PaneState |
获取或设当前方块扫雷的状态 |
Two.它所具有的公共方法:
名称 |
可见性 |
返回类型 |
参数 |
功能描述 |
Mark |
public |
void |
无 |
把当前方块标记为【有雷】状态,即:插上一个小红旗。 |
Open |
public |
void |
无 |
打开该方块。 打开后如果如果有雷,则显示地理图标;否则如果周围有相邻的地理,则显示地雷数量。 |
Reset |
public |
void |
无 |
恢复关闭状态,即:取消Mark()的操作结果。 |
Three:源码:
//默认的构造方法
public Pane()
{InitializeComponent();
this.BackgroundImageLayout = ImageLayout.Stretch;
}
//公有属性:
Public bool HasMine { get; set; }
Public int AroundMineCount { get; set; }
Public PaneState State { get; set; } //由于它有几种状态,设置一个枚举类型属性
public enum PaneState
{
Closed, //关闭状态
Opened, //打开状态
Marked, //标记状态
}
//共有方法:
public void Mark() //标记当前方格为又雷状态,插个小红旗
{
this.BackgroundImage = Properties.Resources.Marked;
this.State = PaneState.Marked;
}
public void Reset() //恢复标记状态,取消小红旗标记
{
this.BackgroundImage = null;
this.State = PaneState.Closed;
}
//打开方法
//打开后如果如果有雷,则显示地理图标;否则如果周围有相邻的地理,则显示地雷数量。
public void Open()
{
if (this.HasMine)
{
this.BackgroundImage = Properties.Resources.MineBomp;
this.Enabled = false;
}
else
{
switch (this.AroundMineCount)
{
case 0:
this.BackgroundImage = null;
this.Enabled = false;
break;
case 1:
this.BackgroundImage = Properties.Resources.Num1;
this.Enabled = false;
break;
case 2:
this.BackgroundImage = Properties.Resources.Num2;
this.Enabled = false;
break;
case 3:
this.BackgroundImage = Properties.Resources.Num3;
this.Enabled = false;
break;
case 4:
this.BackgroundImage = Properties.Resources.Num4;
this.Enabled = false;
break;
case 5:
this.BackgroundImage = Properties.Resources.Num5;
this.Enabled = false;
break;
case 6:
this.BackgroundImage = Properties.Resources.Num6;
this.Enabled = false;
break;
case 7:
this.BackgroundImage = Properties.Resources.Num7;
this.Enabled = false;
break;
case 8:
this.BackgroundImage = Properties.Resources.Num8;
this.Enabled = false;
break;
}
}
}
二、 MineField类
功能描述:CrlMineField是雷区的封装,是游戏的核心组建;它负责方格的布局以及地雷的分布;并控制玩家的基本操作以及正确的响应。
One.它所具有的公有方法
名称 |
可见性 |
返回值类型 |
参数 |
功能描述 |
InitMineField |
Public |
Void |
int paneNumber, int mineNumber |
初始化雷区。布局方格并随机分布地理。 |
DisplayAll |
Public |
Void |
无 |
明示雷区的全部方块里的内容。当踩雷以后,给玩家显示所有地雷位置。 |
DisplayAround |
Pubic |
Void |
Pane pane |
明示与给定方格相关联的无地雷的方格。玩家点击一个无雷方格后使用。 |
Two.它有具有的私有方法
名称 |
可见性 |
返回值类型 |
参数 |
功能描述 |
GetPanesAround |
Private |
List<Pane> |
Pane pane |
获取与当前方格相邻的所有方格。 |
GetMineCountAround |
Private |
int |
Pane pane |
获取周围地雷的总数量 |
GetPaneSize |
Private |
Size |
无 |
获取每个小方格的大小 |
LayoutPanes |
Private |
Void |
无 |
排列有所方格,完成布雷 |
LayMines |
Private |
Void |
int mineNumber |
随机布雷 |
IsAllMineSweeped |
Private |
Bool |
无 |
判断是否扫雷成功 |
Three.事件处理
名称 |
可见性 |
返回值类型 |
参数 |
功能描述 |
MineField_SizeChanged |
Private |
Void |
object sender, EventArgs e |
如果雷区面板尺寸有变化,则重新进行布局。 |
OnPaneMouseDown |
Private |
Void |
object sender, EventArgs e |
仅处理鼠标左键和右键事件,忽略其他按键。 |
Four.源码
//事件的委托
public delegate void MineSweepingCompletedEventHandler(object sender, EventArgs e);
public delegate void MineSweepingFailedEventHandler(object sender, EventArgs e);
public partial class CrlMineField : UserControl
{
//成功和失败两个委托事件的申明
public event MineSweepingCompletedEventHandler MineSweepingCompleted;
public event MineSweepingFailedEventHandler MineSweepingFailed;
public CrlMineField()
{
InitializeComponent();
}
/// <summary>
/// 初始化雷区
/// </summary>
/// <param name="paneNumber">每排方块的数量</param>
/// <param name="mineNumber">地雷的数量</param>
public void InitMineField(int paneNumber, int mineNumber)
{
if (mineNumber >= paneNumber * paneNumber)
{
throw new ApplicationException("地雷太多了,不合法游戏规则。");
}
// 清空现有的所有方块
if (this.Controls.Count > 0)
{
this.Controls.Clear();
}
//添加雷区方格
for (int i = 0; i < paneNumber * paneNumber; i++)
{
Pane pane = new Pane();
pane.MouseDown += new MouseEventHandler(OnPaneMouseDown);
this.Controls.Add(pane);
}
// 布局方格
this.LayoutPanes();
// 随机部署地雷
this.LayMines(mineNumber);
// 设置每个方格周边的地雷数
foreach (Pane p in this.Controls)
{
p.AroundMineCount = this.GetMineCountAround(p);
}
}
/// <summary>
/// 明示雷区的全部方块里的内容
/// </summary>
public void DisplayAll()
{
foreach (Pane pane in this.Controls)
{
if (pane.State != PaneState.Opened)
{
pane.Open();
}
}
}
/// <summary>
/// 明示与给定方格相关联的无地雷的方格
/// </summary>
/// <param name="pane"></param>
public void DisplayAround(Pane pane)
{
if (pane.State == PaneState.Opened || pane.HasMine)
{
return;
}
// 明示当前方格本身
pane.Open();
// 通过递归明示当前方格四周的所有方格
List<Pane> panesAround = this.GetPanesAround(pane);
foreach (Pane p in panesAround)
{
// 如果该方格四周没有相邻的地雷,则递归
if (this.GetMineCountAround(p) == 0)
{
this.DisplayAround(p);
}
else
{
if (p.State != PaneState.Opened && !p.HasMine)
{
p.Open();
}
}
}
}
#region 私有方法
/// <summary>
/// 获取与当前方格相邻的所有方格。
/// </summary>
/// <param name="pane">当前方格</param>
/// <returns></returns>
private List<Pane> GetPanesAround(Pane pane)
{
List<Pane> result = new List<Pane>();
// 通过递归明示当前方格四周的所有方格
int paneHeight = this.GetPaneSize().Height;
int paneWidth = this.GetPaneSize().Width;
foreach (Pane p in this.Controls)
{
逐个扫描当前方格四周地雷数目
if (Math.Abs(p.Top - pane.Top) == 0 && Math.Abs(p.Left - pane.Left) == paneWidth
||
Math.Abs(p.Left - pane.Left) == 0 && Math.Abs(p.Top - pane.Top) == paneHeight
||
Math.Abs(p.Top - pane.Top) == paneHeight && Math.Abs(p.Left - pane.Left) == paneWidth
||
Math.Abs(p.Left - pane.Left) == paneWidth && Math.Abs(p.Top - pane.Top) == paneHeight)
{
result.Add(p);
}
}
return result;
}
/// <summary>
/// 获取当前方格四周地雷数量
/// </summary>
/// <param name="pane">当前方格</param>
/// <returns></returns>
private int GetMineCountAround(Pane pane)
{
int result = 0;
List<Pane> panes = this.GetPanesAround(pane);
foreach (Pane p in panes)
{
if (p.HasMine)
{
result++;
}
}
return result;
}
/// <summary>
/// 获取当前每个方格的尺寸
/// </summary>
/// <returns></returns>
private Size GetPaneSize()
{
if (this.Controls.Count == 0)
{
return new Size();
}
else
{
int paneNumber = (int)Math.Sqrt(this.Controls.Count);
int paneWidth = this.Width / paneNumber;
int paneHeight = this.Height / paneNumber;
return new Size(paneWidth, paneHeight);
}
}
/// <summary>
/// 排列所有雷区的方格,完成布局。
/// </summary>
private void LayoutPanes()
{
if (this.Controls.Count <= 0)
{
return;
}
int paneNumber = (int)Math.Sqrt(this.Controls.Count);
int paneHeight = this.GetPaneSize().Height;
int paneWidth = this.GetPaneSize().Width;
int paneIndex = 0;
// 绘制雷区方块
int paneLeft = 0;
int paneTop = 0;
for (int colNum = 0; colNum < paneNumber; colNum++)
{
paneTop = colNum * paneHeight;
for (int rowNum = 0; rowNum < paneNumber; rowNum++)
{
paneLeft = rowNum * paneWidth;
Pane pane = this.Controls[paneIndex] as Pane;
pane.Location = new Point(paneLeft, paneTop);//设置方块位置
pane.Size = new Size(paneWidth, paneHeight);//设置方块大小
paneIndex++;
}
}
}
/// <summary>
/// 随机部署地雷
/// </summary>
/// <param name="mineNumber"></param>
private void LayMines(int mineNumber)
{
Random rdm = new Random();
for (int i = 0; i < mineNumber; i++)
{
while (true)
{
int index = rdm.Next(0, this.Controls.Count);
Pane pane = this.Controls[index] as Pane;
if (!pane.HasMine)
{
pane.HasMine = true;
break;
}
}
}
}
/// <summary>
/// 是否扫雷成功。即:所有地雷都已经正确作出标记
/// </summary>
/// <returns></returns>
private bool IsAllMineSweeped()
{
int markedCount = 0;
int mineCount = 0;
foreach (Pane pane in this.Controls)
{
if (pane.HasMine)
{
mineCount++;
}
if (pane.State == PaneState.Marked)
{
markedCount++;
if (!pane.HasMine)
{
//存在做了标记但没有地雷的方格,扫雷没有正确完成。
return false;
}
}
}
return mineCount == markedCount;
}
#endregion
#region 事件处理
/// <summary>
/// 如果雷区面板尺寸有变化,则重新进行布局。
/// 使得通过改变方格大小来整体适应雷区面板的尺寸。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CrlMineField_SizeChanged(object sender, EventArgs e)
{
try
{
this.LayoutPanes();
}
catch (Exception ex)
{
ExceptionHandler.OnException(ex);
}
}
private void OnPaneMouseDown(object sender, MouseEventArgs e)
{
// 仅处理鼠标左键和右键事件,忽略其他按键。
if (e.Button != MouseButtons.Left && e.Button != MouseButtons.Right)
{
return;
}
try
{
Pane pane = sender as Pane; //获取当前被鼠标点中的方格
if (e.Button == MouseButtons.Left)//鼠标左键
{
if (pane.HasMine) //踩地雷了
{
pane.Open();
this.DisplayAll();
if (this.MineSweepingFailed != null)
{
this.MineSweepingFailed(this, EventArgs.Empty);
}
}
else
{
//明示当前方格相邻的所有无地雷的方格
this.DisplayAround(pane);
}
}
else if (e.Button == MouseButtons.Right)//鼠标右键
{
if (pane.State == PaneState.Marked)
{
pane.Reset();//取消小红旗标记
}
else
{
pane.Mark(); //插个小红旗做标记
}
}
// 所有地雷扫除成功
if (this.IsAllMineSweeped())
{
if (this.MineSweepingCompleted != null)
{
this.MineSweepingCompleted(this, EventArgs.Empty);
}
}
}
catch (Exception ex)
{
ExceptionHandler.OnException(ex);
}
}
#endregion
三、Main类
还未完善,负责各个模块的协调工作,如导航菜单里面设置游戏的开始,结束,时间计时,保存结果,查看排行榜,游戏的级别等,由于周末两天的时间还未能将其完善,只实现了一个大体的功能,当然还存在一些Bug,有待调试!
附属插图: