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

Windows SDK 純 C 實現黑白棋 (Othello) 完整開發實錄:從 GDI 繪圖到 AI 搜索算法

     編輯:關於C語言

專案背景與技術選型

課程設計選題:黑白棋 (Othello/Reversi)。技術棧:**純 C 語言 + Windows SDK (GDI/User32) + Visual Studio**,拒絕 MFC、C++、托管代碼、第三方圖形庫。目標:深度理解 Windows 消息機制、GDI 繪圖流程、遊戲邏輯與 AI 搜索算法的工程落地。

一、 Windows SDK 程序架構基石

入口點與消息循環

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance,
                      LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW),
                      (HBRUSH)(COLOR_WINDOW+1), NULL, L"OthelloClass", NULL };
    RegisterClassEx(&wc);

    HWND hWnd = CreateWindow(L"OthelloClass", L"黑白棋", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

窗口過程 `WndProc` : 核心消息分發

消息處理邏輯關鍵點
`WM_CREATE`載入資源 (位圖、聲音)、初始化棋盤狀態、創建子窗口/菜單`LoadBitmap`/`LoadMenu`/`CreateWindow` (對話框)
`WM_PAINT`**核心繪圖** : `BeginPaint` → 繪製棋盤格線、棋子、合法落子提示、分數面板 → `EndPaint`**雙緩衝防閃爍** : 兼容 DC + 位圖,先畫內存再 `BitBlt` 到屏幕
`WM_LBUTTONDOWN`座標轉換 (屏幕→棋盤格)、合法性校驗、翻轉邏輯、AI 觸發、檢測勝負客戶區座標 `LOWORD(lParam)`/`HIWORD(lParam)`
`WM_COMMAND`菜單/對話框命令:新遊戲、難度選擇、設置、幫助、英雄榜`DialogBoxParam` 模態對話框
`WM_TIMER`AI 思考動畫、落子動畫幀更新`SetTimer`/`KillTimer`
`WM_DESTROY`釋放 GDI 對象 (`DeleteObject`)、殺死定時器、保存配置、`PostQuitMessage(0)`**資源泄漏零容忍**

二、 資源系統:位圖、圖標、菜單、對話框

使用 **Visual Studio 資源編輯器** 可視化設計,生成 `resource.h` (ID 宏) 與 `.rc` 腳本,編譯鏈入可執行文件。

  • 棋子/棋盤/背景:24/32 位 BMP,`LR_CREATEDIBSECTION` 載入支持 `GetObject` 獲取像素數據。
  • 9 大模態對話框:關於、開局選項、英雄榜輸入/顯示、幫助、設置、輸/贏/平局提示。對話框過程 `DlgProc` 處理 `WM_INITDIALOG`、`WM_COMMAND` (IDOK/IDCANCEL/控件通知)。
  • 菜單資源:主菜單 (遊戲/設置/幫助),快捷鍵關聯。

三、 遊戲核心邏輯:數據結構與狀態機

棋盤表示


#define BOARD_SIZE 8
#define EMPTY 0
#define BLACK 1
#define WHITE 2

typedef struct {
    BYTE board[BOARD_SIZE][BOARD_SIZE]; // 0空 1黑 2白
    int currentPlayer; // BLACK/WHITE
    int blackCount, whiteCount;
    int gameState; // PLAYING, BLACK_WIN, WHITE_WIN, DRAW
    int aiLevel; // 1/2/3
    bool vsAI;
} GameState;

GameState g_game; // 全局單例 (簡單項目可接受)

合法落子與翻轉算法 (八方向掃描)


const int dx[8] = {-1,-1,-1,0,0,1,1,1};
const int dy[8] = {-1,0,1,-1,1,-1,0,1};

// 檢查 (x,y) 落子是否合法,可選執行翻轉
bool IsValidMove(int x, int y, bool doFlip) {
    if (g_game.board[y][x] != EMPTY) return false;
    int player = g_game.currentPlayer;
    int opponent = 3 - player;
    bool anyFlipped = false;

    for (int dir = 0; dir < 8; dir++) {
        int nx = x + dx[dir], ny = y + dy[dir];
        int count = 0;
        while (InBoard(nx, ny) && g_game.board[ny][nx] == opponent) {
            nx += dx[dir]; ny += dy[dir]; count++;
        }
        if (count > 0 && InBoard(nx, ny) && g_game.board[ny][nx] == player) {
            if (doFlip) {
                nx = x + dx[dir]; ny = y + dy[dir];
                while (g_game.board[ny][nx] == opponent) {
                    g_game.board[ny][nx] = player;
                    nx += dx[dir]; ny += dy[dir];
                }
                g_game.board[y][x] = player;
                UpdateCount(player, count + 1); // 更新棋子數
            }
            anyFlipped = true;
        }
    }
    return anyFlipped;
}

遊戲流程狀態機


Player Move (WM_LBUTTONDOWN)
    → Validate & Flip → Check Opponent Has Move?
        → Yes: Switch Player → If AI Turn → SetTimer(AI_THINK)
        → No:  Check Current Player Has Move?
            → Yes: Continue (Player gets another turn)
            → No:  Game Over → Calculate Winner → Show Result Dialog

四、 AI 設計:從啟發式評估到 Alpha-Beta 剪枝

三級難度架構

級別策略特點
Level 1 (入門)**固定權重表 (Positional Weight Table)**角 > 邊 > 內部,無搜索,極快,固定走法,易破解
Level 2 (進階)**最大翻轉貪心 + 隨機打擾**選翻轉最多位置,平局隨機,仍屬短視,無前瞻
Level 3 (專家 - 未完成)**Minimax + Alpha-Beta 剪枝 + 迭代加深 + 置換表**多層搜索 (4-6 層),動態評估函數 (行動力、穩定性、邊緣、奇偶性),時間控制

Level 1 靜態評估表 (權重示例)


const int WEIGHT[8][8] = {
    {120, -20,  20,   5,   5,  20, -20, 120},
    {-20, -40,  -5,  -5,  -5,  -5, -40, -20},
    { 20,  -5,  15,   3,   3,  15,  -5,  20},
    {  5,  -5,   3,   3,   3,   3,  -5,   5},
    {  5,  -5,   3,   3,   3,   3,  -5,   5},
    { 20,  -5,  15,   3,   3,  15,  -5,  20},
    {-20, -40,  -5,  -5,  -5,  -5, -40, -20},
    {120, -20,  20,   5,   5,  20, -20, 120}
};
// 角(120)極高,角旁(-20/-40)極低 (避免送角),邊緣次之

Alpha-Beta 剪枝核心框架 (Level 3 設計草案)


int AlphaBeta(int depth, int alpha, int beta, int player) {
    if (depth == 0 || GameOver()) return EvaluateBoard(player);

    Move moves[MAX_MOVES]; int moveCount = GenerateMoves(moves, player);
    if (moveCount == 0) return -AlphaBeta(depth-1, -beta, -alpha, 3-player); // Pass

    for (int i = 0; i < moveCount; i++) {
        MakeMove(&moves[i]);
        int score = -AlphaBeta(depth-1, -beta, -alpha, 3-player);
        UndoMove(&moves[i]);
        if (score >= beta) return beta; // Beta cutoff
        if (score > alpha) alpha = score;
    }
    return alpha;
}

// 迭代加深 + 時間控制
Move FindBestMove(int maxTimeMs) {
    Move bestMove;
    for (int depth = 1; ; depth++) {
        if (GetTickCount() - startTime > maxTimeMs) break;
        int alpha = -INF, beta = INF;
        Move moves[MAX_MOVES]; int n = GenerateMoves(moves, g_game.currentPlayer);
        for (int i = 0; i < n; i++) {
            MakeMove(&moves[i]);
            int score = -AlphaBeta(depth-1, -beta, -alpha, 3-g_game.currentPlayer);
            UndoMove(&moves[i]);
            if (score > alpha) { alpha = score; bestMove = moves[i]; }
        }
    }
    return bestMove;
}

五、 GDI 繪圖優化:雙緩衝與區域重繪


case WM_PAINT: {
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    // 1. 創建兼容 DC 與位圖 (僅首次/尺寸變化時創建,靜態變量緩存)
    static HDC hMemDC = NULL; static HBITMAP hMemBmp = NULL, hOldBmp = NULL;
    RECT rc; GetClientRect(hWnd, &rc);
    if (!hMemDC || rc.right != memWidth || rc.bottom != memHeight) {
        if (hMemDC) { SelectObject(hMemDC, hOldBmp); DeleteObject(hMemBmp); DeleteDC(hMemDC); }
        hMemDC = CreateCompatibleDC(hdc);
        hMemBmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
        hOldBmp = SelectObject(hMemDC, hMemBmp);
        memWidth = rc.right; memHeight = rc.bottom;
    }
    
    // 2. 繪製到內存 DC
    FillRect(hMemDC, &rc, GetSysColorBrush(COLOR_WINDOW)); // 背景
    DrawBoardGrid(hMemDC);        // 格線
    DrawPieces(hMemDC);           // 棋子 (TransparentBlt / MaskBlt 實現透明)
    DrawValidHints(hMemDC);       // 合法落子高亮
    DrawScorePanel(hMemDC);       // 分數、當前玩家
    
    // 3. 一次性 BitBlt 到屏幕
    BitBlt(hdc, 0, 0, rc.right, rc.bottom, hMemDC, 0, 0, SRCCOPY);
    
    EndPaint(hWnd, &ps);
    return 0;
}

關鍵優化**:靜態緩存 `hMemDC`/`hMemBmp` 避免幀幀創建銷毀;`WM_ERASEBKGND` 返回 `TRUE` 禁止系統擦除背景;`InvalidateRect(hWnd, &dirtyRect, FALSE)` 實現**區域重繪** (僅重繪變化區域)。

六、 工程總結與遺憾

亮點

  • 純 C + SDK 完整實現 Windows GUI 遊戲,**零外部依賴**,深度理解消息循環、GDI、資源管理。
  • 模塊化設計:`main.c` (入口/窗口)、`game.c/h` (邏輯/AI)、`draw.c/h` (繪圖)、`resource.h` (ID)、`global.h` (全局狀態)。
  • AI 從靜態評估演進到 Alpha-Beta 框架,算法結構清晰。
  • 資源釋放嚴謹,無 GDI 泄漏、無內存泄漏、無句柄泄漏。

遺憾與改進方向

  1. Level 3 AI 未完成:缺乏置換表、移動排序、奇偶性裁剪、穩定性評估、開局庫、終局精確搜索。
  2. 全局狀態耦合:`g_game` 全局變量難以支持多實例/網絡對戰,應封裝為 `GameContext*` 實例傳遞。
  3. 異步 AI 卡頓:`WM_TIMER` 分片思考或 worker 線程 + `PostMessage` 通知主線程繪製,避免界面凍結。
  4. 缺乏持久化:棋譜保存/回放 (SGF/自定義格式)、配置文件 (INI/Registry)。
  5. 高 DPI 模糊:需 `SetProcessDPIAware` + 繪圖邏輯支持縮放。
  6. 單元測試缺失:邏輯層 (`game.c`) 應剝離平台依賴,編寫獨立測試 (MinUnit/Check)。

七、 給後續開發者的建議

  1. 先寫核心邏輯庫 (Pure C, 無平台依賴),再包裝平台層 (Win32/SDL/Qt/WebAssembly)。
  2. AI 參考開源引擎:Edax, Zebra, WZebra — 研究其評估函數、搜索優化、開局庫結構。
  3. 現代化圖形層**:新項目建議 SDL2 / Raylib / Direct2D / Vulkan,GDI 僅限維舊項目。
  4. 嚴肅對待資源管理**:RAII 風格封裝 `HBITMAP`/`HDC`/`HFONT` 等句柄,即使寫 C 也要用 `cleanup` 模式 (`goto Error` 統一釋放)。

這個專案雖是課程作業,但踩過的坑 (消息循環、GDI 泄漏、AI 算法、繪圖閃爍、全局耦合) 正是 Windows 系統級開發的必修課。代碼已開源 (假設鏈接),歡迎交流指正。

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