 |
【main.c】 #include <tchar.h> #include <time.h> #include <Windows.h> #include "diamonds.h"
BOOL bGameOver = FALSE; BOOL bPaused = FALSE; HWND hwndMain; extern LPTSTR lpMsg;
// 窗口過程函數 LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (uMsg) { case WM_CHAR: switch (wParam) { case '+': case '=': if (!bGameOver && !bPaused) AdjustDiamond(DOWN); break; case '-': case '_': if (!bGameOver && !bPaused) AdjustDiamond(UP); break; case 'l': case 'L': if (!bPaused) LoadGame(); break; case 'p': case 'P': if (!bGameOver) { bPaused = !bPaused; UpdateGame(); } break; case 's': case 'S': if (bGameOver) ShowMsg(TEXT("遊戲結束時不能存檔")); else SaveGame(); break; } break; case WM_CREATE: hwndMain = hWnd; srand((UINT)time(NULL)); InitGame(); break; case WM_DESTROY: KillTimer(hWnd, IDT_TIMER1); // 關閉定時器 ExitGame(); // 清理遊戲數據 PostQuitMessage(0); // 結束消息循環 break; case WM_KEYDOWN: if (bPaused) break; // 遊戲暫停時不能執行這些操作 switch (wParam) { case VK_DOWN: if (!bGameOver) MoveDiamond(DOWN); break; case VK_ESCAPE: RestartGame(); break; case VK_LEFT: if (!bGameOver) MoveDiamond(LEFT); break; case VK_RIGHT: if (!bGameOver) MoveDiamond(RIGHT); break; } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); Draw(hdc, &ps); EndPaint(hWnd, &ps); break; case WM_TIMER: switch (wParam) { case IDT_TIMER1: if (!bPaused) MoveDiamond(DOWN); break; case IDT_TIMER2: lpMsg = NULL; UpdateGame(); KillTimer(hWnd, IDT_TIMER2); } break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return FALSE; }
// 主函數 int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { HWND hWnd; MSG msg; RECT rect; WNDCLASSEX wcex;
// 註冊窗口類 ZeroMemory(&wcex, sizeof(wcex)); wcex.cbSize = sizeof(WNDCLASSEX); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); wcex.hInstance = hInstance; wcex.lpfnWndProc = WndProc; wcex.lpszClassName = TEXT("Diamonds"); wcex.style = CS_HREDRAW | CS_VREDRAW; RegisterClassEx(&wcex);
// 計算窗口大小 SetRect(&rect, 0, 0, GAME_WIDTH, GAME_HEIGHT); AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
// 創建並顯示窗口 hWnd = CreateWindow(wcex.lpszClassName, TEXT("寶石方塊"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL); if (!hWnd) return 1; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
// 消息循環 while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
|
 |
【diamonds.c】 #include <stdio.h> #include <tchar.h> #include <Windows.h> #include <strsafe.h> #include "diamonds.h"
COLORREF clrColorList[] = {0xa00000, 0xa05000, 0xa0a000, 0xc000, 0xa0a0, 0x4040c0, 0xa000a0, 0x808080, 0x577ab9, 0xec9ff}; // 顏色表
/* 應寫入文件的遊戲數據 */ GAME game; BYTE bMap[DIAMOND_VERT_NUM][DIAMOND_VERT_NUM]; // 已釋放的寶石列表
/* 不應寫入文件的遊戲數據*/ HFONT hFont = NULL; HFONT hfontWarning = NULL; LPTSTR lpMsg = NULL;
extern BOOL bGameOver, bPaused; extern HWND hwndMain;
// 調整寶石順序 void AdjustDiamond(BYTE bDirection) { if (bDirection == UP) { game.bDiamond = (game.bDiamond >> 2) | ((game.bDiamond & 0x03) << 4); game.wColor = (game.wColor >> 4) | ((game.wColor & 0x0f) << 8); } else { game.bDiamond = ((game.bDiamond << 2) & 0x3c) | (game.bDiamond >> 4); game.wColor = ((game.wColor << 4) & 0xff0) | (game.wColor >> 8); } UpdateGame(); }
// 檢查模型是否與已有寶石碰撞 BOOL CheckCrash(void) { int i, y; for (i = 0; i < 3; i++) { y = game.ptDiamond.y + i; if (bMap[y][game.ptDiamond.x] & 0x80) return TRUE; } return FALSE; }
// 隨機生成模型 void CreateDiamond(void) { DWORD dwData[2] = {0}; int nColors = _countof(clrColorList); // 顏色表中的顏色數 int i, j, n; if (!(game.bNextDiamond & 0x80)) { // 如果創建的不是第一個模型 n = 1; game.bDiamond = game.bNextDiamond; game.wColor = game.wNextColor; } else n = 2; for (i = 0; i < n; i++) { // 形狀: 00CCBBAA for (j = 0; j < 3; j++) { dwData[i] <<= 2; dwData[i] |= rand() % 4; } // 顏色: 0000CCCC BBBBAAAA for (j = 0; j < 3; j++) { dwData[i] <<= 4; dwData[i] |= rand() % nColors; } } game.bNextDiamond = dwData[0] >> 12; game.wNextColor = dwData[0] & 0xfff; if (n == 2) { game.bDiamond = dwData[1] >> 12; game.wColor = dwData[1] & 0xfff; } game.ptDiamond.y = 0; if (CheckCrash()) GameOver(); }
// 繪製遊戲主界面 void Draw(HDC hdc, LPPAINTSTRUCT lpps) { HBITMAP hbmp; HDC hdcMem; LPCTSTR pStr; RECT rect, rcClient; TCHAR szText[50];
// 創建畫布 hbmp = CreateCompatibleBitmap(hdc, GAME_WIDTH, GAME_HEIGHT); hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hbmp); // 將整個畫布刷成白色 if (lpps->fErase) { SetRect(&rect, 0, 0, GAME_WIDTH, GAME_HEIGHT); FillRect(hdcMem, &rect, GetSysColorBrush(COLOR_WINDOW)); }
// 繪製當前方塊的矩形框 SelectObject(hdcMem, GetStockObject(BLACK_PEN)); // 黑色邊框 SelectObject(hdcMem, GetStockObject(NULL_BRUSH)); // 不填充 rect.left = rect.top = 10; rect.right = rect.left + DIAMOND_WIDTH * 4; rect.bottom = rect.top + DIAMOND_HEIGHT * 4; Rectangle(hdcMem, rect.left, rect.top, rect.right + 1, rect.bottom + 1); DrawDiamondIn(hdcMem, &rect, game.bNextDiamond, game.wNextColor);
// 繪製遊戲操作說明矩形框 OffsetRect(&rect, 0, rect.bottom - rect.top + 26); // 向下移動矩形 rect.bottom = rect.top + 190; Rectangle(hdcMem, rect.left, rect.top, rect.right, rect.bottom); // 在框中顯示文字 SelectObject(hdcMem, hFont); // 選擇字體 InflateRect(&rect, -4, -4); // 設置文本邊界空白寬度(padding) pStr = TEXT("遊戲操作說明\n1.按「←」左移\n2.按「→」右移\n3.按「+」「-」調整順序\n4.按「↓」加速下落\n5.按「S」存檔\n6.按「L」讀檔\n7.按「ESC」重新開始\n8.按「P」暫停\n9.三個形狀一樣的寶石相遇即可消除"); DrawText(hdcMem, pStr, lstrlen(pStr), &rect, DT_WORDBREAK);
// 繪製遊戲區域框 rect.top = 10; rect.left = rect.right + 36; rect.right = rect.left + DIAMOND_HORZ_NUM * DIAMOND_WIDTH; rect.bottom = rect.top + DIAMOND_VERT_NUM * DIAMOND_HEIGHT; Rectangle(hdcMem, rect.left, rect.top, rect.right + 1, rect.bottom + 1);
// 繪製遊戲區域 DrawGameDistrict(hdcMem, &rect);
// 在遊戲區域下面顯示分數 rect.top = rect.bottom; rect.bottom = GAME_HEIGHT; StringCchPrintf(szText, _countof(szText), TEXT("分數: %u"), game.uScore); SelectObject(hdcMem, hfontWarning); SetTextColor(hdcMem, RGB(163, 73, 164)); SetBkMode(hdcMem, TRANSPARENT); DrawText(hdcMem, szText, lstrlen(szText), &rect, DT_CENTER);
SetRect(&rect, 0, GAME_HEIGHT / 2 - 26, GAME_WIDTH, GAME_HEIGHT / 2 + 16); if (lpMsg != NULL) { SetTextColor(hdcMem, RGB(255, 128, 0)); rect.bottom += 20; DrawText(hdcMem, lpMsg, lstrlen(lpMsg), &rect, DT_CENTER); } else if (bGameOver || bPaused) { SetTextColor(hdcMem, RGB(255, 0, 0)); if (bGameOver) pStr = TEXT("GAME OVER"); else if (bPaused) pStr = TEXT("GAME PAUSED"); DrawText(hdcMem, pStr, lstrlen(pStr), &rect, DT_CENTER); } // 將畫布上的內容顯示到屏幕上 GetClientRect(hwndMain, &rcClient); StretchBlt(hdc, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hdcMem, 0, 0, GAME_WIDTH, GAME_HEIGHT, SRCCOPY);
// 刪除畫布 DeleteDC(hdcMem); DeleteObject(hbmp); }
// 繪製寶石模型 void DrawDiamond(HDC hdc, int x, int y, BYTE bDiamond, WORD wColor) { HBRUSH hbrOld; HPEN hpenOld; int i; RECT rect; rect.left = x; rect.top = y; rect.right = rect.left + DIAMOND_WIDTH + 1; hbrOld = (HBRUSH)SelectObject(hdc, GetStockObject(DC_BRUSH)); hpenOld = (HPEN)SelectObject(hdc, GetStockObject(BLACK_PEN)); // 選擇黑色畫筆 for (i = 0; i < 3; i++) { SetDCBrushColor(hdc, clrColorList[wColor & 0x0f]); rect.bottom = rect.top + DIAMOND_HEIGHT + 1; DrawShape(hdc, &rect, bDiamond & 0x03); rect.top += DIAMOND_HEIGHT; bDiamond >>= 2; wColor >>= 4; }
// 還原之前的畫刷和畫筆 SelectObject(hdc, hbrOld); SelectObject(hdc, hpenOld); }
// 在指定區域內繪製寶石模型 void DrawDiamondIn(HDC hdc, LPRECT lpRect, BYTE bDiamond, WORD wColor) { int x, y; x = lpRect->left + (lpRect->right - lpRect->left - DIAMOND_WIDTH) / 2; y = lpRect->top + (lpRect->bottom - lpRect->top - DIAMOND_HEIGHT * 3) / 2; DrawDiamond(hdc, x, y, bDiamond, wColor); }
void DrawGameDistrict(HDC hdc, LPRECT lpRect) { HBRUSH hbrOld; HPEN hpenOld; int x, y; RECT rect; // 已釋放的寶石 hbrOld = (HBRUSH)SelectObject(hdc, GetStockObject(DC_BRUSH)); hpenOld = (HPEN)SelectObject(hdc, GetStockObject(BLACK_PEN)); for (y = 0; y < DIAMOND_VERT_NUM; y++) { for (x = 0; x < DIAMOND_HORZ_NUM; x++) { if (bMap[y][x] & 0x80) { rect.left = lpRect->left + x * DIAMOND_WIDTH; rect.top = lpRect->top + y * DIAMOND_HEIGHT; rect.right = rect.left + DIAMOND_WIDTH + 1; rect.bottom = rect.top + DIAMOND_HEIGHT + 1; SetDCBrushColor(hdc, clrColorList[bMap[y][x] & 0x0f]); DrawShape(hdc, &rect, (bMap[y][x] >> 4) & 0x03); } } }
// 正在下落的寶石 if (!bGameOver) DrawDiamond(hdc, lpRect->left + game.ptDiamond.x * DIAMOND_WIDTH, lpRect->top + game.ptDiamond.y * DIAMOND_HEIGHT, game.bDiamond, game.wColor);
SelectObject(hdc, hbrOld); SelectObject(hdc, hpenOld); }
void DrawShape(HDC hdc, LPRECT lpRect, BYTE bShape) { POINT pt[4]; switch (bShape) { case 0: Rectangle(hdc, lpRect->left, lpRect->top, lpRect->right, lpRect->bottom); break; case 1: Ellipse(hdc, lpRect->left, lpRect->top, lpRect->right, lpRect->bottom); break; case 2: pt[0].x = lpRect->left + (lpRect->right - lpRect->left) / 2; pt[0].y = lpRect->top; pt[1].x = lpRect->right - 1; pt[1].y = lpRect->top + (lpRect->bottom - lpRect->top) / 2; pt[2].x = pt[0].x; pt[2].y = lpRect->bottom - 1; pt[3].x = lpRect->left; pt[3].y = pt[1].y; Polygon(hdc, pt, 4); break; case 3: pt[0].x = lpRect->left + (lpRect->right - lpRect->left) / 2; pt[0].y = lpRect->top; pt[1].x = lpRect->left; pt[1].y = lpRect->bottom - 1; pt[2].x = lpRect->right - 1; pt[2].y = pt[1].y; Polygon(hdc, pt, 3); } }
void ExitGame(void) { DeleteObject(hFont); DeleteObject(hfontWarning); }
void GameOver(void) { KillTimer(hwndMain, IDT_TIMER1); bGameOver = TRUE; }
void InitFont(void) { LOGFONT lf; ZeroMemory(&lf, sizeof(lf)); lf.lfCharSet = GB2312_CHARSET; StringCbCopy(lf.lfFaceName, sizeof(lf.lfFaceName), TEXT("宋體")); lf.lfHeight = 12; hFont = CreateFontIndirect(&lf);
lf.lfCharSet = DEFAULT_CHARSET; StringCbCopy(lf.lfFaceName, sizeof(lf.lfFaceName), TEXT("Times New Roman")); lf.lfHeight = 30; lf.lfWeight = FW_BOLD; hfontWarning = CreateFontIndirect(&lf); }
void InitGame(void) { game.ptDiamond.x = 5; game.uScore = 0; if (hFont == NULL) { InitFont(); game.bNextDiamond = 0x80; // 只有遊戲最開始時才同時隨機生成當前塊和下一塊 } ZeroMemory(bMap, sizeof(bMap)); CreateDiamond(); StartTimer(); }
BOOL LoadGame(void) { FILE *fp; fopen_s(&fp, FILENAME, "rb"); if (fp == NULL) { ShowMsg(TEXT("讀取遊戲進度失敗\n無法打開文件")); return FALSE; } fread(&game, sizeof(GAME), 1, fp); fread(bMap, sizeof(bMap), 1, fp); fclose(fp);
if (bGameOver) { bGameOver = FALSE; StartTimer(); }
UpdateGame(); return TRUE; }
void MoveDiamond(int iDirection) { BOOL bRecreate = FALSE; POINT ptOld = game.ptDiamond;
switch (iDirection) { case LEFT: if (game.ptDiamond.x > 0) game.ptDiamond.x--; break; case RIGHT: if (game.ptDiamond.x + 1 < DIAMOND_HORZ_NUM) game.ptDiamond.x++; break; case DOWN: if (game.ptDiamond.y + 3 < DIAMOND_VERT_NUM) game.ptDiamond.y++; else bRecreate = TRUE; } if (!bRecreate) { if (CheckCrash()) { game.ptDiamond = ptOld; if (iDirection == DOWN) bRecreate = TRUE; // 只有當移動方向向下時才重新創建方塊 } } if (bRecreate) { ReleaseDiamond(); RemoveDiamonds(); CreateDiamond(); } UpdateGame(); }
// 釋放寶石模型 void ReleaseDiamond(void) { BYTE bData; BYTE bShape = game.bDiamond; int i; WORD wColor = game.wColor; for (i = 0; i < 3; i++) { bData = 0x80; // 最高位表示是否有寶石 bData |= (bShape & 0x03) << 4; bData |= wColor & 0x0f; bMap[game.ptDiamond.y + i][game.ptDiamond.x] = bData; bShape >>= 2; wColor >>= 4; } }
// 消除相同顏色的寶石 BOOL RemoveDiamonds(void) { BOOL bRemoved; BYTE bShape; int x, y, m, n; int score_unit = SCORE; do { // 尋找可消除的寶石 bRemoved = FALSE; for (y = 0; y < DIAMOND_VERT_NUM; y++) { for (x = 0; x < DIAMOND_HORZ_NUM; x++) { if (!(bMap[y][x] & 0x80)) continue; // 如果沒有寶石則跳過 // 向右 bShape = bMap[y][x] & 0xb0; // 當前方塊的形狀 n = 1; // 找到的相同方塊個數(包括本身) for (m = 1; x + m < DIAMOND_HORZ_NUM; m++) { // 如果有寶石且形狀相同 if ((bMap[y][x + m] & 0xb0) == bShape) n++; else break; // 形狀不同時立即退出 } if (n >= 3) { // 找到3個及以上相同的寶石時可消除 bRemoved = TRUE; game.uScore += score_unit; for (m = 0; m < n; m++) bMap[y][x + m] |= 0x40; // 標記刪除 } // 向下 n = 1; for (m = 1; y + m < DIAMOND_VERT_NUM; m++) { if ((bMap[y + m][x] & 0xb0) == bShape) n++; else break; } if (n >= 3) { bRemoved = TRUE; game.uScore += score_unit; for (m = 0; m < n; m++) bMap[y + m][x] |= 0x40; } // 向右下 n = 1; for (m = 1; x + m < DIAMOND_HORZ_NUM && y + m < DIAMOND_VERT_NUM; m++) { if ((bMap[y + m][x + m] & 0xb0) == bShape) n++; else break; } if (n >= 3) { bRemoved = TRUE; game.uScore += score_unit; for (m = 0; m < n; m++) bMap[y + m][x + m] |= 0x40; } // 向左下 n = 1; for (m = 1; x - m >= 0 && y + m < DIAMOND_VERT_NUM; m++) { if ((bMap[y + m][x - m] & 0xb0) == bShape) n++; else break; } if (n >= 3) { bRemoved = TRUE; game.uScore += score_unit; for (m = 0; m < n; m++) bMap[y + m][x - m] |= 0x40; } } } if (bRemoved) { score_unit *= 1.5; for (x = 0; x < DIAMOND_HORZ_NUM; x++) { // 清除加了標記的寶石 for (y = 0; y < DIAMOND_VERT_NUM; y++) { if (bMap[y][x] & 0x40) bMap[y][x] = 0; } // 消除騰出的空位 m = -1; // 空白區域開始處 for (y = DIAMOND_VERT_NUM - 1; y >= 0; y--) { if (bMap[y][x] & 0x80) { if (m != -1) { // 移動寶石 while (y >= 0 && (bMap[y][x] & 0x80)) { bMap[m][x] = bMap[y][x]; bMap[y][x] = 0; m--; y--; } y = m + 1; m = -1; } } else { if (m == -1) m = y; } } } } } while (bRemoved); // 只要有寶石被消去,就必須重新檢查 }
void RestartGame(void) { if (bGameOver) bGameOver = FALSE; else KillTimer(hwndMain, IDT_TIMER1); ZeroMemory(bMap, sizeof(bMap)); // 刪除所有之前的寶石 InitGame(); UpdateGame(); }
BOOL SaveGame(void) { FILE *fp; fopen_s(&fp, FILENAME, "wb"); if (fp == NULL) { ShowMsg(TEXT("保存遊戲進度失敗\n無法打開文件")); return FALSE; } fwrite(&game, sizeof(GAME), 1, fp); fwrite(bMap, sizeof(bMap), 1, fp); fclose(fp); return TRUE; }
void ShowMsg(LPTSTR lpStr) { lpMsg = lpStr; UpdateGame(); SetTimer(hwndMain, IDT_TIMER2, 3000, NULL); }
void StartTimer(void) { SetTimer(hwndMain, IDT_TIMER1, 800, NULL); }
void UpdateGame(void) { // 重繪整個窗口 InvalidateRect(hwndMain, NULL, TRUE); }
|