C#进阶:扫雷 附完整源码与解读

WinXP自带的扫雷游戏是一代人的童年。突然想自己实现一个,就做了C#版本的。

看看效果

特性:难度自定义;右键标黄;窗体大小随格子数量变化,初始状态无格子

先上源码

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

namespace Mines
{
	public partial class Form1 : Form
	{
		private RadioButton[] levels;
		private List<Button> Field = new List<Button>();
		private enum BlockState
		{
			Init,
			Flag,
			Mined
		};
		private List<BlockState> FieldState = new List<BlockState>();
		private List<int> BombMap = new List<int>();
		private Dictionary<RadioButton, int> gridSize;
		private DataGridViewTextBoxColumn[] cols;
		private bool GameOn = false;
		private bool FirstClick = true;
		private int countMine = 0;
		private Stopwatch stopwatch = new Stopwatch();
		Task taskTimer;
		CancellationToken tokenTimer;
		CancellationTokenSource tsTimer;
		int size = 10;
		public Form1()
		{
			System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;

			InitializeComponent();
			InitializeSizeDic();
			InitializeLevels();
			this.Board.Size = new Size(0, 0);
			this.Size = new Size(this.panel1.Width + 30, this.panel1.Height);
		}
		private void InitializeSizeDic()
		{
			gridSize = new Dictionary<RadioButton, int>() { [radio_level1] = 10, [radio_level2] = 15, [radio_level3] = 20 };
		}
		private void InitializeLevels()
		{
			levels = new RadioButton[] { radio_level1, radio_level2, radio_level3 };
		}
		private void PaintBlankCanvas()
		{
			int a = 50;
			//获取扫雷棋盘的尺寸,也是埋雷个数
			foreach (RadioButton rb in levels)
			{
				if (rb.Checked)
				{
					size = gridSize[rb];
				}
			}
			this.panel1.Dock = DockStyle.Right;
			Board.Size = new Size(a * (size + 1) + 50, a * (size + 1) + 10);
			Board.Controls.Clear();
			Field.Clear();
			FieldState.Clear();

			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					Button button = new Button()
					{
						Location = new Point(i * a, j * a),
						Size = new Size(a, a),
						BackColor = Color.White,

					};
					button.MouseDown += BlockClick;

					Board.Controls.Add(button);
					Field.Add(button);
					FieldState.Add(BlockState.Init);
				}
			}

			this.Size = new Size(a * (size + 1) + 300, a * (size + 1) + 100);


		}
		//po表示第一次单机画布时候的位置
		private void InitializeCanvas(Point p0)
		{
			int size = 0;
			//获取扫雷棋盘的尺寸,也是埋雷个数
			foreach (RadioButton rb in levels)
			{
				if (rb.Checked)
				{
					size = gridSize[rb];
				}
			}
			//Canvas保存棋盘布局
			//bombs保存炸弹位置
			List<Point> bombs = new List<Point>(size);
			for (int i = 0; i < size * size; i++)
			{
				BombMap.Add(0);
			}
			int x;
			int y;
			//随机选择size个不同的点放置炸弹
			for (int i = 0; i < size; i++)
			{
				Random random = new Random();
				x = random.Next(size);
				y = random.Next(size);
				//注意炸弹不能放在初始点下的位置
				while (bombs.Contains(p0))
				{
					x = random.Next(size);
					y = random.Next(size);
				}
				while (bombs.Contains(new Point(x, y)))
				{
					x = random.Next(size);
					y = random.Next(size);
				}
				bombs.Add(new Point(x, y));
			}

			//数一数空格旁边有几个炸弹
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					if (bombs.Contains(new Point(i, j)))
					{
						BombMap[i * size + j] = int.MaxValue;
						continue;
					}

					//初始化为0
					int count = 0;
					for (int dx = -1; dx <= 1; dx++)
					{
						//count存放炸弹数量

						for (int dy = -1; dy <= 1; dy++)
						{
							if (dx == 0 && dy == 0) continue;
							int nx = i + dx;
							int ny = j + dy;
							if (nx < 0 || nx >= size || ny < 0 || ny >= size) continue;
							if (bombs.Contains(new Point(nx, ny)))
							{
								count++;
							}
						}

					}
					BombMap[i * size + j] = count;
				}
			}
		}
		#region radioButton互斥属性实现
		private void radio_level1_CheckedChanged(object sender, EventArgs e)
		{
			radio_level1.Checked = true;
			radio_level2.Checked = false;
			radio_level3.Checked = false;
		}

		private void radio_level2_CheckedChanged(object sender, EventArgs e)
		{
			radio_level2.Checked = true;
			radio_level1.Checked = false;
			radio_level3.Checked = false;
		}

		private void radio_level3_CheckedChanged(object sender, EventArgs e)
		{
			radio_level3.Checked = true;
			radio_level2.Checked = false;
			radio_level1.Checked = false;
		}
		#endregion
		private string TimerLabel(int second)
		{
			//若时长超过1小时
			if (second >= 60 * 60)
			{
				return "超过1小时";
			}
			string min = Convert.ToString(second / 60).PadLeft(2, '0');
			string sec = Convert.ToString(second % 60).PadLeft(2, '0');
			return min + ":" + sec;
		}
		public async Task RunAsyncTimer(CancellationToken ct)
		{
			if (ct.IsCancellationRequested) return;
			await Task.Run(() => UpdateTImer(ct), ct);
		}
		private void UpdateTImer(CancellationToken token)
		{
			while (!token.IsCancellationRequested)
			{
				label_timer.Invoke((EventHandler)delegate
				{
					label_timer.Text = "计时  " + TimerLabel((int)(stopwatch.ElapsedMilliseconds / 1000));
				});
				Thread.Sleep(200);
			}



		}
		private void button_restart_Click(object sender, EventArgs e)
		{
			Thread.Sleep(500);
			if (!GameOn)
			{
				GameOn = true;
				FirstClick = true;
				button_result.ImageIndex = 0;
				stopwatch.Reset();
				stopwatch.Start();
				tsTimer = new CancellationTokenSource();
				tokenTimer = tsTimer.Token;
				taskTimer = RunAsyncTimer(tokenTimer);
				PaintBlankCanvas();
				button_restart.Text = "放弃";

			}
			else
			{
				GameOn = false;
				stopwatch.Stop();
				tsTimer.Cancel();
				button_restart.Text = "重新开始";
			}
		}

		private void BlockClick(object sender, MouseEventArgs e)
		{
			//若未开始游戏,点击无效
			if (!GameOn) return;
			Button b = sender as Button;
			//若点击的不是button控件,无效
			if (b == null) return;
			int id = Field.IndexOf(b);
			//若点击的button不属于Field列表,无效
			if (id == -1) return;
			//若当前块被置标志,不动作
			if (e.Button == MouseButtons.Right)
			{
				//若当前块已经点开,返回
				if (FieldState[id] == BlockState.Mined) return;
				if (FieldState[id] != BlockState.Flag)
				{
					FieldState[id] = BlockState.Flag;
					b.Text = "F";
					b.BackColor = Color.Yellow;
				}
				else
				{
					FieldState[id] = BlockState.Init;
					b.Text = "";
					b.BackColor = Color.White;
				}
			}
			else
			{
				if (FieldState[id] == BlockState.Flag) return;
				FieldState[id] = BlockState.Mined;
				int row = id / size;
				int col = id % size;
				if (FirstClick)
				{
					InitializeCanvas(new Point(row, col));
					FirstClick = false;
					countMine = 1;
				}

				int curr = BombMap[row * size + col];
				if (curr == int.MaxValue)
				{
					b.Text = "*";
					b.BackColor = Color.Red;
					GameOn = false;
					button_restart.Text = "重新开始";
					tsTimer?.Cancel();
					MessageBox.Show("You died", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);

					return;

				}
				else
				{
					if (curr == 0)
					{
						b.Text = "";
					}
					else
					{
						b.Text = curr.ToString();
					}
					b.BackColor = Color.Green;
					countMine++;
				}

				if (countMine == size * size - size)
				{
					button_result.ImageIndex = 1;
					MessageBox.Show("You WON");
					GameOn = false;
					return;
				}
			}

		}

	}
}

这是界面

namespace Mines
{
	partial class Form1
	{
		/// <summary>
		///  Required designer variable.
		/// </summary>
		private System.ComponentModel.IContainer components = null;

		/// <summary>
		///  Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing && (components != null))
			{
				components.Dispose();
			}
			base.Dispose(disposing);
		}

		#region Windows Form Designer generated code

		/// <summary>
		///  Required method for Designer support - do not modify
		///  the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
			button_result = new Button();
			imageList1 = new ImageList(components);
			button_restart = new Button();
			label_timer = new Label();
			radio_level1 = new RadioButton();
			radio_level2 = new RadioButton();
			radio_level3 = new RadioButton();
			Board = new Panel();
			panel1 = new Panel();
			panel1.SuspendLayout();
			SuspendLayout();
			// 
			// button_result
			// 
			button_result.BackgroundImageLayout = ImageLayout.Stretch;
			button_result.Dock = DockStyle.Top;
			button_result.Enabled = false;
			button_result.FlatStyle = FlatStyle.Flat;
			button_result.ImageIndex = 0;
			button_result.ImageList = imageList1;
			button_result.Location = new Point(0, 0);
			button_result.Margin = new Padding(2, 3, 2, 3);
			button_result.Name = "button_result";
			button_result.Size = new Size(142, 107);
			button_result.TabIndex = 2;
			button_result.UseVisualStyleBackColor = true;
			// 
			// imageList1
			// 
			imageList1.ColorDepth = ColorDepth.Depth8Bit;
			imageList1.ImageStream = (ImageListStreamer)resources.GetObject("imageList1.ImageStream");
			imageList1.TransparentColor = Color.Transparent;
			imageList1.Images.SetKeyName(0, "sad.png");
			imageList1.Images.SetKeyName(1, "smile.png");
			// 
			// button_restart
			// 
			button_restart.Dock = DockStyle.Top;
			button_restart.Location = new Point(0, 107);
			button_restart.Margin = new Padding(2, 3, 2, 3);
			button_restart.Name = "button_restart";
			button_restart.Size = new Size(142, 48);
			button_restart.TabIndex = 3;
			button_restart.Text = "重新开始";
			button_restart.UseVisualStyleBackColor = true;
			button_restart.Click += button_restart_Click;
			// 
			// label_timer
			// 
			label_timer.AutoSize = true;
			label_timer.Dock = DockStyle.Top;
			label_timer.Font = new Font("微软雅黑", 13.8F, FontStyle.Bold, GraphicsUnit.Point);
			label_timer.Location = new Point(0, 155);
			label_timer.Margin = new Padding(2, 0, 2, 0);
			label_timer.Name = "label_timer";
			label_timer.Size = new Size(123, 26);
			label_timer.TabIndex = 4;
			label_timer.Text = "计时 00:00";
			// 
			// radio_level1
			// 
			radio_level1.AutoSize = true;
			radio_level1.Checked = true;
			radio_level1.Dock = DockStyle.Top;
			radio_level1.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
			radio_level1.Location = new Point(0, 231);
			radio_level1.Margin = new Padding(2, 3, 2, 3);
			radio_level1.Name = "radio_level1";
			radio_level1.Size = new Size(142, 25);
			radio_level1.TabIndex = 5;
			radio_level1.TabStop = true;
			radio_level1.Text = "初级(10阶)";
			radio_level1.UseVisualStyleBackColor = true;
			radio_level1.Click += radio_level1_CheckedChanged;
			// 
			// radio_level2
			// 
			radio_level2.AutoSize = true;
			radio_level2.Dock = DockStyle.Top;
			radio_level2.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
			radio_level2.Location = new Point(0, 206);
			radio_level2.Margin = new Padding(2, 3, 2, 3);
			radio_level2.Name = "radio_level2";
			radio_level2.Size = new Size(142, 25);
			radio_level2.TabIndex = 6;
			radio_level2.Text = "中级(15阶)";
			radio_level2.UseVisualStyleBackColor = true;
			radio_level2.Click += radio_level2_CheckedChanged;
			// 
			// radio_level3
			// 
			radio_level3.AutoSize = true;
			radio_level3.Dock = DockStyle.Top;
			radio_level3.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
			radio_level3.Location = new Point(0, 181);
			radio_level3.Margin = new Padding(2, 3, 2, 3);
			radio_level3.Name = "radio_level3";
			radio_level3.Size = new Size(142, 25);
			radio_level3.TabIndex = 7;
			radio_level3.Text = "高级(20阶)";
			radio_level3.UseVisualStyleBackColor = true;
			radio_level3.Click += radio_level3_CheckedChanged;
			// 
			// Board
			// 
			Board.AutoSize = true;
			Board.Dock = DockStyle.Fill;
			Board.Location = new Point(0, 0);
			Board.Margin = new Padding(2);
			Board.Name = "Board";
			Board.Size = new Size(808, 448);
			Board.TabIndex = 8;
			// 
			// panel1
			// 
			panel1.Controls.Add(radio_level1);
			panel1.Controls.Add(radio_level2);
			panel1.Controls.Add(radio_level3);
			panel1.Controls.Add(label_timer);
			panel1.Controls.Add(button_restart);
			panel1.Controls.Add(button_result);
			panel1.Dock = DockStyle.Right;
			panel1.Location = new Point(808, 0);
			panel1.Name = "panel1";
			panel1.Size = new Size(142, 448);
			panel1.TabIndex = 9;
			// 
			// Form1
			// 
			AutoScaleDimensions = new SizeF(7F, 17F);
			AutoScaleMode = AutoScaleMode.Font;
			ClientSize = new Size(950, 448);
			Controls.Add(Board);
			Controls.Add(panel1);
			Margin = new Padding(2, 3, 2, 3);
			Name = "Form1";
			Text = "扫雷";
			panel1.ResumeLayout(false);
			panel1.PerformLayout();
			ResumeLayout(false);
			PerformLayout();
		}

		#endregion
		private Button button_result;
		private Button button_restart;
		private Label label_timer;
		private RadioButton radio_level1;
		private RadioButton radio_level2;
		private RadioButton radio_level3;
		private ImageList imageList1;
		private Panel Canvas;
		private Panel Board;
		private Panel panel1;
	}
}

界面效果如下

代码思路

1. 界面安排

(1)3个radiobutton,选中后点击“重新开始”,就在左边的panel中填充对应阶数的按钮矩阵。

(2)每个按钮可以左键与右键。左键代表翻开,右键代表Flag/取消Flag。翻开后若不是雷,显示绿色和四周雷个数,若是雷显示红色,若Flag显示黄色。在取消Flag之前,不能翻开。

2. 变量分析

(1)List<Button> Field存放主界面上按钮矩阵的引用。List<BlockState> FieldState存放每个按钮的状态,Init代表未Flag也未翻开,Mined表示翻开。

(2)private List<int> BombMap存放按钮对应的状态,如果BombMap[i]等于int.MaxValue, 翻开按钮Field[i]就是翻开雷,如果等于0,代表该按钮四周没有雷,等于1,说明按钮四周有1颗雷,翻开按钮显示绿色与数字1,依次类推。

(3)Dictionary<RadioButton, int> gridSize存放主界面的三个RadioButton与阶数的对应关系。每次点击其中一个RadioButton,就把阶数更新到全局的int size变量。每次开始新一局游戏时根据size值配置按钮矩阵的尺寸以及对应的界面尺寸。

(4)还有几个全局变量负责多线程,给游戏计时。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值