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

Qt 開發掃雷遊戲:MVC 架構完整實現教學

     編輯:關於C++

專案概覽

使用 Qt 4/5 實作經典掃雷遊戲,採用 MVC 架構 分離遊戲邏輯與界面渲染,支援三種難度、計時、標記、遞歸展開等完整功能。原始碼已開源於 GitHub:MineSweep。

核心資料結構

// 格子狀態
enum BlockState { UN_DIG, DIGGED, MARKED, BOMB, WRONG_BOMB };

// 單個格子
struct MineBlock {
    BlockState curState = UN_DIG;
    int valueFlag = 0; // -1=雷, 0-8=周圍雷數
};

// 遊戲狀態
enum GameState { PLAYING, FAULT, OVER, WIN };
enum GameLevel { BASIC, MEDIUM, HARD };

// 遊戲模型類
class GameModel {
public:
    vector<vector<MineBlock>> gameMap;
    int mRow, mCol, totalMineNumber, curMineNumber, timerSeconds;
    GameState gameState;
    GameLevel gameLevel;

    void createGame(int row, int col, int mineCount, GameLevel level);
    void digMine(int r, int c);      // 左鍵挖雷
    void markMine(int r, int c);     // 右鍵標記
    void checkGame();                // 判輸贏
    void restartGame();              // 重新開始
};

遊戲邏輯實現細節

1. 初始化與佈雷

void GameModel::createGame(int row, int col, int mineCount, GameLevel level) {
    gameMap.clear();
    mRow = row; mCol = col; totalMineNumber = curMineNumber = mineCount;
    gameState = PLAYING; gameLevel = level; timerSeconds = 0;

    // 初始化空格子
    gameMap.resize(mRow, vector<MineBlock>(mCol));

    // 隨機佈雷(避免重複)
    srand(time(0));
    int placed = 0;
    while (placed < totalMineNumber) {
        int r = rand() % mRow, c = rand() % mCol;
        if (gameMap[r][c].valueFlag != -1) {
            gameMap[r][c].valueFlag = -1;
            ++placed;
        }
    }

    // 計算數字提示(八鄰域)
    for (int i = 0; i < mRow; ++i)
        for (int j = 0; j < mCol; ++j)
            if (gameMap[i][j].valueFlag != -1)
                for (int dr = -1; dr <= 1; ++dr)
                    for (int dc = -1; dc <= 1; ++dc)
                        if (dr || dc) {
                            int nr = i+dr, nc = j+dc;
                            if (nr>=0 && nr=0 && nc

2. 遞歸展開挖雷

void GameModel::digMine(int r, int c) {
    if (r<0 || r>=mRow || c<0 || c>=mCol) return;
    MineBlock& b = gameMap[r][c];
    if (b.curState != UN_DIG) return;

    if (b.valueFlag > 0) { b.curState = DIGGED; return; } // 數字格直接翻開

    if (b.valueFlag == 0) { // 空白格遞歸展開
        b.curState = DIGGED;
        for (int dr=-1; dr<=1; ++dr)
            for (int dc=-1; dc<=1; ++dc)
                if (dr||dc) digMine(r+dr, c+dc);
        return;
    }

    if (b.valueFlag == -1) { // 踩雷
        b.curState = BOMB;
        gameState = OVER;
    }
    checkGame();
}

3. 標記與錯誤判斷

void GameModel::markMine(int r, int c) {
    MineBlock& b = gameMap[r][c];
    if (b.curState == UN_DIG) {
        if (b.valueFlag == -1) b.curState = MARKED;
        else { b.curState = WRONG_BOMB; gameState = FAULT; }
        --curMineNumber;
    } else if (b.curState == MARKED || b.curState == WRONG_BOMB) {
        b.curState = UN_DIG;
        gameState = PLAYING;
        ++curMineNumber;
    }
    checkGame();
}

4. 勝負判定

void GameModel::checkGame() {
    if (gameState == OVER) { // 輸:揭露所有雷
        for (auto& row : gameMap)
            for (auto& b : row)
                if (b.valueFlag == -1) b.curState = BOMB;
        return;
    }
    if (gameState == FAULT) return; // 有錯誤標記,繼續遊戲

    // 檢查是否所有非雷格子都已翻開
    for (auto& row : gameMap)
        for (auto& b : row)
            if (b.curState == UN_DIG) return; // 還有未翻開

    gameState = WIN; // 全翻開且無錯誤 → 勝利
}

界面渲染與交互 (MainGameWindow)

繪製邏輯

繼承 `QMainWindow`,重寫 `paintEvent`,使用 `QPainter` 根據 `gameMap` 狀態繪製位圖切片:

  • 笑臉按鈕:依 `gameState` 顯示正常/失敗/勝利表情。
  • 剩餘雷數:數字位圖拼接顯示。
  • 遊戲區:雙層迴圈遍歷 `gameMap`,根據 `curState` 選擇位圖偏移量繪製。

滑鼠事件處理

void MainGameWindow::mousePressEvent(QMouseEvent* e) {
    if (e->y() < topAreaHeight) { // 點擊笑臉區
        if (isSmileyClicked(e->x(), e->y())) {
            game->restartGame(); timer->start(1000); update(); return;
        }
    }
    if (game->gameState == OVER || game->gameState == WIN) return;

    int col = (e->x() - offsetX) / blockSize;
    int row = (e->y() - offsetY - topAreaHeight) / blockSize;
    if (row<0 || row>=game->mRow || col<0 || col>=game->mCol) return;

    if (e->button() == Qt::LeftButton)  game->digMine(row, col);
    else if (e->button() == Qt::RightButton) game->markMine(row, col);
    update(); // 觸發重繪
}

難度切換與計時

  • 選單動作連線至 `onLevelChooseClicked()`,調用 `game->createGame(...)` 並 `setFixedSize()` 重設視窗大小。
  • `QTimer` 每秒觸發 `updateTimer()` 更新 `timerSeconds` 與標籤顯示。

專案結構與編譯

MineSweep/
├── main.cpp
├── GameModel.h/.cpp      # 遊戲邏輯 (Model)
├── MainGameWindow.h/.cpp # 界面與控制 (View + Controller)
├── resources.qrc         # 位圖資源 (blocks.bmp, faces.bmp, ...)
└── MineSweep.pro         # qmake 專案檔

使用 Qt Creator 開啟 `.pro` 即可編譯運行。支援 Qt 4.8+ 與 Qt 5/6(微調 `include` 路徑)。

擴展建議

  1. 加入最高分記錄(`QSettings` 持久化)。
  2. 支援自訂尺寸/雷數對話框。
  3. 動畫效果:翻開格子漸變、爆炸粒子。
  4. 單元測試覆蓋 `GameModel` 核心邏輯。

此專案展示了 Qt 機制(信號槽、繪圖系統、事件處理)與 MVC 設計模式在遊戲開發中的實戰應用,是進階 Qt 開發的優秀練手項目。

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