您的位置:首页 > 其它

Windows游戏设计(一)- 拼图游戏 - 使用Win32 SDK

2012-11-15 00:20 627 查看

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 程序,我已经尽力做好,暂时到此为止~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐