首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

趣味程式設計:C# 掃雷遊戲完整原始碼實作與邏輯解析

     編輯:關於C語言

掃雷是經典的邏輯遊戲,用 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; // 未翻開格子數
}

核心演算法解析

1. 佈雷與數字計算

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;
}

2. 遞迴展開(洪水填充演算法)

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);
    }
    // 數字格只翻開自己,不遞迴
}

3. 滑鼠事件處理

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();
        }
    }
}

4. 右鍵狀態機

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();
}

5. 繪圖邏輯

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(僅剩雷未翻開),停止計時

程式碼結構亮點

  • 關注點分離:資料模型(mineMap/clickState)與視圖(RedrawBoard)分離
  • 狀態機設計:右鍵三態循環、遊戲狀態流轉清晰
  • 遞迴邊界檢查:防止堆疊溢位與越界
  • 首次點擊安全:可延伸實作「首次點擊永不踩雷」

完整程式碼包含約 300 行,涵蓋初始化、佈雷、遞迴展開、事件處理、繪圖、計時、重新開始等完整遊戲循環,是學習 C# WinForms 遊戲開發的優秀教材。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved