掃雷是經典的邏輯遊戲,用 C# WinForms 實作不僅能練習事件驅動程式設計、陣列操作、遞迴演算法,還能體驗完整遊戲循環的建構。本文提供完整可執行的原始碼並解析核心邏輯。
標準掃雷介面:左上角顯示剩餘旗幟數,右上角計時器,主區域為可點擊格子網格。
public partial class Frm1 : Form
{
// 遊戲參數
public const int Cols = 20, Rows = 11;
public const int MineCount = 30;
// 狀態陣列
public int[,] mineMap = new int[Cols, Rows]; // 0=無雷, 1-8=周圍雷數, 9=雷
public int[,] clickState = new int[Cols, Rows]; // 0=未點, 10=已清除, 11=旗幟, 12=問號, 16=已翻開
// 遊戲狀態
public int flagCount = 0; // 已標記旗幟數
public int elapsedTime = 0; // 經過秒數
public int gameState = 0; // 0=進行中, 1=結束
public int remainingCells = 0; // 未翻開格子數
}
public void InitializeMinefield()
{
// 重置陣列
for (int x = 0; x < Cols; x++)
for (int y = 0; y < Rows; y++)
{
mineMap[x, y] = 0;
clickState[x, y] = 0;
}
// 隨機放置雷
Random rnd = new Random((int)DateTime.Now.Ticks);
int placed = 0;
while (placed < MineCount)
{
int x = rnd.Next(Cols);
int y = rnd.Next(Rows);
if (mineMap[x, y] != 9)
{
mineMap[x, y] = 9;
placed++;
}
}
// 計算各格周圍雷數
for (int x = 0; x < Cols; x++)
for (int y = 0; y < Rows; y++)
if (mineMap[x, y] != 9)
CountAdjacentMines(x, y);
flagCount = 0;
remainingCells = Cols * Rows;
}
public void RevealCell(int x, int y)
{
if (x < 0 || x >= Cols || y < 0 || y >= Rows) return;
if (clickState[x, y] == 10 || clickState[x, y] == 11) return; // 已翻開或旗幟
if (mineMap[x, y] == 9) // 踩到雷
{
GameOver(x, y);
return;
}
clickState[x, y] = 10; // 標記已翻開
remainingCells--;
if (mineMap[x, y] == 0) // 空白格,遞迴展開周圍 8 格
{
for (int dx = -1; dx <= 1; dx++)
for (int dy = -1; dy <= 1; dy++)
if (dx != 0 || dy != 0)
RevealCell(x + dx, y + dy);
}
// 數字格只翻開自己,不遞迴
}
private void labdraw_MouseClick(object sender, MouseEventArgs e)
{
if (gameState != 0) return; // 遊戲結束不處理
// 計算點擊格子座標
int cellX = (e.X - 4) / 22;
int cellY = (e.Y - 4) / 22;
if (cellX < 0 || cellX >= Cols || cellY < 0 || cellY >= Rows) return;
if (e.Button == MouseButtons.Right) // 右鍵:切換旗幟/問號
{
HandleRightClick(cellX, cellY);
}
else // 左鍵:翻開格子
{
if (clickState[cellX, cellY] != 11) // 非旗幟狀態可翻開
{
timer1.Enabled = true; // 首次點擊啟動計時
RevealCell(cellX, cellY);
RedrawBoard();
CheckWinCondition();
}
}
}
void HandleRightClick(int x, int y)
{
switch (clickState[x, y])
{
case 0: // 未點擊 -> 旗幟
if (flagCount < MineCount)
{
clickState[x, y] = 11;
flagCount++;
}
break;
case 11: // 旗幟 -> 問號
clickState[x, y] = 12;
flagCount--;
break;
case 12: // 問號 -> 恢復未點擊
clickState[x, y] = 0;
break;
}
UpdateFlagDisplay();
RedrawBoard();
}
void RedrawBoard()
{
Graphics g = labdraw.CreateGraphics();
g.Clear(Color.FromArgb(224, 224, 224));
for (int x = 0; x < Cols; x++)
for (int y = 0; y < Rows; y++)
{
int px = x * 22 + 4;
int py = y * 22 + 4;
if (clickState[x, y] != 10 && clickState[x, y] != 16) // 未翻開:畫立體按鈕
{
Draw3DButton(g, px, py);
}
else // 已翻開
{
if (mineMap[x, y] == 9 || mineMap[x, y] == 100) // 雷
{
DrawMine(g, px, py, mineMap[x, y] == 100); // 100=被點中的雷畫叉叉
}
else if (mineMap[x, y] > 0) // 數字
{
g.DrawString(mineMap[x, y].ToString(), new Font("微軟正黑體", 12), Brushes.Black, px + 2, py + 2);
}
}
// 畫旗幟/問號
if (clickState[x, y] == 11) DrawFlag(g, px, py);
else if (clickState[x, y] == 12) DrawQuestionMark(g, px, py);
}
}
mineMap[x,y] == 9),顯示所有雷,停止計時remainingCells == MineCount(僅剩雷未翻開),停止計時完整程式碼包含約 300 行,涵蓋初始化、佈雷、遞迴展開、事件處理、繪圖、計時、重新開始等完整遊戲循環,是學習 C# WinForms 遊戲開發的優秀教材。