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)还有几个全局变量负责多线程,给游戏计时。