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 程序,我已经尽力做好,暂时到此为止~~