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

仿 Windows 經典掃雷遊戲開發實戰(MFC 版)

     編輯:關於VC++

專案背景與目標

作為 MFC 入門練手項目,本專案完整復刻了 Windows 經典掃雷遊戲的核心邏輯與界面交互。通過這個項目,重點掌握了 MFC 文檔/視圖架構、GDI 繪圖技術、遞歸算法在遊戲邏輯中的應用,以及資源管理與事件處理機制。

核心架構設計

數據模型:MINEWND 結構體

每個格子的狀態封裝在 MINEWND 結構中:

struct MINEWND {
    UINT row, col;        // 行列座標
    UINT uAttrib;         // 屬性:普通/雷/標記
    UINT uState;          // 當前狀態:未翻開/已翻開/標旗/問號
    UINT uOldState;       // 上一狀態(用於復原/繪圖優化)
};

主視圖類 CMineWnd 維護二維數組 m_pMines[24][30] 支持最大 24×30 難度。

界面渲染策略:貼圖技術

  • 預載入所有狀態位圖(數字 0-8、雷、旗、問號、未翻開、錯誤標記等)
  • 根據 uState 動態選擇位圖繪製
  • 雙緩衝繪圖消除閃爍:內存 DC 合成後一次性 BitBlt 到屏幕

關鍵算法實現

1. 佈雷算法 - 首點安全機制

為保證公平性,第一次點擊不觸雷,佈雷在首次點擊時動態生成:

void CMineWnd::LayMines(UINT row, UINT col) {
    srand((unsigned)time(NULL));  // 種子確保每局不同
    for (UINT index = 0; index < m_uMineNum; ) {
        UINT i = rand() % m_uYNum;
        UINT j = rand() % m_uXNum;
        if (i == row && j == col) continue;  // 首點絕不佈雷
        if (m_pMines[i][j].uAttrib != ATTRIB_MINE) {
            m_pMines[i][j].uAttrib = ATTRIB_MINE;
            index++;
        }
    }
}

2. 遞歸展開算法 - 洪水填充優化

點擊空白格(周圍雷數為 0)時自動展開鄰域:

void CMineWnd::ExpandMines(UINT row, UINT col) {
    UINT around = GetAroundNum(row, col);
    m_pMines[row][col].uState = 15 - around;  // 狀態映射到對應數字圖片
    m_pMines[row][col].uOldState = 15 - around;
    DrawSpecialMine(row, col);  // 局部重繪

    if (around == 0) {  // 只有空白格才遞歸展開
        UINT minRow = (row == 0) ? 0 : row - 1;
        UINT maxRow = min(row + 2, m_uYNum);
        UINT minCol = (col == 0) ? 0 : col - 1;
        UINT maxCol = min(col + 2, m_uXNum);

        for (UINT i = minRow; i < maxRow; i++) {
            for (UINT j = minCol; j < maxCol; j++) {
                if (!(i == row && j == col) 
                    && m_pMines[i][j].uState == STATE_NORMAL 
                    && m_pMines[i][j].uAttrib != ATTRIB_MINE) {
                    ExpandMines(i, j);  // 遞歸調用
                }
            }
        }
    }
}

關鍵優化:邊界檢查使用 min/max 避免越界,狀態判斷防止重複展開導致棧溢出。

3. 勝負判定邏輯

  • 失敗:點擊到雷(uAttrib == ATTRIB_MINE)或標記錯誤後揭示
  • 勝利:所有非雷格子被翻開(STATE_NORMAL 剩餘數等於雷數)

交互細節處理

操作左鍵右鍵雙鍵/中鍵
未翻開格展開標旗/問號循環周圍旗數匹配時自動展開
已翻開數字無效無效周圍旗數匹配時自動展開
已標旗無效改問號/取消無效

技術亮點與避坑指南

  1. 隨機種子管理:srand(time(NULL)) 僅在佈雷時調用一次,避免同秒多次調用導致序列相同
  2. 遞歸深度控制:最大 24×30=720 格,遞歸深度理論上可達 720,Windows 默認棧 1MB 足夠,但建議改用显式棧(BFS)更穩健
  3. 繪圖性能:僅重繪變化格子(DrawSpecialMine),配合 InvalidateRect 區域失效
  4. 資源清理:析構函數釋放所有 CBitmap、CDC,防止 GDI 句柄泄漏

擴展建議

  • 增加計時器精度到毫秒級(參考多媒體定時器)
  • 實現「最佳成績」本地存儲(註冊表/INI/JSON)
  • 支持自定義難度(行、列、雷數可配置)
  • 加入鍵盤操作支持(方向鍵移動、空格/Enter 翻開、F 標旗)
  • 移植到現代框架(Qt/WxWidgets/WinUI 3)或 WebAssembly

致謝

參考了 lpq9907 的掃雷源碼,感謝開源社區的知識分享。代碼註釋採用英文練習,歡迎指正。

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