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

掃雷遊戲純 C 實現(三):Win32 API 視窗與 GDI 繪圖完整接入

     編輯:關於C語言

專案架構

  • 邏輯層:map.c/h — 佈雷、計數、展開、勝負判定
  • 繪圖層:DrawMap.c/h — GDI 繪製格子、數字、地雷、旗幟
  • 主程式:main.c — WinMain、視窗類註冊、訊息迴圈、WndProc 分發

主程式進入點


#include 
#include 
#include "DrawMap.h"
#include "def.h"

int m = 10, n = 10;               // 棋盤大小
int map[MAX_X][MAX_Y];            // 邏輯棋盤 (0-8 數字, -1 地雷)
int g_x[MAX_X], g_y[MAX_Y];       // 每格像素座標快取

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// 繪圖輔助 API 前向宣告 (實際在 DrawMap.c 實現)
BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT);
BOOL WINAPI LineTo(HDC, int, int);
HPEN WINAPI CreatePen(int, int, COLORREF);

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow) {
    init();                       // 佈雷、計算數字
    int xPos, yPos, xSize, ySize;
    calc_window_size(&xPos, &yPos, &xSize, &ySize); // 依格子大小換算視窗尺寸

    WNDCLASS wc = {0};
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hInst;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszClassName = TEXT("MineSweeperClass");

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, TEXT("註冊視窗類失敗,需 Windows NT 以上系統"), wc.lpszClassName, MB_ICONERROR);
        return 0;
    }

    HWND hwnd = CreateWindow(wc.lpszClassName,
                             TEXT("掃雷遊戲 — 純 C Win32 實現"),
                             WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
                             xPos, yPos, xSize, ySize,
                             NULL, NULL, hInst, NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

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

視窗程式:核心訊息處理


LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    static HDC hdcMem = NULL;
    static HBITMAP hbmMem = NULL;
    static RECT rcClient;
    PAINTSTRUCT ps;
    HDC hdc;

    switch (msg) {
    case WM_CREATE: {
        hdc = GetDC(hwnd);
        hdcMem = CreateCompatibleDC(hdc);
        GetClientRect(hwnd, &rcClient);
        hbmMem = CreateCompatibleBitmap(hdc, rcClient.right, rcClient.bottom);
        SelectObject(hdcMem, hbmMem);
        ReleaseDC(hwnd, hdc);
        return 0;
    }
    case WM_SIZE: {
        GetClientRect(hwnd, &rcClient);
        if (hbmMem) DeleteObject(hbmMem);
        hdc = GetDC(hwnd);
        hbmMem = CreateCompatibleBitmap(hdc, rcClient.right, rcClient.bottom);
        SelectObject(hdcMem, hbmMem);
        ReleaseDC(hwnd, hdc);
        InvalidateRect(hwnd, NULL, TRUE); // 觸發重繪
        return 0;
    }
    case WM_PAINT: {
        hdc = BeginPaint(hwnd, &ps);
        draw_board(hdcMem);           // 雙緩衝:先畫記憶體 DC
        BitBlt(hdc, 0, 0, rcClient.right, rcClient.bottom, hdcMem, 0, 0, SRCCOPY);
        EndPaint(hwnd, &ps);
        return 0;
    }
    case WM_LBUTTONDOWN: {
        int col = LOWORD(lp) / CELL_W;
        int row = HIWORD(lp) / CELL_H;
        if (reveal_cell(row, col)) {  // 返回 true 表踩雷
            MessageBox(hwnd, TEXT("Game Over!"), TEXT("掃雷"), MB_ICONINFORMATION);
            init();                   // 重新開始
            InvalidateRect(hwnd, NULL, TRUE);
        } else if (check_win()) {
            MessageBox(hwnd, TEXT("You Win!"), TEXT("掃雷"), MB_ICONINFORMATION);
            init();
            InvalidateRect(hwnd, NULL, TRUE);
        }
        return 0;
    }
    case WM_RBUTTONDOWN: {
        int col = LOWORD(lp) / CELL_W;
        int row = HIWORD(lp) / CELL_H;
        toggle_flag(row, col);
        InvalidateRect(hwnd, NULL, TRUE);
        return 0;
    }
    case WM_DESTROY: {
        if (hdcMem) DeleteDC(hdcMem);
        if (hbmMem) DeleteObject(hbmMem);
        PostQuitMessage(0);
        return 0;
    }
    }
    return DefWindowProc(hwnd, msg, wp, lp);
}

雙緩衝繪圖關鍵點

  1. WM_CREATE 建立相容 DC + 位圖,WM_SIZE 重建適應新尺寸。
  2. WM_PAINT 只負責 BitBlt 將記憶體 DC 一次性拷貝到螢幕 DC,**零閃爍**。
  3. 所有繪圖邏輯集中在 draw_board(HDC memDC)(實現於 DrawMap.c)。

DrawMap.c 核心繪製邏輯(節錄)


void draw_board(HDC hdc) {
    // 1. 填充背景
    HBRUSH hBrush = CreateSolidBrush(RGB(192,192,192));
    FillRect(hdc, &rcClient, hBrush);
    DeleteObject(hBrush);

    // 2. 畫格線
    HPEN hPen = CreatePen(PS_SOLID, 1, RGB(128,128,128));
    HPEN hOld = SelectObject(hdc, hPen);
    for (int i = 0; i <= n; ++i)
        MoveToEx(hdc, g_x[i], 0, NULL), LineTo(hdc, g_x[i], g_y[m]);
    for (int j = 0; j <= m; ++j)
        MoveToEx(hdc, 0, g_y[j], NULL), LineTo(hdc, g_x[n], g_y[j]);
    SelectObject(hdc, hOld);
    DeleteObject(hPen);

    // 3. 遍歷格子繪製內容
    for (int r = 0; r < m; ++r)
        for (int c = 0; c < n; ++c)
            draw_cell(hdc, r, c);
}

void draw_cell(HDC hdc, int r, int c) {
    RECT rc = { g_x[c], g_y[r], g_x[c+1], g_y[r+1] };
    int val = map[r][c];
    if (val == -2) {                 // 未揭開
        DrawEdge(hdc, &rc, EDGE_RAISED, BF_RECT);
    } else if (val == -3) {          // 標記旗幟
        DrawEdge(hdc, &rc, EDGE_SUNKEN, BF_RECT);
        draw_flag(hdc, rc);
    } else if (val == -1) {          // 地雷 (失敗揭示)
        draw_mine(hdc, rc);
    } else if (val > 0) {            // 數字 1-8
        SetBkMode(hdc, TRANSPARENT);
        SetTextColor(hdc, number_color[val]);
        DrawText(hdc, number_str[val], 1, &rc, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
    } else {                         // val == 0 空白
        DrawEdge(hdc, &rc, EDGE_SUNKEN, BF_RECT);
    }
}

座標快取計算


void calc_window_size(int* xPos, int* yPos, int* w, int* h) {
    for (int i = 0; i <= n; ++i) g_x[i] = i * CELL_W;
    for (int j = 0; j <= m; ++j) g_y[j] = j * CELL_H;
    *w = g_x[n] + GetSystemMetrics(SM_CXFRAME)*2;
    *h = g_y[m] + GetSystemMetrics(SM_CYFRAME)*2 + GetSystemMetrics(SM_CYCAPTION);
    *xPos = *yPos = CW_USEDEFAULT;
}

編譯與執行

  • Visual Studio:建立 **Win32 Project** → **Windows Application** → 加入 main.c、DrawMap.c、map.c、def.h、DrawMap.h。
  • MinGW-w64:gcc main.c DrawMap.c map.c -o minesweeper.exe -mwindows -lgdi32
  • 執行後出現 10×10 初級棋盤,左鍵揭開、右鍵標旗,踩雷/全揭開均彈窗提示並自動重置。

系列總結

  1. 邏輯層:哨兵法計數、遞歸/棧式 Flood Fill、狀態機。
  2. 優化:方向向量表、哨兵消除邊界判斷。
  3. GUI 層:Win32 視窗類、雙緩衝 GDI、訊息分發、座標映射。

三篇文章構成一個**零依賴、單檔編譯、約 400 行**的完整掃雷遊戲,展示純 C 在 Windows 平台上的系統級開發能力。

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