踩地雷是經典遊戲,若從未玩過,建議先體驗再動手實作演算法。透過思考推敲才能真正掌握核心邏輯,直接下載原始碼追蹤只會讓你停留在「知道怎麼寫」而非「理解為什麼這樣寫」。
遊戲畫面採用兩層重疊:
Label 陣列,負責顯示地雷圖片或周圍地雷數字。Button 陣列,蓋在 Label 上方,點擊時移除按鈕露出底層內容。雷區大小固定為 20×20:
private const int Xcount = 20;
private const int Ycount = 20;
private Label[] labels2 = new Label[Xcount * Ycount];
private Label[,] labels = new Label[Xcount, Ycount];
int indexOfLable = 0;
private void CreateLable()
{
int[] ints = new int[400];
Random rd = new Random();
for (int i = 0; i < 400; i++)
{
ints[i] = rd.Next(1, 400);
}
for (int i = 0; i < Xcount; i++)
{
for (int j = 0; j < Ycount; j++)
{
Label lb = new Label();
lb.Location = new Point(i * 30, 30 * j);
lb.Size = new Size(30, 30);
lb.BorderStyle = BorderStyle.Fixed3D;
this.Controls.Add(lb);
labels[i, j] = lb;
labels2[indexOfLable] = lb;
indexOfLable++;
}
}
// 隨機決定是否為地雷(約 10% 機率)
for (int i = 0; i < ints.Length; i++)
{
if (ints[i] < 40)
{
labels2[i].Image = Properties.Resources.標準地雷;
}
}
}
利用 Tag 屬性將按鈕與其邏輯座標 (x, y) 關聯:
private Button[,] buttons = new Button[Xcount, Ycount];
private void AddButton()
{
for (int i = 0; i < Xcount; i++)
{
for (int j = 0; j < Ycount; j++)
{
Button btn = new Button();
btn.Click += new EventHandler(btn_Click);
btn.MouseUp += new MouseEventHandler(btn_MouseUp);
btn.Location = new Point(i * 30, 30 * j);
btn.Size = new Size(30, 30);
this.Controls.Add(btn);
Point pt = new Point(i, j);
btn.Tag = pt; // 關鍵:儲存座標
buttons[i, j] = btn;
}
}
}
void btn_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
Button btn = (Button)sender;
btn.BackgroundImage = (btn.BackgroundImage == null)
? Properties.Resources.挖雷工兵
: null;
}
}
點擊按鈕後:
CheckLable(x, y) 遞迴展開void btn_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
this.Controls.Remove(btn);
Point pt = (Point)btn.Tag;
x = pt.X;
y = pt.Y;
if (labels[x, y].Image != null)
{
MessageBox.Show("遊戲結束,果然是踩雷高手");
ReStart();
return;
}
CheckLable(x, y);
}
CheckLable 會計算周圍地雷數:
private void CheckLable(int x, int y)
{
if (x < Xcount && x >= 0 && y >= 0 && y < Ycount)
{
num = 0;
num = GetCount(x, y);
if (num == 8) // 周圍無地雷
{
num = 0;
string tag = buttons[x, y].Tag.ToString();
buttons[x, y].Tag = 0; // 標記已展開
if (labels[x, y].Image == null && tag != "0")
{
// 依序移除周圍 8 個按鈕
x++; RemoveButton(x, y);
y++; RemoveButton(x, y);
x--; RemoveButton(x, y);
x--; RemoveButton(x, y);
y--; RemoveButton(x, y);
y--; RemoveButton(x, y);
x++; RemoveButton(x, y);
x++; RemoveButton(x, y);
if (tag != "0")
{
num = 0;
Recursion(x, y); // 遞迴展開
}
}
}
else // 周圍有地雷
{
if (x < Xcount && x >= 0 && y >= 0 && y < Ycount)
{
labels[x, y].Text = (8 - num).ToString(); // 顯示地雷數
num = 0;
return;
}
}
}
}
private int GetCount(int x, int y)
{
temp2 = 0;
// 依序檢查 8 個方向,超出邊界視為有地雷(temp2++)
x++; CheckBounds(x, y);
y++; CheckBounds(x, y);
x--; CheckBounds(x, y);
x--; CheckBounds(x, y);
y--; CheckBounds(x, y);
y--; CheckBounds(x, y);
x++; CheckBounds(x, y);
x++; CheckBounds(x, y);
return temp2;
}
// 輔助方法簡化邊界檢查
private void CheckBounds(int x, int y)
{
if (x < Xcount && x >= 0 && y >= 0 && y < Ycount)
{
if (labels[x, y].Image == null) temp2++;
}
else { temp2++; }
}
private void Recursion(int x, int y)
{
CheckLable(x, y);
y++; CheckLable(x, y);
y++; CheckLable(x, y);
x--; CheckLable(x, y);
x--; CheckLable(x, y);
y--; CheckLable(x, y);
y--; CheckLable(x, y);
x++; CheckLable(x, y);
}
public void ReStart()
{
this.Controls.Clear();
InitializeComponent();
temp2 = 0; x = 0; y = 0; indexOfLable = 0; num = 0;
AddButton();
CreateLable();
}