C#版扫雷游戏
知识点:
- 枚举类型的使用
- 当某个方法暂时不想去实现的时候,最好在方法体中写上:throw new NotImplementedException()
- 如何将图片资源引入到项目的属性中去
- 设置控件的大小:pane.Size = new Size(paneWidth,paneHeight);
- 设置控件的位置:pane.Location = new Point(paneLeft,paneTop);
- 事件的动态绑定:pane.MouseDown += pane_MouseDown;
- 如何定义事件并引发事
public delegate void MineSweepedSuccessEventHandler(object sender,EventArgs e);
public event MineSweepedSuccessEventHandler MineSweepedSucces;
Form1(主窗体)代码:
View Code
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace 扫雷游戏 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //注意:只有将自定义控件MineField拖到Form1中,才可以使用 this.mineField1.Init(10,8); } private void button1_Click(object sender, EventArgs e) { this.mineField1.DisplayAll(); } } }
Pane.cs(继承自Button控件):
View Code
/*************************************************************** * 当前类用于封装方格,该方格继承自button控件(考虑到雷区中方格类似按钮) * 共有三个属性,三个方法 * 注意事项: * 1.当某个方法暂时没去实现的时候,可以在方法体中写上:throw new NotImplementedException(); * 2.表示方格状态的时候,使用到了枚举类型 * *************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace 扫雷游戏 { /// <summary> /// 封装方格 /// </summary> public class Pane:Button { /// <summary> /// 设置或获取当前方格是否埋有地雷 /// </summary> public bool hasMine{get;set;} /// <summary> /// 设置或获取当前方格周围的地雷数 /// </summary> public int aroundMineNums { get; set; } /// <summary> /// 获取或设置当前方格的状态 /// </summary> public PaneState State{get;set;} public Pane() { this.BackgroundImageLayout = ImageLayout.Stretch;//设置方格的背景图以平铺的方式展现 } /// <summary> /// 打开方格 /// </summary> public void Open() { if (this.hasMine)//踩雷 { this.BackgroundImage = Properties.Resources.MineBomp; this.Enabled=false; } else { switch (this.aroundMineNums) { case 0: this.BackgroundImage=null; break; case 1: this.BackgroundImage=Properties.Resources.Num1; break; case 2: this.BackgroundImage=Properties.Resources.Num2; break; case 3: this.BackgroundImage=Properties.Resources.Num3; break; case 4: this.BackgroundImage=Properties.Resources.Num4; break; case 5: this.BackgroundImage=Properties.Resources.Num5; break; case 6: this.BackgroundImage=Properties.Resources.Num6; break; case 7: this.BackgroundImage=Properties.Resources.Num7; break; case 8: this.BackgroundImage=Properties.Resources.Num8; break; } this.Enabled=false; } } /// <summary> /// 标记方格 /// </summary> public void Mark() { this.BackgroundImage = Properties.Resources.Marked; this.State = PaneState.Marked; } /// <summary> /// 重置标记的方格 /// </summary> public void Reset() { this.BackgroundImage = null; this.State = PaneState.Closed; } } /// <summary> /// 枚举类型 /// 方格状态 /// </summary> public enum PaneState { /// <summary> /// 关闭状态 /// </summary> Closed, /// <summary> /// 打开状态 /// </summary> Opened, /// <summary> /// 标记状态 /// </summary> Marked, } }
用户自定义控件:MineField类(雷区)
View Code
/*************************************************************** * 自定义用户控件 * 设计的时候,并没有往“雷区”中添加控件(即:小方格),控件主要是在运行的时候动态加载进来的 * 要点: * 1.如何定义事件并引发事件 * 2.综合考虑代码的可读性、可重用性,可将LayoutPane()方法独立出来 * 3.设置方格的大小:pane.Size = new Size(paneWidth,paneHeight); * 4.设置方格的位置:pane.Location = new Point(paneLeft,paneTop); * 5.考虑到之后可能会对随机算法进行改变,故将随机布雷的方法抽象出来 * 6.动态的事件绑定 * *************************************************************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace 扫雷游戏 { public partial class MineField : UserControl { /// <summary> /// 定义一个扫雷成功引发的事件 /// </summary> /// <param name="sender">事件的发起者</param> /// <param name="e">事件向外部传递的参数</param> public delegate void MineSweepedSuccessEventHandler(object sender,EventArgs e); public event MineSweepedSuccessEventHandler MineSweepedSucces; /// <summary> /// 定义一个扫雷失败引发的事件 /// </summary> /// <param name="sender">事件的发起者</param> /// <param name="e">事件向外部传递的参数</param> public delegate void MineSweepedFailEventHandler(object sender,EventArgs e); public event MineSweepedFailEventHandler MineSweepedFail; public MineField() { InitializeComponent(); } /// <summary> /// 初始化雷区(即:将小方格动态加载到雷区中) /// </summary> /// <param name="paneLineCount">一个方向过去方格的数量</param> /// <param name="mineCount">整个雷区中地雷的数量</param> public void Init(int paneLineCount,int mineCount) { //根据指定的数量构建雷区中的方格 for (int i = 0; i < paneLineCount * paneLineCount;i++ ) { Pane pane = new Pane(); this.Controls.Add(pane);//将自定义用户控件(方格)添加到"雷区"中 } //布局雷区中方格的位置 this.LayoutPane(); //随机布雷 this.LayoutMines(mineCount); //设置每个方格四周的地雷数 foreach(Pane pane in this.Controls) { pane.aroundMineNums = this.GetAroundMineCount(pane); } } /// <summary> /// 布局雷区中的所有方格的位置 /// </summary> public void LayoutPane() { //初始化时,也会触发MineField_SizeChanged事件,而此时,雷区中的方格数为0 if(this.Controls.Count==0) { return; } int paneLineCount=(int)Math.Sqrt(this.Controls.Count);//获取一行或一列下来方格的数量 int paneWidth=this.Width/paneLineCount;//方格的宽度 int paneHeight=this.Height/paneLineCount;//方格的高度 int paneIndex = 0;//方格的索引 int paneTop = 0; int paneLeft = 0; for(int row=0;row<paneLineCount;row++) { paneTop = row * paneHeight;//方格的TOP for (int col = 0; col < paneLineCount; col++) { paneLeft = col * paneWidth; Pane pane = this.Controls[paneIndex] as Pane; //为每个方格动态的绑定MouseDown事件,当触发该事件时,会调用pane_MouseDown方法 //这其实是一个委托,将MouseDonw事件委托给pane_MouseDown方法 pane.MouseDown += pane_MouseDown; //设置方格的大小 pane.Size = new Size(paneWidth,paneHeight); //设置方格的位置 pane.Location = new Point(paneLeft,paneTop); //索引递增 paneIndex++; } } } /// <summary> /// 响应pane被鼠标点击时的操作 /// </summary> /// <param name="sender">事件的发起对象</param> /// <param name="e"></param> private void pane_MouseDown(object sender, MouseEventArgs e) { Pane pane = sender as Pane; this.DisplayAround(pane); } private void DisplayAround(Pane currentPane) { if(currentPane.State==PaneState.Opened||currentPane.hasMine) { return; } currentPane.Open(); List<Pane> panes = this.GetAroundPanes(currentPane);//得到当前pane四周的所有pane foreach(Pane p in panes) { if (this.GetAroundMineCount(p) == 0) { DisplayAround(p); } else { if(p.State!=PaneState.Opened && !currentPane.hasMine) { p.Open(); } } } } public void DisplayAll() { foreach(Pane pane in this.Controls) { if(pane.State!=PaneState.Opened) { pane.Open(); } } } /// <summary> /// 获取指定的方格四周的地雷数 /// </summary> private int GetAroundMineCount(Pane pane) { List<Pane> panes = this.GetAroundPanes(pane); int mineCount = 0; foreach(Pane p in panes) { if(p.hasMine) { mineCount++; } } return mineCount; } /// <summary> /// 获取指定的方格四周的所有方格 /// </summary> /// <returns></returns> private List<Pane> GetAroundPanes(Pane pane) { List<Pane> result=new List<Pane>(); foreach(Pane p in this.Controls) { //左右方向 if(p.Top==pane.Top&&Math.Abs(p.Left-pane.Left)==p.Width) { result.Add(p); } //上下方向 if(p.Left==pane.Left&&Math.Abs(p.Top-pane.Top)==p.Height) { result.Add(p); } //45度斜角方向 if(Math.Abs(p.Top-pane.Top)==p.Height&&Math.Abs(p.Left-pane.Left)==p.Width) { result.Add(p); } } return result; } /// <summary> /// 随机布雷:将HasMine的属性改为true即可 /// </summary> /// <param name="mineCOunt">地雷的数量</param> private void LayoutMines(int mineCount) { Random rand = new Random(); for (int i = 0; i < mineCount;i++ ) { int mineIndex=rand.Next(0,this.Controls.Count); Pane pane=this.Controls[mineIndex] as Pane; pane.hasMine = true; } } /// <summary> /// 当雷区的区域大小改变时,重新布置雷区中方格的大小和位置 /// </summary> /// <param name="sender">事件的发起者</param> /// <param name="e"></param> private void MineField_SizeChanged(object sender, EventArgs e) { this.LayoutPane(); } } }