使用 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(); // 重新開始
};
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` 路徑)。
擴展建議
- 加入最高分記錄(`QSettings` 持久化)。
- 支援自訂尺寸/雷數對話框。
- 動畫效果:翻開格子漸變、爆炸粒子。
- 單元測試覆蓋 `GameModel` 核心邏輯。
此專案展示了 Qt 機制(信號槽、繪圖系統、事件處理)與 MVC 設計模式在遊戲開發中的實戰應用,是進階 Qt 開發的優秀練手項目。