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);
}
WM_CREATE 建立相容 DC + 位圖,WM_SIZE 重建適應新尺寸。WM_PAINT 只負責 BitBlt 將記憶體 DC 一次性拷貝到螢幕 DC,**零閃爍**。draw_board(HDC memDC)(實現於 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;
}
main.c、DrawMap.c、map.c、def.h、DrawMap.h。gcc main.c DrawMap.c map.c -o minesweeper.exe -mwindows -lgdi32三篇文章構成一個**零依賴、單檔編譯、約 400 行**的完整掃雷遊戲,展示純 C 在 Windows 平台上的系統級開發能力。