1.画图
要做个拼图,第一步当然是把图给画出来。啥也不说,先把昨天那个显示位图的程序照搬过来:
/*----------------------------------------------------------- Bricks1.cpp -- LoadBitmap Demostration ------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Bricks1"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass (&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0 ; } hwnd = CreateWindow(szAppName, TEXT ("LoadBitmap Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap; static int cxClient, cyClient, cxSource, cySource; BITMAP bitmap; HDC hdc, hdcMem; PAINTSTRUCT ps; int x, y; HINSTANCE hInstance; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->hInstance; hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); cxSource = bitmap.bmWidth; cySource = bitmap.bmHeight; return 0; case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hBitmap); for (y = 0; y < cyClient; y += cySource) for (x = 0; x < cxClient; x += cxSource) { BitBlt(hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY); } DeleteDC(hdcMem); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: DeleteObject(hBitmap); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
发现有几个问题:
1)窗口大小并不合适 -- 窗口大小要适合图片的大小
2)窗口大小可以改变 -- 窗口大小不应该允许改变,且不应该允许最大化窗口
解决方案:
1)通过获得位图大小后调用MoveWindow设置窗口大小及窗口位置
case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->hInstance; // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); // 获取位图宽、高 cxBitmap = bitmap.bmWidth; cyBitmap = bitmap.bmHeight; // 获取程序窗口大小及客户区大小 RECT rcWindow, rcClient; GetWindowRect(hwnd, &rcWindow); GetClientRect(hwnd, &rcClient); // 非客户区的宽、高 int cxNonClient, cyNonClient; cxNonClient = rcWindow.right - rcWindow.left - (rcClient.right - rcClient.left); cyNonClient = rcWindow.bottom- rcWindow.top - (rcClient.bottom- rcClient.top); // 修改后的窗口大小 int cxWindow, cyWindow; cxWindow = cxNonClient + cxBitmap; cyWindow = cyNonClient + cyBitmap; // 显示位置(居中显示) int xScreen, yScreen; xScreen = GetSystemMetrics(SM_CXSCREEN)/2 - cxWindow/2; yScreen = GetSystemMetrics(SM_CYSCREEN)/2 - cyWindow/2; // 设置窗口位置和大小 MoveWindow(hwnd, xScreen, yScreen, cxWindow, cyWindow, TRUE); return 0;
2)修改CreateWindow中的参数,使的窗口大小不可改变,并不允许最大化窗口
hwnd = CreateWindow(szAppName, TEXT ("拼图游戏"), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
修改后运行效果:
2.拆分块
1)显示整幅图成功了,那接下来就是把它分成n*n显示了。简单起见,将n暂时固定为4.
2)然后就是随机打乱这16个图像块
3)绘制方格,分隔成16块
数据结构:
用0~15表示16个图像块,用int nGameMap[16]表示游戏盘面上16个块分别对应的图像块。
例如nGameMap[15] = 0 表示游戏盘面最右下角的块应该显示图像块0,即最左上角的图像块。
随机打乱算法采用《算法导论》中的随机洗牌算法:
void InitGameMap(int* map) { for (int i = 0; i < CELLNUM; i++) map[i] = i; srand((unsigned int)(time(0))); for (int i = CELLNUM-1; i > 0; i--) { int randIndex = rand() % i; int tmp = map[i]; map[i] = map[randIndex]; map[randIndex] = tmp; } }
绘制图像块,和分隔线:
void DrawGameMap(HDC hdc, HDC hdcMem, int* map, int width, int height) { int cxCell = width / VHNUMS; int cyCell = height/ VHNUMS; // 绘制图片块 for (int i = 0; i < VHNUMS; i++) for (int j = 0; j < VHNUMS; j++) { int nCell = map[i*VHNUMS + j]; int nRow = nCell / VHNUMS; int nCol = nCell % VHNUMS; BitBlt(hdc, i*cxCell, j*cyCell, cxCell, cyCell, hdcMem, nCol*cxCell, nRow*cyCell, SRCCOPY); } // 绘制方格,分隔各个图像块 for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, i*cxCell, 0, NULL); LineTo(hdc, i*cxCell, height); } for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, 0, i*cyCell, NULL); LineTo(hdc, height, i*cyCell); } }
运行效果:
忽视了一个问题,忘了留下一个空白方格。
解决方法:
反正图像块已经是打乱的了,就把最后一个方格设成空白的吧~,简单起见,-1表示空白…
void InitGameMap(int* map) { for (int i = 0; i < CELLNUM; i++) map[i] = i; srand((unsigned int)(time(0))); for (int i = CELLNUM-1; i > 0; i--) { int randIndex = rand() % i; int tmp = map[i]; map[i] = map[randIndex]; map[randIndex] = tmp; } // 空白方格 map[CELLNUM-1] = -1; }
void DrawGameMap(HDC hdc, HDC hdcMem, int* map, int width, int height) { int cxCell = width / VHNUMS; int cyCell = height/ VHNUMS; // 绘制图片块 for (int i = 0; i < VHNUMS; i++) for (int j = 0; j < VHNUMS; j++) { int nCell = map[i*VHNUMS + j]; if (nCell == -1) // 空白方格 { Rectangle(hdc, i*cxCell, j*cyCell, i*cxCell+cxCell, j*cyCell+cyCell); continue; } int nRow = nCell / VHNUMS; int nCol = nCell % VHNUMS; BitBlt(hdc, i*cxCell, j*cyCell, cxCell, cyCell, hdcMem, nCol*cxCell, nRow*cyCell, SRCCOPY); } // 绘制方格,分隔各个图像块 for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, i*cxCell, 0, NULL); LineTo(hdc, i*cxCell, height); } for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, 0, i*cyCell, NULL); LineTo(hdc, height, i*cyCell); } }
3.鼠标事件
方法:鼠标点击一个块,若它的周围有空白块,则点击的块和空白块互换。
1)获取鼠标点的是哪个块
2)点中一个块后,若它周围有空白块,则移动
3)判断是否胜利
4)补齐空白块
int OutOfMap(int x, int y) { if (x < 0 || y < 0 || x >= VHNUMS || y >= VHNUMS) return 1; return 0; } int LButtonDownAt(int* map, int xPos, int yPos) { int index = xPos + yPos*VHNUMS; if (map[index] == -1) return -1; static int iDirect[4][2] = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} }; // 邻近的四个块 for (int i = 0; i < 4; i++) { int xNew = xPos + iDirect[i][0]; int yNew = yPos + iDirect[i][1]; int newIndex = xNew + yNew*VHNUMS; // 没有出界,且是空白块,则移动 if (!OutOfMap(xNew, yNew) && map[newIndex] == -1) { int tmp = map[index]; map[index] = map[newIndex]; map[newIndex] = tmp; return newIndex; } } return -1; } int fIsWin(int* map) { int iGone = -1; for (int i = 0; i < CELLNUM; i++) { if (map[i] == -1) { iGone = i; } if (map[i] != -1 && map[i] != i) return 0; } map[iGone] = iGone; return 1; }
case WM_LBUTTONDOWN: if (iWin) return 0; xCell = cxBitmap / VHNUMS; yCell = cyBitmap / VHNUMS; xPos = LOWORD(lParam) / xCell; yPos = HIWORD(lParam) / yCell; index = LButtonDownAt(nGameMap, xPos, yPos); if (index != -1) { rcCur.left = xPos*xCell; rcCur.right = xPos*xCell + xCell; rcCur.top = yPos*yCell; rcCur.bottom= yPos*yCell + yCell; xPos = index % VHNUMS; yPos = index / VHNUMS; rcNew.left = xPos*xCell; rcNew.right = xPos*xCell + xCell; rcNew.top = yPos*yCell; rcNew.bottom= yPos*yCell + yCell; InvalidateRect(hwnd, &rcCur, FALSE); InvalidateRect(hwnd, &rcNew, FALSE); iWin = fIsWin(nGameMap); if (iWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); } } return 0;
效果:(为了玩起来简单些,VHNUM改成3 了。。)
发现有时是拼不起来的。。。
比如:
4.键盘消息
case WM_KEYDOWN: if (iWin) return 0; switch (wParam) { case VK_LEFT: MoveLeft(nGameMap); break; case VK_RIGHT: MoveRight(nGameMap); break; case VK_UP: MoveUp(nGameMap); break; case VK_DOWN: MoveDown(nGameMap); break; default: break; } GetClientRect(hwnd, &rcClient); InvalidateRect(hwnd, &rcClient, FALSE); iWin = fIsWin(nGameMap); if (iWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); } return 0;
int FindBlack(int* map) { for (int i = 0; i < CELLNUM; i++) if (map[i] == -1) return i; return -1; } void MoveRight(int* map) { int index = FindBlack(map); if (index % VHNUMS == 0) // 最左边的块 return; int tmp = map[index]; map[index] = map[index-1]; map[index-1] = tmp; } void MoveLeft(int* map) { int index = FindBlack(map); if (index % VHNUMS == VHNUMS-1) // 最右边的块 return; int tmp = map[index]; map[index] = map[index+1]; map[index+1] = tmp; } void MoveUp(int* map) { int index = FindBlack(map); if (index >= CELLNUM-VHNUMS) // 最下边的块 return; int tmp = map[index]; map[index] = map[index+VHNUMS]; map[index+VHNUMS] = tmp; } void MoveDown(int* map) { int index = FindBlack(map); if (index < VHNUMS) // 最上边的块 return; int tmp = map[index]; map[index] = map[index-VHNUMS]; map[index-VHNUMS] = tmp; }
5.改进随机函数
方法为开始后去掉最右下角一个块,然后随机移动若干步,这样肯定能够经过这若干步的逆步骤还原:
void InitGameMap(int* map) { for (int i = 0; i < CELLNUM; i++) map[i] = i; map[CELLNUM-1] = -1; srand((unsigned int)(time(0))); for (int i = 0; i < 1000; i++) { int randNum = rand() % 4; switch (randNum) { case 0: MoveLeft(map); break; case 1: MoveRight(map); break; case 2: MoveUp(map); break; case 3: MoveDown(map); break; default: break; } } }
现在既能保证拼起来,又能保证基本功能的实现。
6.整理优化
1)将不变量定义成static变量,如hdcMem,程序开始运行时创建,结束时撤销,提高效率
2)用一个变量表示空白块,以免每次寻找
3)整理函数名称、优化函数效率
新的源代码如下:
/*----------------------------------------------------------- file: PinTu.cpp -- 实现一个拼图游戏 author: guzhoudiaoke@126.com date: 2012-11-14 ------------------------------------------------------------*/ #include <windows.h> #include <stdlib.h> #include <time.h> #include "resource.h" const int VHNUMS = 3; const int CELLNUM = VHNUMS*VHNUMS; int nGameMap[CELLNUM]; // 保存游戏盘面 int nBlack; // 保存空白块位置 BOOL bIsWin; // 是否已经获胜 int cxBitmap, cyBitmap; // bmp的宽、高 int cxCell, cyCell; // 每个块的宽、高 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("PinTu"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass (&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0 ; } hwnd = CreateWindow(szAppName, TEXT ("拼图游戏"), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } void SetWindowSizeAndPos(HWND hwnd, int width, int height) { // 获取程序窗口大小及客户区大小 RECT rcWindow, rcClient; GetWindowRect(hwnd, &rcWindow); GetClientRect(hwnd, &rcClient); // 非客户区的宽、高 int cxNonClient, cyNonClient; cxNonClient = rcWindow.right - rcWindow.left - (rcClient.right - rcClient.left); cyNonClient = rcWindow.bottom- rcWindow.top - (rcClient.bottom- rcClient.top); // 修改后的窗口大小 int cxWindow, cyWindow; cxWindow = cxNonClient + width; cyWindow = cyNonClient + height; // 显示位置(居中显示) int xScreen, yScreen; xScreen = GetSystemMetrics(SM_CXSCREEN)/2 - cxWindow/2; yScreen = GetSystemMetrics(SM_CYSCREEN)/2 - cyWindow/2; // 设置窗口位置和大小 MoveWindow(hwnd, xScreen, yScreen, cxWindow, cyWindow, TRUE); } // 交换游戏盘面上的两个位置,即实现移动 void Swap(int indexa, int indexb) { int tmp = nGameMap[indexa]; nGameMap[indexa] = nGameMap[indexb]; nGameMap[indexb] = tmp; } void MoveRight() { if (nBlack % VHNUMS == 0) // 最左边的块 return; // 移动块 Swap(nBlack-1, nBlack); nBlack = nBlack-1; } void MoveLeft() { if (nBlack % VHNUMS == VHNUMS-1) // 最右边的块 return; // 移动块 Swap(nBlack+1, nBlack); nBlack = nBlack+1; } void MoveUp() { if (nBlack >= CELLNUM-VHNUMS) // 最下边的块 return; // 移动块 Swap(nBlack+VHNUMS, nBlack); nBlack = nBlack+VHNUMS; } void MoveDown() { if (nBlack < VHNUMS) // 最上边的块 return; // 移动块 Swap(nBlack-VHNUMS, nBlack); nBlack = nBlack-VHNUMS; } // 初始化游戏 void InitGameMap() { for (int i = 0; i < CELLNUM; i++) nGameMap[i] = i; nBlack = CELLNUM-1; nGameMap[nBlack] = -1; srand((unsigned int)(time(0))); for (int i = 0; i < 1000; i++) { int randNum = rand() % 4; switch (randNum) { case 0: MoveLeft(); break; case 1: MoveRight(); break; case 2: MoveUp(); break; case 3: MoveDown(); break; default: break; } } } // 绘制游戏盘面 void DrawGameMap(HDC hdc, HDC hdcMem) { // 绘制图片块 for (int i = 0; i < VHNUMS; i++) for (int j = 0; j < VHNUMS; j++) { int nCell = nGameMap[j*VHNUMS + i]; if (nCell == -1) // 空白方格 { Rectangle(hdc, i*cxCell, j*cyCell, i*cxCell+cxCell, j*cyCell+cyCell); continue; } int nRow = nCell / VHNUMS; int nCol = nCell % VHNUMS; BitBlt(hdc, i*cxCell, j*cyCell, cxCell, cyCell, hdcMem, nCol*cxCell, nRow*cyCell, SRCCOPY); } // 绘制方格,分隔各个图像块 for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, i*cxCell, 0, NULL); LineTo(hdc, i*cxCell, cyBitmap); } for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, 0, i*cyCell, NULL); LineTo(hdc, cxBitmap, i*cyCell); } } // 检查是否获胜,若获胜,则将空白块补齐 int CheckIsWin() { for (int i = 0; i < CELLNUM-1; i++) { if (nGameMap[i] != i) return 0; } bIsWin = TRUE; nGameMap[CELLNUM-1] = CELLNUM-1; return 1; } // 重绘序号为index的块 void RedrawRect(HWND hwnd, int index) { RECT rcCur; int xPos, yPos; xPos = index % VHNUMS; yPos = index / VHNUMS; // 设置当前块RECT rcCur.left = xPos*cxCell; rcCur.right = rcCur.left + cxCell; rcCur.top = yPos*cyCell; rcCur.bottom= rcCur.top + cyCell; // 重绘这两个块 InvalidateRect(hwnd, &rcCur, FALSE); } // 鼠标左键点击事件 void WMLbuttonDown(HWND hwnd, LPARAM lParam, WPARAM) { int xPos, yPos, index, nBlackSave; // 玩家已经获胜,则不响应消息 if (bIsWin) { return; } // 获取鼠标点击的块的行、列 xPos = LOWORD(lParam) / cxCell; yPos = HIWORD(lParam) / cyCell; index = xPos + yPos*VHNUMS; if (nGameMap[index] == -1) // 点中了空白块 return; // 点中的块不在空白块的周围则返回,因为只有点中了与空白块相邻的块,才移动 if (index != nBlack-1 && index != nBlack+1 && index != nBlack+VHNUMS && index != nBlack-VHNUMS) return; // 移动块 Swap(index, nBlack); nBlackSave = nBlack; nBlack = index; // 重绘这两个块 RedrawRect(hwnd, nBlackSave); RedrawRect(hwnd, nBlack); // 检查是否获胜,若获胜,则将空白块补齐 CheckIsWin(); // 若获胜,则显示消息框,提示已经获胜 if (bIsWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); } } void WMKeyDown(HWND hwnd, LPARAM lParam, WPARAM wParam) { int nBlackSave = nBlack; // 玩家已经获胜,则不响应消息 if (bIsWin) return; switch (wParam) { case VK_LEFT: MoveLeft(); break; case VK_RIGHT: MoveRight();break; case VK_UP: MoveUp(); break; case VK_DOWN: MoveDown(); break; default: break; } // 若发生了移动,重绘这两个块 if (nBlackSave != nBlack) { RedrawRect(hwnd, nBlackSave); RedrawRect(hwnd, nBlack); } // 检查是否获胜,若获胜,则将空白块补齐 CheckIsWin(); if (bIsWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap; BITMAP bitmap; HDC hdc, hdcMem; PAINTSTRUCT ps; HINSTANCE hInstance; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->hInstance; // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); // 获取位图宽、高 cxBitmap = bitmap.bmWidth; cyBitmap = bitmap.bmHeight; // 每个块的宽、高 cxCell = cxBitmap / VHNUMS; cyCell = cyBitmap / VHNUMS; // 设置窗口大小和位置 SetWindowSizeAndPos(hwnd, cxBitmap, cyBitmap); // 初始化游戏 InitGameMap(); return 0; case WM_LBUTTONDOWN: WMLbuttonDown(hwnd, lParam, wParam); return 0; case WM_KEYDOWN: WMKeyDown(hwnd, lParam, wParam); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hBitmap); DrawGameMap(hdc, hdcMem); DeleteDC(hdcMem); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: DeleteObject(hBitmap); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
7.功能完善
1)对照图片的显示
2)时间、步数统计
3)时间、步数限制
要显示对照图片,则应先扩大客户区大小:
cxWindow = cxNonClient + cxBitmap*2 + SEPWIDTH; cyWindow = cyNonClient + cyBitmap + PROCESSBARWIDTH*2 + SEPWIDTH*3;
然后绘图:
// 绘制参考图像 BitBlt(hdc, cxBitmap+SEPWIDTH, 0, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY);
添加进度条,规定最多可用时间、最多可移动步数,超过则判输。
添加进度条,首先再次扩大客户区,在底下显示两个进度条。
键盘、鼠标移动一次,剩余移动次数减一:
InvalidateRect(hwnd, &rcLeftMoveBar, FALSE);
用上面的代码要求重绘,但发现没有达到预定的目的。后来想到,由于进度条是由长变短的,所以重绘剩余的进度条后,原先长的那块冰没有去掉,所以看上去没有重绘。所以需要先用白色重绘两个进度条区域:
// 绘制剩余时间和剩余移动数目进度条 HBRUSH hBrush = CreateSolidBrush(RGB(0, 128, 255)); HBRUSH hBrushWhite = CreateSolidBrush(RGB(255, 255, 255)); FillRect(hdc, &rcProcessBar, hBrushWhite); FillRect(hdc, &rcLeftTimeBar, hBrush); FillRect(hdc, &rcLeftMoveBar, hBrush); DeleteObject(hBrushWhite); DeleteObject(hBrush);
添加WM_TIMER,每过一秒,剩余时间减一:
SetTimer(hwnd, ID_TIMER, 100, NULL);
case WM_TIMER: if (bIsWin || bIsLose) return 0; // 剩余时间减一 iTimeLeft--; rcLeftTimeBar.right -= iTimeSegLen; if (rcLeftTimeBar.right < iTimeSegLen) rcLeftTimeBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); if (iTimeLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,时间到了!"), TEXT("拼图"), MB_ICONINFORMATION); } return 0;
使进度条颜色改变,起初为蓝色,越来越变成红色,需要修改画刷颜色:
HBRUSH hBrushTime = CreateSolidBrush(RGB(255-255*iTimeLeft/TOTALTIME, 128*iTimeLeft/TOTALTIME, 255*iTimeLeft/TOTALTIME)); HBRUSH hBrushMove = CreateSolidBrush(RGB(255-255*iMoveLeft/TOTALMOVE, 128*iMoveLeft/TOTALMOVE, 255*iMoveLeft/TOTALMOVE));
最终代码:
/*----------------------------------------------------------- file: PinTu.cpp -- 实现一个拼图游戏 author: guzhoudiaoke@126.com date: 2012-11-14 ------------------------------------------------------------*/ #include <windows.h> #include <stdlib.h> #include <time.h> #include "resource.h" const int VHNUMS = 3; // 每行、列方格数 const int CELLNUM = VHNUMS*VHNUMS; const int SEPWIDTH = 8; // 中间分隔的宽度 const int PROCESSBARWIDTH = 10; // 进度条的宽度 const int TOTALTIME = CELLNUM*30; // 总共可用的时间秒数 const int TOTALMOVE = CELLNUM*8; // 总共可用的移动次数 const int ID_TIMER = 1; // 计时器ID int nGameMap[CELLNUM]; // 保存游戏盘面 int nBlack; // 保存空白块位置 BOOL bIsWin; // 是否已经获胜 BOOL bIsLose; // 是否已输 int cxBitmap, cyBitmap; // bmp的宽、高 int cxCell, cyCell; // 每个块的宽、高 int iTimeLeft, iMoveLeft; // 剩余时间、移动次数 RECT rcLeftTimeBar; // 剩余时间进度条 RECT rcLeftMoveBar; // 剩余移动次数进度条 RECT rcProcessBar; // 进度条所在区域 int iTimeSegLen; // 时间进度条单位长度 int iMoveSegLen; // 剩余移动次数进度条单位长度 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("PinTu"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass (&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0 ; } hwnd = CreateWindow(szAppName, TEXT ("拼图游戏"), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } void SetWindowSizeAndPos(HWND hwnd) { // 获取程序窗口大小及客户区大小 RECT rcWindow, rcClient; GetWindowRect(hwnd, &rcWindow); GetClientRect(hwnd, &rcClient); // 非客户区的宽、高 int cxNonClient, cyNonClient; cxNonClient = rcWindow.right - rcWindow.left - (rcClient.right - rcClient.left); cyNonClient = rcWindow.bottom- rcWindow.top - (rcClient.bottom- rcClient.top); // 修改后的窗口大小 int cxWindow, cyWindow; cxWindow = cxNonClient + cxBitmap*2 + SEPWIDTH; cyWindow = cyNonClient + cyBitmap + PROCESSBARWIDTH*2 + SEPWIDTH*3; // 显示位置(居中显示) int xScreen, yScreen; xScreen = GetSystemMetrics(SM_CXSCREEN)/2 - cxWindow/2; yScreen = GetSystemMetrics(SM_CYSCREEN)/2 - cyWindow/2; // 设置窗口位置和大小 MoveWindow(hwnd, xScreen, yScreen, cxWindow, cyWindow, TRUE); } // 交换游戏盘面上的两个位置,即实现移动 void Swap(int indexa, int indexb) { int tmp = nGameMap[indexa]; nGameMap[indexa] = nGameMap[indexb]; nGameMap[indexb] = tmp; } void MoveRight() { if (nBlack % VHNUMS == 0) // 最左边的块 return; // 移动块 Swap(nBlack-1, nBlack); nBlack = nBlack-1; } void MoveLeft() { if (nBlack % VHNUMS == VHNUMS-1) // 最右边的块 return; // 移动块 Swap(nBlack+1, nBlack); nBlack = nBlack+1; } void MoveUp() { if (nBlack >= CELLNUM-VHNUMS) // 最下边的块 return; // 移动块 Swap(nBlack+VHNUMS, nBlack); nBlack = nBlack+VHNUMS; } void MoveDown() { if (nBlack < VHNUMS) // 最上边的块 return; // 移动块 Swap(nBlack-VHNUMS, nBlack); nBlack = nBlack-VHNUMS; } // 初始化游戏 void InitGameMap() { for (int i = 0; i < CELLNUM; i++) nGameMap[i] = i; nBlack = CELLNUM-1; nGameMap[nBlack] = -1; iTimeLeft = TOTALTIME; iMoveLeft = TOTALMOVE; iTimeSegLen = (cxBitmap*2 + SEPWIDTH) / TOTALTIME + 1; iMoveSegLen = (cxBitmap*2 + SEPWIDTH) / TOTALMOVE + 1; rcLeftTimeBar.left = 0; rcLeftTimeBar.right = cxBitmap*2 + SEPWIDTH; rcLeftTimeBar.top = cyBitmap + SEPWIDTH; rcLeftTimeBar.bottom= rcLeftTimeBar.top + PROCESSBARWIDTH; rcLeftMoveBar.left = 0; rcLeftMoveBar.right = cxBitmap*2 + SEPWIDTH; rcLeftMoveBar.top = rcLeftTimeBar.bottom + SEPWIDTH; rcLeftMoveBar.bottom= rcLeftMoveBar.top + PROCESSBARWIDTH; rcProcessBar.left = 0; rcProcessBar.right = cxBitmap*2 + SEPWIDTH; rcProcessBar.top = cyBitmap; rcProcessBar.bottom = cyBitmap + PROCESSBARWIDTH*2 + SEPWIDTH*3; srand((unsigned int)(time(0))); for (int i = 0; i < 1000; i++) { int randNum = rand() % 4; switch (randNum) { case 0: MoveLeft(); break; case 1: MoveRight(); break; case 2: MoveUp(); break; case 3: MoveDown(); break; default: break; } } } // 绘制游戏盘面 void DrawGameMap(HDC hdc, HDC hdcMem) { // 绘制图片块 for (int i = 0; i < VHNUMS; i++) for (int j = 0; j < VHNUMS; j++) { int nCell = nGameMap[j*VHNUMS + i]; if (nCell == -1) // 空白方格 { Rectangle(hdc, i*cxCell, j*cyCell, i*cxCell+cxCell, j*cyCell+cyCell); continue; } int nRow = nCell / VHNUMS; int nCol = nCell % VHNUMS; BitBlt(hdc, i*cxCell, j*cyCell, cxCell, cyCell, hdcMem, nCol*cxCell, nRow*cyCell, SRCCOPY); } // 绘制参考图像 BitBlt(hdc, cxBitmap+SEPWIDTH, 0, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY); // 绘制方格,分隔各个图像块 for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, i*cxCell, 0, NULL); LineTo(hdc, i*cxCell, cyBitmap); } for (int i = 0; i <= VHNUMS; i++) { MoveToEx(hdc, 0, i*cyCell, NULL); LineTo(hdc, cxBitmap, i*cyCell); } // 绘制剩余时间和剩余移动数目进度条 HBRUSH hBrushTime = CreateSolidBrush(RGB(255-255*iTimeLeft/TOTALTIME, 128*iTimeLeft/TOTALTIME, 255*iTimeLeft/TOTALTIME)); HBRUSH hBrushMove = CreateSolidBrush(RGB(255-255*iMoveLeft/TOTALMOVE, 128*iMoveLeft/TOTALMOVE, 255*iMoveLeft/TOTALMOVE)); HBRUSH hBrushWhite = CreateSolidBrush(RGB(255, 255, 255)); FillRect(hdc, &rcProcessBar, hBrushWhite); FillRect(hdc, &rcLeftTimeBar, hBrushTime); FillRect(hdc, &rcLeftMoveBar, hBrushMove); DeleteObject(hBrushWhite); DeleteObject(hBrushTime); DeleteObject(hBrushMove); } // 检查是否获胜,若获胜,则将空白块补齐 int CheckIsWin() { for (int i = 0; i < CELLNUM-1; i++) { if (nGameMap[i] != i) return 0; } bIsWin = TRUE; nGameMap[CELLNUM-1] = CELLNUM-1; return 1; } // 重绘序号为index的块 void RedrawRect(HWND hwnd, int index) { RECT rcCur; int xPos, yPos; xPos = index % VHNUMS; yPos = index / VHNUMS; // 设置当前块RECT rcCur.left = xPos*cxCell; rcCur.right = rcCur.left + cxCell; rcCur.top = yPos*cyCell; rcCur.bottom= rcCur.top + cyCell; // 重绘这个块 InvalidateRect(hwnd, &rcCur, FALSE); } // 鼠标左键点击事件 void WMLbuttonDown(HWND hwnd, LPARAM lParam, WPARAM) { int xPos, yPos, index, nBlackSave; // 玩家已经获胜,则不响应消息 if (bIsWin || bIsLose) { return; } // 获取鼠标点击的块的行、列 xPos = LOWORD(lParam) / cxCell; yPos = HIWORD(lParam) / cyCell; index = xPos + yPos*VHNUMS; if (nGameMap[index] == -1) // 点中了空白块 return; // 点中的块不在空白块的周围则返回,因为只有点中了与空白块相邻的块,才移动 if (index != nBlack-1 && index != nBlack+1 && index != nBlack+VHNUMS && index != nBlack-VHNUMS) return; // 移动块 Swap(index, nBlack); nBlackSave = nBlack; nBlack = index; // 重绘这两个块 RedrawRect(hwnd, nBlackSave); RedrawRect(hwnd, nBlack); // 剩余移动次数减少一次 iMoveLeft--; rcLeftMoveBar.right -= iMoveSegLen; if (rcLeftMoveBar.right < iMoveSegLen) rcLeftMoveBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); // 检查是否获胜,若获胜,则将空白块补齐 CheckIsWin(); // 若获胜,则显示消息框,提示已经获胜 if (bIsWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); } // 检查是否超过移动次数 if (iMoveLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,规定的移动次数到了!"), TEXT("拼图"), MB_ICONINFORMATION); } } void WMKeyDown(HWND hwnd, LPARAM lParam, WPARAM wParam) { int nBlackSave = nBlack; // 玩家已经获胜,则不响应消息 if (bIsWin || bIsLose) return; switch (wParam) { case VK_LEFT: MoveLeft(); break; case VK_RIGHT: MoveRight();break; case VK_UP: MoveUp(); break; case VK_DOWN: MoveDown(); break; default: break; } // 若发生了移动,重绘这两个块 if (nBlackSave != nBlack) { RedrawRect(hwnd, nBlackSave); RedrawRect(hwnd, nBlack); // 剩余移动次数减少一次 iMoveLeft--; rcLeftMoveBar.right -= iMoveSegLen; if (rcLeftMoveBar.right < iMoveSegLen) rcLeftMoveBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); // 检查是否获胜,若获胜,则将空白块补齐 CheckIsWin(); if (bIsWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); } // 检查是否超过移动次数 if (iMoveLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,规定的移动次数到了!"), TEXT("拼图"), MB_ICONINFORMATION); } } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap; static int iTickCount; BITMAP bitmap; HDC hdc, hdcMem; PAINTSTRUCT ps; HINSTANCE hInstance; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->hInstance; // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); // 获取位图宽、高 cxBitmap = bitmap.bmWidth; cyBitmap = bitmap.bmHeight; // 每个块的宽、高 cxCell = cxBitmap / VHNUMS; cyCell = cyBitmap / VHNUMS; // 设置窗口大小和位置 SetWindowSizeAndPos(hwnd); // 初始化游戏 InitGameMap(); SetTimer(hwnd, ID_TIMER, 100, NULL); return 0; case WM_LBUTTONDOWN: WMLbuttonDown(hwnd, lParam, wParam); return 0; case WM_KEYDOWN: WMKeyDown(hwnd, lParam, wParam); return 0; case WM_TIMER: if (bIsWin || bIsLose) return 0; // 剩余时间减一 iTimeLeft--; rcLeftTimeBar.right -= iTimeSegLen; if (rcLeftTimeBar.right < iTimeSegLen) rcLeftTimeBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); if (iTimeLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,时间到了!"), TEXT("拼图"), MB_ICONINFORMATION); } return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hBitmap); DrawGameMap(hdc, hdcMem); DeleteDC(hdcMem); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: DeleteObject(hBitmap); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
8.进一步的完善
功能上已经基本完成了。
若再要改进,就是用户自主控制方面了:
1)菜单选择使用哪个图片
2)菜单选择难度(如3*3,4*4,5*5)
3)可用的时间、移动步数:
思路1:挑战模式,玩家可以设定可用时间、移动步数
思路2:难度模式,可用时间与移动步数与难度相关,改进相关公式
思路3:过关模式,可用时间、移动步数逐步减少
思路4:英雄榜模式,用剩余时间和剩余步数计算一个分数,并实现记录用户数据
/*----------------------------------------------------------- file: PinTu.cpp -- 实现一个拼图游戏 author: guzhoudiaoke@126.com date: 2012-11-14 ------------------------------------------------------------*/ #include <windows.h> #include <stdlib.h> #include <time.h> #include "resource.h" const int VHNUMS = 5; // 每行列最大方格数 const int CELLNUM = VHNUMS*VHNUMS; // 方格总数 const int SEPWIDTH = 8; // 中间分隔的宽度 const int PROCESSBARWIDTH = 10; // 进度条的宽度 const int TOTALTIME = CELLNUM*30; // 总共可用的时间秒数 const int TOTALMOVE = CELLNUM*8; // 总共可用的移动次数 const int ID_TIMER = 1; // 计时器ID HINSTANCE hInstance; HBITMAP hBitmap; // 设备相关位图句柄 BITMAP bitmap; int nGameMap[CELLNUM]; // 保存游戏盘面 int nBlack; // 保存空白块位置 int nVHnums; // 每行每列方格数 int nCellNums; BOOL bIsWin; // 是否已经获胜 BOOL bIsLose; // 是否已输 int cxBitmap, cyBitmap; // bmp的宽、高 int cxCell, cyCell; // 每个块的宽、高 int iTimeLeft, iMoveLeft; // 剩余时间、移动次数 RECT rcLeftTimeBar; // 剩余时间进度条 RECT rcLeftMoveBar; // 剩余移动次数进度条 RECT rcProcessBar; // 进度条所在区域 int iTimeSegLen; // 时间进度条单位长度 int iMoveSegLen; // 剩余移动次数进度条单位长度 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void SetMenuPic(HWND); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("PinTu"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1); wndclass.lpszClassName = szAppName; if (!RegisterClass (&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0 ; } hwnd = CreateWindow(szAppName, TEXT ("拼图游戏"), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } void SetWindowSizeAndPos(HWND hwnd) { // 获取程序窗口大小及客户区大小 RECT rcWindow, rcClient; GetWindowRect(hwnd, &rcWindow); GetClientRect(hwnd, &rcClient); // 非客户区的宽、高 int cxNonClient, cyNonClient; cxNonClient = rcWindow.right - rcWindow.left - (rcClient.right - rcClient.left); cyNonClient = rcWindow.bottom- rcWindow.top - (rcClient.bottom- rcClient.top); // 修改后的窗口大小 int cxWindow, cyWindow; cxWindow = cxNonClient + cxBitmap*2 + SEPWIDTH; cyWindow = cyNonClient + cyBitmap + PROCESSBARWIDTH*2 + SEPWIDTH*3; // 显示位置(居中显示) int xScreen, yScreen; xScreen = GetSystemMetrics(SM_CXSCREEN)/2 - cxWindow/2; yScreen = GetSystemMetrics(SM_CYSCREEN)/2 - cyWindow/2; // 设置窗口位置和大小 MoveWindow(hwnd, xScreen, yScreen, cxWindow, cyWindow, TRUE); } // 交换游戏盘面上的两个位置,即实现移动 void Swap(int indexa, int indexb) { int tmp = nGameMap[indexa]; nGameMap[indexa] = nGameMap[indexb]; nGameMap[indexb] = tmp; } void MoveRight() { if (nBlack % nVHnums == 0) // 最左边的块 return; // 移动块 Swap(nBlack-1, nBlack); nBlack = nBlack-1; } void MoveLeft() { if (nBlack % nVHnums == nVHnums-1) // 最右边的块 return; // 移动块 Swap(nBlack+1, nBlack); nBlack = nBlack+1; } void MoveUp() { if (nBlack >= nCellNums-nVHnums) // 最下边的块 return; // 移动块 Swap(nBlack+nVHnums, nBlack); nBlack = nBlack+nVHnums; } void MoveDown() { if (nBlack < nVHnums) // 最上边的块 return; // 移动块 Swap(nBlack-nVHnums, nBlack); nBlack = nBlack-nVHnums; } // 初始化游戏 void InitGameMap() { for (int i = 0; i < nCellNums; i++) nGameMap[i] = i; nBlack = nCellNums-1; nGameMap[nBlack] = -1; iTimeLeft = nCellNums*30; iMoveLeft = nCellNums*8; bIsLose = FALSE; bIsWin = FALSE; iTimeSegLen = (cxBitmap*2 + SEPWIDTH) / nCellNums / 30 + 1; iMoveSegLen = (cxBitmap*2 + SEPWIDTH) / nCellNums / 8 + 1; rcLeftTimeBar.left = 0; rcLeftTimeBar.right = cxBitmap*2 + SEPWIDTH; rcLeftTimeBar.top = cyBitmap + SEPWIDTH; rcLeftTimeBar.bottom= rcLeftTimeBar.top + PROCESSBARWIDTH; rcLeftMoveBar.left = 0; rcLeftMoveBar.right = cxBitmap*2 + SEPWIDTH; rcLeftMoveBar.top = rcLeftTimeBar.bottom + SEPWIDTH; rcLeftMoveBar.bottom= rcLeftMoveBar.top + PROCESSBARWIDTH; rcProcessBar.left = 0; rcProcessBar.right = cxBitmap*2 + SEPWIDTH; rcProcessBar.top = cyBitmap; rcProcessBar.bottom = cyBitmap + PROCESSBARWIDTH*2 + SEPWIDTH*3; srand((unsigned int)(time(0))); for (int i = 0; i < 1000; i++) { int randNum = rand() % 4; switch (randNum) { case 0: MoveLeft(); break; case 1: MoveRight(); break; case 2: MoveUp(); break; case 3: MoveDown(); break; default: break; } } } // 绘制游戏盘面 void DrawGameMap(HDC hdc, HDC hdcMem) { // 绘制图片块 for (int i = 0; i < nVHnums; i++) for (int j = 0; j < nVHnums; j++) { int nCell = nGameMap[j*nVHnums + i]; if (nCell == -1) // 空白方格 { Rectangle(hdc, i*cxCell, j*cyCell, i*cxCell+cxCell, j*cyCell+cyCell); continue; } int nRow = nCell / nVHnums; int nCol = nCell % nVHnums; BitBlt(hdc, i*cxCell, j*cyCell, cxCell, cyCell, hdcMem, nCol*cxCell, nRow*cyCell, SRCCOPY); } // 绘制参考图像 BitBlt(hdc, cxBitmap+SEPWIDTH, 0, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY); // 绘制方格,分隔各个图像块 for (int i = 0; i <= nVHnums; i++) { MoveToEx(hdc, i*cxCell, 0, NULL); LineTo(hdc, i*cxCell, cyBitmap); } for (int i = 0; i <= nVHnums; i++) { MoveToEx(hdc, 0, i*cyCell, NULL); LineTo(hdc, cxBitmap, i*cyCell); } // 绘制剩余时间和剩余移动数目进度条 HBRUSH hBrushTime = CreateSolidBrush(RGB(255-255*iTimeLeft/nCellNums/30, 128*iTimeLeft/nCellNums/30, 255*iTimeLeft/nCellNums/30)); HBRUSH hBrushMove = CreateSolidBrush(RGB(255-255*iMoveLeft/nCellNums/8, 128*iMoveLeft/nCellNums/8, 255*iMoveLeft/nCellNums/8)); HBRUSH hBrushWhite = CreateSolidBrush(RGB(255, 255, 255)); FillRect(hdc, &rcProcessBar, hBrushWhite); FillRect(hdc, &rcLeftTimeBar, hBrushTime); FillRect(hdc, &rcLeftMoveBar, hBrushMove); DeleteObject(hBrushWhite); DeleteObject(hBrushTime); DeleteObject(hBrushMove); } // 检查是否获胜,若获胜,则将空白块补齐 int CheckIsWin() { for (int i = 0; i < nCellNums-1; i++) { if (nGameMap[i] != i) return 0; } bIsWin = TRUE; nGameMap[nCellNums-1] = nCellNums-1; return 1; } // 重绘序号为index的块 void RedrawRect(HWND hwnd, int index) { RECT rcCur; int xPos, yPos; xPos = index % nVHnums; yPos = index / nVHnums; // 设置当前块RECT rcCur.left = xPos*cxCell; rcCur.right = rcCur.left + cxCell; rcCur.top = yPos*cyCell; rcCur.bottom= rcCur.top + cyCell; // 重绘这个块 InvalidateRect(hwnd, &rcCur, FALSE); } // 鼠标左键点击事件 void WMLbuttonDown(HWND hwnd, LPARAM lParam, WPARAM) { int xPos, yPos, index, nBlackSave; // 玩家已经获胜,则不响应消息 if (bIsWin || bIsLose) { return; } if (LOWORD(lParam) > cxBitmap) return; // 获取鼠标点击的块的行、列 xPos = LOWORD(lParam) / cxCell; yPos = HIWORD(lParam) / cyCell; index = xPos + yPos*nVHnums; if (nGameMap[index] == -1) // 点中了空白块 return; // 点中的块不在空白块的周围则返回,因为只有点中了与空白块相邻的块,才移动 if (index != nBlack-1 && index != nBlack+1 && index != nBlack+nVHnums && index != nBlack-nVHnums) return; // 移动块 Swap(index, nBlack); nBlackSave = nBlack; nBlack = index; // 重绘这两个块 RedrawRect(hwnd, nBlackSave); RedrawRect(hwnd, nBlack); // 剩余移动次数减少一次 iMoveLeft--; rcLeftMoveBar.right -= iMoveSegLen; if (rcLeftMoveBar.right < iMoveSegLen) rcLeftMoveBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); // 检查是否获胜,若获胜,则将空白块补齐 CheckIsWin(); // 若获胜,则显示消息框,提示已经获胜 if (bIsWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); return; } // 检查是否超过移动次数 if (iMoveLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,规定的移动次数到了!"), TEXT("拼图"), MB_ICONINFORMATION); } } void WMKeyDown(HWND hwnd, LPARAM lParam, WPARAM wParam) { int nBlackSave = nBlack; // 玩家已经获胜,则不响应消息 if (bIsWin || bIsLose) return; switch (wParam) { case VK_LEFT: MoveLeft(); break; case VK_RIGHT: MoveRight();break; case VK_UP: MoveUp(); break; case VK_DOWN: MoveDown(); break; default: break; } // 若发生了移动,重绘这两个块 if (nBlackSave != nBlack) { RedrawRect(hwnd, nBlackSave); RedrawRect(hwnd, nBlack); // 剩余移动次数减少一次 iMoveLeft--; rcLeftMoveBar.right -= iMoveSegLen; if (rcLeftMoveBar.right < iMoveSegLen) rcLeftMoveBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); // 检查是否获胜,若获胜,则将空白块补齐 CheckIsWin(); if (bIsWin) { MessageBox(hwnd, TEXT("恭喜你赢了"), TEXT("拼图"), MB_ICONINFORMATION); return; } // 检查是否超过移动次数 if (iMoveLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,规定的移动次数到了!"), TEXT("拼图"), MB_ICONINFORMATION); } } } void NewGame(HWND hwnd, LPARAM lParam, WPARAM wParam) { // 获取位图宽、高 cxBitmap = bitmap.bmWidth; cyBitmap = bitmap.bmHeight; // 每个块的宽、高 cxCell = cxBitmap / nVHnums; cyCell = cyBitmap / nVHnums; // 设置窗口大小和位置 SetWindowSizeAndPos(hwnd); // 初始化游戏 InitGameMap(); KillTimer(hwnd, ID_TIMER); SetTimer(hwnd, ID_TIMER, 100, NULL); } void WMCommand(HWND hwnd, LPARAM lParam, WPARAM wParam) { HMENU hMenu = GetMenu(hwnd); RECT rcClient; GetClientRect(hwnd, &rcClient); switch (LOWORD(wParam)) { case ID_NEWGAME: NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; case ID_EXITGAME: SendMessage(hwnd, WM_DESTROY, wParam, lParam); break; case ID_EASY3X3: nVHnums = 3; nCellNums = nVHnums*nVHnums; NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; case ID_MID4X4: nVHnums = 4; nCellNums = nVHnums*nVHnums; NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; case ID_HARD5X5: nVHnums = 5; nCellNums = nVHnums*nVHnums; NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; case ID_BAOCHAI: // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; case ID_DAIYU: // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP2)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; case ID_SHANDIAN: // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP3)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); NewGame(hwnd, lParam, wParam); InvalidateRect(hwnd, &rcClient, FALSE); break; } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int iTickCount; HDC hdc, hdcMem; PAINTSTRUCT ps; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->hInstance; nVHnums = VHNUMS; nCellNums = nVHnums*nVHnums; // 加载位图 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &bitmap); NewGame(hwnd, lParam, wParam); return 0; case WM_COMMAND: WMCommand(hwnd, lParam, wParam); return 0; case WM_LBUTTONDOWN: WMLbuttonDown(hwnd, lParam, wParam); return 0; case WM_KEYDOWN: WMKeyDown(hwnd, lParam, wParam); return 0; case WM_TIMER: if (bIsWin || bIsLose) return 0; // 剩余时间减一 iTimeLeft--; rcLeftTimeBar.right -= iTimeSegLen; if (rcLeftTimeBar.right < iTimeSegLen) rcLeftTimeBar.right = 0; InvalidateRect(hwnd, &rcProcessBar, FALSE); if (iTimeLeft == 0) { bIsLose = TRUE; MessageBox(hwnd, TEXT("不好意思,时间到了!"), TEXT("拼图"), MB_ICONINFORMATION); } return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hBitmap); DrawGameMap(hdc, hdcMem); DeleteDC(hdcMem); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: KillTimer(hwnd, ID_TIMER); DeleteObject(hBitmap); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
现在玩家能选择图像和难度了。
但:
当程序行数达到一定量时,有点乱了;
为了参数简单,定义了大量全局变量;
对Win32 程序该怎么组织还是有点迷惑;
但是,作为自己动手设计、实现的第一个Win32 SDK 程序,我已经尽力做好,暂时到此为止~~