Mine Sweeper
| assets | o_o ! | |
| include | o_O!! | |
| lili | o_O!! | |
| .gitignore | o_o ! | |
| mineSw1.c | o_o ! | |
| Minesweeper.png | o_O!! | |
| README.md | o_o ! | |
MineSweeper Game (mineSw1.c)
這是一個使用 Raylib 圖形庫開發的經典踩地雷(Minesweeper)遊戲程式。程式以 C 語言撰寫,支援不同的難度等級,並包含音效、圖片資源和自訂字體。
專案概述
這個程式實現了完整的踩地雷遊戲邏輯,包括:
- 三種難度:簡單 (9x9, 10 地雷)、中等 (16x16, 40 地雷)、困難 (30x16, 99 地雷)
- 左鍵揭開格子,右鍵標記旗子或問號
- 第一次點擊保證安全(不會踩到地雷)
- 勝利條件:揭開所有非地雷格子
- 失敗條件:揭開地雷
- 計時器和地雷計數器
- 音效和視覺效果
依賴項
- Raylib: 圖形、音訊和輸入處理庫
- 自訂庫: lili/myfont.c (自訂字體), lili/util.h (工具函式)
- 資源檔案:
- assets/brick.png: 磚塊圖片 (用於格子)
- assets/num.png: 數字圖片 (0-9 和紅色方塊)
- assets/flag.wav: 插旗音效
- assets/boom.wav: 爆炸音效
- assets/win.wav: 勝利音效
- assets/question.wav: 問號音效
編譯和運行
Windows (使用 MinGW 或 MSVC)
gcc mineSw1.c -o mineSw1.exe -lraylib -lwinmm -lgdi32 -lopengl32
./mineSw1.exe
Linux
gcc mineSw1.c -o mineSw1 -lraylib -lGL -lm -lpthread -ldl -lrt -lX11
./mineSw1
macOS
gcc mineSw1.c -o mineSw1 -lraylib -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL
./mineSw1
程式流程
-
初始化階段 (
main()):- 初始化隨機種子
- 設定視窗 (800x600, "Raylib Minesweeper")
- 啟動音訊裝置
- 設定目標 FPS 為 60
- 載入自訂字體
- 初始化遊戲結構 (
GameInit())
-
主要遊戲循環:
- 更新階段 (
Update()):- 根據遊戲狀態處理輸入
- 在 MENU 狀態下,按 1/2/3 選擇難度並重設遊戲
- 在 PLAYING 狀態下,處理滑鼠輸入 (左鍵揭開,右鍵標記)
- 檢查勝利或失敗條件
- 繪製階段 (
Draw()):- 清除背景
- 根據狀態繪製菜單或遊戲畫面
- 顯示地雷數量、時間、網格等
- 更新階段 (
-
清理階段:
- 釋放資源 (
GameFini()) - 關閉音訊裝置和視窗
- 釋放資源 (
遊戲狀態
- MENU: 顯示難度選擇菜單
- PLAYING: 遊戲進行中
- WON: 勝利狀態
- LOST: 失敗狀態
關鍵函式說明
初始化與清理
-
GameInit(Game* game):- 初始化遊戲狀態為 MENU
- 載入磚塊紋理 (brick.png) 和數字紋理 (num.png)
- 定義紋理切片矩形
- 載入音效檔案
- 如果資源載入失敗,使用生成的備用圖片
-
GameFini(Game* game):- 釋放所有載入的紋理和音效資源
遊戲邏輯
-
ResetGame(Game* game, Difficulty diff):- 根據選擇的難度設定遊戲參數 (行列數、地雷數、格子大小)
- 重設計時器和狀態
- 清空格子網格
- 調整視窗大小以適應網格
-
GenMines(Game* game, Cell* cell):- 在第一次點擊後生成地雷
- 確保點擊格子和其周圍 3x3 區域沒有地雷
- 計算每個格子的地雷鄰居數量
- 設定每個格子的鄰居指標陣列
-
RevealCell(Game* game, Cell* cell):- 遞歸揭開格子
- 如果是地雷,設定為 LOST 狀態並播放爆炸音效
- 如果地雷計數為 0,自動揭開所有鄰居格子
- 第一次點擊時觸發地雷生成
-
CheckWin(Game* game):- 檢查所有非地雷格子是否已被揭開
- 如果是,設定為勝利狀態並播放勝利音效
-
ToggleMark(Game* game, int r, int c):- 切換格子標記狀態:無標記 → 旗子 → 問號 → 無標記
- 更新剩餘地雷計數
- 播放相應音效
-
RevealAllMines(Game* game):- 在失敗時一次性揭開所有地雷格子
- 防止重複執行
繪製函式
-
DrawCustomDigit(Game* game, int digit, float x, float y, float scale):- 使用數字紋理繪製單個數字 (0-10,10 為紅色方塊)
-
DrawCustomNumber(Game* game, int number, float x, float y, float scale):- 將數字轉換為三個數字字串,使用 DrawCustomDigit 繪製
主循環函式
-
Update(Game* game):- 處理鍵盤和滑鼠輸入
- 更新遊戲狀態和邏輯
-
Draw(Game* game):- 根據當前狀態繪製適當的畫面
- 顯示地雷數量和經過時間
- 繪製網格,根據格子狀態使用不同紋理和文字
資料結構
DifficultySettings
rows: 行數cols: 列數mines: 地雷數cellSize: 格子像素大小
Cell
isMine: 是否為地雷isRevealed: 是否已被揭開isFlagged: 是否標記旗子isQuestion: 是否標記問號mineCount: 周圍地雷數量 (0-8)neighborsCount: 有效鄰居數量 (3-8)neighbors[8]: 指向鄰居格子的指標
Game
grid[MAX_ROWS][MAX_COLS]: 二維格子陣列currentState: 當前遊戲狀態currentDifficulty: 當前難度minesLeft: 剩餘地雷數量startTime/endTime: 開始和結束時間 (用於計時)curC/curR: 當前滑鼠所在的列/行brickTexture/numberTexture: 載入的紋理brickRecs[6]/numberRecs[11]: 紋理切片矩形firstClick: 是否為第一次點擊 (用於安全機制)gameOverRevealed: 失敗時是否已顯示地雷- 多個音效物件
操作說明
-
菜單:
- 按 1: 簡單難度
- 按 2: 中等難度
- 按 3: 困難難度
-
遊戲中:
- 左鍵點擊: 揭開格子
- 右鍵點擊: 切換標記 (無/旗子/問號)
- 中鍵點擊: 同右鍵
-
結束遊戲:
- 勝利或失敗後,按 R 鍵返回菜單
資源載入備用機制
程式設計時考慮了資源檔案可能不存在的情況:
- 如果 brick.png 不存在,使用
GenImageChecked()生成格子狀圖片 - 如果 num.png 不存在,使用
GenImageChecked()生成數字區域 - 如果音效檔案不存在,程式仍能正常運行,只是沒有音效
這使得程式在沒有完整資源的情況下也能運行,適合開發和測試。
最佳實踐
- 使用紋理圖集 (atlas) 而非個別圖片,提高載入效率
- 有效率的地雷生成演算法,避免重複隨機位置
- 遞歸揭開演算法處理連續空白區域
- 狀態機設計清晰分離遊戲邏輯和繪製
- 資源管理:載入時初始化,結束時清理