Windows游戏设计(三)- 黑白棋游戏 - 使用Win32 SDK

注:以下程序为本人原创,写的不好,若有好的建议,望留言告知。而若能帮助一二访客,幸甚!


上回用Python 写黑白棋,后来想添加个最小最大规则搜索博弈树的算法,没能实现,于是想先用Win32 写一个,再改编成Python版的。

于是有该程序。

1.游戏界面和框架

游戏框架由打砖块游戏改编而来。

/*
 * BlackWhite: 实现一个简单的黑白棋游戏
 * 孤舟钓客(guzhoudiaoke@126.com)
 * 2012-12-01
 */


/* INCLUDES *******************************************************************************/
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include "resource.h"


/* DEFINES ********************************************************************************/
// defines for windows
#define WINDOW_CLASS_NAME		TEXT("WIN32CLASS")
#define WINDOW_WIDTH			640
#define WINDOW_HEIGHT			480

// states for game loop
#define GAME_STATE_INIT         0
#define GAME_STATE_START_LEVEL  1
#define GAME_STATE_RUN          2
#define GAME_STATE_SHUTDOWN     3
#define GAME_STATE_EXIT         4

// block defines
#define NUM_CELL_ROWS			8
#define NUM_CELL_COLUMNS		8

#define CELL_SIZE				50
#define PEACE_SIZE				48

#define BOARD_LEFT				35
#define BOARD_TOP				35
#define BOARD_RIGHT				435
#define BOARD_BOTTOM			435

// color defines
#define COLOR_WHITE				1
#define COLOR_BLACK				16
#define COLOR_NONE				0

// these read the keyboard asynchronously
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)


/* basic unsigned types *******************************************************************/
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned char  UCHAR;
typedef unsigned char  BYTE;


/* FUNCTION DECLARATION *******************************************************************/
int Game_Init(void *parms = NULL);
int Game_Shutdown(void *parms = NULL);
int Game_Main(void *parms = NULL);

void Draw_Board(void);


/* GLOBALS *********************************************************************************/
HWND		main_window_handle	= NULL;				// save the window handle
HINSTANCE	main_instance		= NULL;				// save the instance

HBITMAP		h_bitmap_board		= NULL;
BITMAP		bitmap_board;
HBITMAP		h_bitmap_black		= NULL;
BITMAP		bitmap_black;
HBITMAP		h_bitmap_white		= NULL;
BITMAP		bitmap_white;

int			cxBitmapBoard		= 0;
int			cyBitmapBoard		= 0;

int			game_state			= GAME_STATE_INIT;	// starting state
int			game_board[NUM_CELL_ROWS][NUM_CELL_COLUMNS];


/* WINDPROC ********************************************************************************/
LRESULT CALLBACK WindowProc(HWND	hwnd,
							UINT	msg,
							WPARAM	wparam,
							LPARAM	lparam)
{
	// this is the main message handler of the system
	PAINTSTRUCT	ps;
	HDC			hdc;

	switch (msg)
	{
	case WM_CREATE:
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		Draw_Board();
		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;
	}

	return DefWindowProc(hwnd, msg, wparam, lparam);
}


/* WINMAIN ********************************************************************************/
int WINAPI WinMain(HINSTANCE hinstance,
				   HINSTANCE hprevinstance,
				   LPSTR lpcmdline,
				   int ncmdshow)
{
	WNDCLASS	winclass;
	HWND		hwnd;
	MSG			msg;

	/* CS_DBLCLKS Specifies that the window should be notified of double clicks with 
	 * WM_xBUTTONDBLCLK messages
	 * CS_OWNDC标志,属于此窗口类的窗口实例都有自己的DC(称为私有DC),私有DC仅属于该窗口实例,
	 * 所以程序只需要调用一次GetDC或BeginPaint获取DC,系统就为窗口初始化一个DC,并且保存程序
	 * 对其进行的改变。ReleaseDC和EndPaint函数不再需要了,因为其他程序无法访问和改变私有DC。
	 */
	winclass.style			= CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winclass.lpfnWndProc	= WindowProc;
	winclass.cbClsExtra		= 0;
	winclass.cbWndExtra		= 0;
	winclass.hInstance		= hinstance;
	winclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	winclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	winclass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	winclass.lpszMenuName	= NULL;
	winclass.lpszClassName	= WINDOW_CLASS_NAME;

	// register the window class
	if (!RegisterClass(&winclass))
		return 0;

	// Create the window, note the use of WS_POPUP
	hwnd = CreateWindow(
		WINDOW_CLASS_NAME,			// class
		TEXT("黑白棋"),				// title
		WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
		200,						// initial x
		100,						// initial y	
		WINDOW_WIDTH,				// initial width
		WINDOW_HEIGHT,				// initial height
		NULL,						// handle to parent
		NULL,						// handle to menu
		hinstance,					// instance
		NULL);						// creation parms

	if (! hwnd)
		return 0;

	ShowWindow(hwnd, ncmdshow);
	UpdateWindow(hwnd);

	// save the window handle and instance in a global
	main_window_handle	= hwnd;
	main_instance		= hinstance;

	// perform all game console specific initialization
	Game_Init();

	// enter main event loop
	while (1)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			// test if this is a quit msg
			if (msg.message == WM_QUIT)
				break;
			
			TranslateMessage(&msg);	// translate any accelerator keys
			DispatchMessage(&msg);	// send the message to the window proc
		}

		Game_Main();	// main game processing goes here
		Sleep(30);
	}

	// shutdown game and release all resources
	Game_Shutdown();

	// return to windows like this
	return (msg.wParam);
}


void SetWindowSizeAndPos()
{
    // 获取程序窗口大小及客户区大小
    RECT rcWindow, rcClient;
    GetWindowRect(main_window_handle, &rcWindow);
    GetClientRect(main_window_handle, &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 + cxBitmapBoard;
    cyWindow = cyNonClient + cyBitmapBoard;

	// 显示位置(居中显示)
    int xScreen, yScreen;
    xScreen = GetSystemMetrics(SM_CXSCREEN)/2 - cxWindow/2;
    yScreen = GetSystemMetrics(SM_CYSCREEN)/2 - cyWindow/2;

	// 设置窗口位置和大小
    MoveWindow(main_window_handle, xScreen, yScreen, cxWindow, cyWindow, TRUE);
}


/* DRAW FUNCTION **************************************************************************/
int Draw_Rectangle(int x1, int y1, int x2, int y2, int color)
{
	// this function uses Win32 API to draw a filled rectangle
	HBRUSH		hbrush;
	HDC			hdc;
	RECT		rect;

	SetRect(&rect, x1, y1, x2, y2);

	hbrush = CreateSolidBrush(color);
	hdc = GetDC(main_window_handle);

	FillRect(hdc, &rect, hbrush);
	ReleaseDC(main_window_handle, hdc);
	DeleteObject(hbrush);

	return 1;
}

int DrawText_GUI(TCHAR *text, int x, int y, int color)
{
	HDC	hdc;
	hdc = GetDC(main_window_handle);
	
	SetTextColor(hdc, color);				// set the colors for the text up
	SetBkMode(hdc, TRANSPARENT);			// set background mode to transparent so black isn't copied
	TextOut(hdc, x, y, text, lstrlen(text));// draw the text
	ReleaseDC(main_window_handle, hdc);		// release the dc

	return 1;
}


/* GAME PROGRAMMING CONSOLE FUNCTIONS *****************************************************/
void Init_Blocks(void)
{
	// initialize the block field
	for (int row = 0; row < NUM_CELL_ROWS; row++)
		for (int col = 0; col < NUM_CELL_COLUMNS; col++)
			game_board[row][col] = COLOR_NONE;

	game_board[3][3] = COLOR_WHITE;
	game_board[4][4] = COLOR_WHITE;
	game_board[3][4] = COLOR_BLACK;
	game_board[4][3] = COLOR_BLACK;
}

void Draw_Board(void)
{
	// this function draws all the blocks in row major form
	HDC hdc, hdcMem_board, hdcMem_black, hdcMem_white;
	hdc = GetDC(main_window_handle);

	hdcMem_board = CreateCompatibleDC(hdc);
    SelectObject(hdcMem_board, h_bitmap_board);

	hdcMem_black = CreateCompatibleDC(hdc);
	SelectObject(hdcMem_black, h_bitmap_black);

	hdcMem_white = CreateCompatibleDC(hdc);
	SelectObject(hdcMem_white, h_bitmap_white);
    
	// 绘制棋盘
    BitBlt(hdc, 0, 0, cxBitmapBoard, cyBitmapBoard, hdcMem_board, 0, 0, SRCCOPY);

	//BitBlt(hdc, 0, 0, CELL_SIZE, CELL_SIZE, hdcMem_white, 0, 0, SRCCOPY);

	// 绘制棋子
	int cur_x = BOARD_LEFT;
	int cur_y = BOARD_TOP;
	for (int y = 0; y < NUM_CELL_ROWS; y++)
	{
		cur_x = BOARD_LEFT;
		for (int x = 0; x < NUM_CELL_COLUMNS; x++)
		{
			if (game_board[y][x] == COLOR_WHITE)
			{
				BitBlt(hdc, cur_x+1, cur_y+1, PEACE_SIZE, PEACE_SIZE, hdcMem_white, 0, 0, SRCCOPY);
			}
			else if (game_board[y][x] == COLOR_BLACK)
			{
				BitBlt(hdc, cur_x+1, cur_y+1, PEACE_SIZE, PEACE_SIZE, hdcMem_black, 0, 0, SRCCOPY);
			}

			cur_x += CELL_SIZE;
		}
		cur_y += CELL_SIZE;
	}

    DeleteDC(hdcMem_board);
	DeleteDC(hdcMem_white);
	DeleteDC(hdcMem_black);
}



int Game_Init(void *parms)
{
	// this function is where you do all the initialization for your game
	return 1;
}

int Game_Shutdown(void *parms)
{
	// this function is where you shutdown your game and release all resources 
	// that you allocated
	return 1;
}

int Game_Main(void *parms)
{
	// this is the workhorse of your game it will be called continuously in real-time
	// this is like main() in C all the calls for you game go here!
	TCHAR	buffer[80];

	// what state is the game in?
	if (game_state == GAME_STATE_INIT)
	{
		h_bitmap_board = LoadBitmap(main_instance, MAKEINTRESOURCE(IDB_BOARD));
		GetObject(h_bitmap_board, sizeof(BITMAP), &bitmap_board);

		h_bitmap_black = LoadBitmap(main_instance, MAKEINTRESOURCE(IDB_BLACK));
		GetObject(h_bitmap_black, sizeof(BITMAP), &bitmap_black);

		h_bitmap_white = LoadBitmap(main_instance, MAKEINTRESOURCE(IDB_WHITE));
		GetObject(h_bitmap_white, sizeof(BITMAP), &bitmap_white);


		cxBitmapBoard = bitmap_board.bmWidth;
		cyBitmapBoard = bitmap_board.bmHeight;

		SetWindowSizeAndPos();

		// transition to start level state
		game_state = GAME_STATE_START_LEVEL;
	}
	else if (game_state == GAME_STATE_START_LEVEL)
	{
		// get a new level ready to run
		Init_Blocks();				// initialize the blocks
		
		game_state = GAME_STATE_RUN;// transition to run state
	}
	else if (game_state == GAME_STATE_RUN)
	{
		// draw the info
		wsprintf(buffer, TEXT("FREAKOUT            Score %d              Level %d"), 1, 2);
		//Draw_Rectangle(8, WINDOW_HEIGHT-26, WINDOW_WIDTH, WINDOW_HEIGHT, BACKGROUND_COLOR);
		//DrawText_GUI(buffer, 8, WINDOW_HEIGHT-26, RGB(255, 255, 128));

		// check if user is trying to exit
		if (KEY_DOWN(VK_ESCAPE))
		{
			// send message to windows to exit
			PostMessage(main_window_handle, WM_DESTROY, 0, 0);

			// set exit state
			game_state = GAME_STATE_SHUTDOWN;
		}

		if (KEY_DOWN(VK_LBUTTON))
		{
			POINT point;   //POINT结构体类型,包含x、y属性
			GetCursorPos(&point);
			ScreenToClient(main_window_handle, &point);

			int row = 0, col = 0;
			if (point.x > BOARD_LEFT && point.x < BOARD_RIGHT &&
				point.y > BOARD_TOP  && point.y < BOARD_BOTTOM)
			{
				col = (point.x-BOARD_LEFT) / CELL_SIZE;
				row = (point.y-BOARD_TOP)  / CELL_SIZE;

				Draw_Board();

				wsprintf(buffer, TEXT("(%d, %d)"), row, col);
				MessageBox(main_window_handle, buffer, TEXT("HAHA"), MB_OK);
			}
		}
	}
	else if (game_state == GAME_STATE_SHUTDOWN)
	{
		// in this state shut everything down and release resources
		// switch to exit state
		game_state = GAME_STATE_EXIT;
	}

	return 1;
}



2. 游戏规则

下面目标实现一个两边都由玩家落子的程序。

是否能够落子的判断:

/* 游戏规则 ***********************************************************************************/
void Copy_Board(int dst[NUM_CELL_ROWS][NUM_CELL_COLUMNS], int src[NUM_CELL_ROWS][NUM_CELL_COLUMNS])
{
	for (int y = 0; y < NUM_CELL_ROWS; y++)
		for (int x = 0; x < NUM_CELL_COLUMNS; x++)
			dst[y][x] = src[y][x];
}

BOOL Is_On_Board(int pos_row, int pos_col)
{
	if (pos_row >= 0 && pos_row < NUM_CELL_ROWS &&
		pos_col >= 0 && pos_col < NUM_CELL_COLUMNS)
		return TRUE;
	return FALSE;
}

BOOL Is_Valid_Move(int color, int pos_row, int pos_col, vector<int> &tiles_to_flip)
{
	// 如果该位置不在棋盘上或者该位置已有棋子,则一定非法
	if (!Is_On_Board(pos_row, pos_col) || game_board[pos_row][pos_col] != COLOR_NONE)
		return FALSE;

	// 对方的颜色
	int other_color;
	if (color == COLOR_BLACK)
		other_color = COLOR_WHITE;
	else
		other_color = COLOR_BLACK;

	// 检查需要翻转的棋子
	tiles_to_flip.clear();
	for (int i = 0; i < 8; i++)
	{
		int row = pos_row + move_dir[i][1]; int col = pos_col + move_dir[i][0];
		
		while (Is_On_Board(row, col) && game_board[row][col] == other_color)
		{
			row += move_dir[i][1]; col += move_dir[i][0];
		}

		if (! Is_On_Board(row, col))
			continue;

		if (game_board[row][col] == color)
		{
			while (1)
			{
				row -= move_dir[i][1]; col -= move_dir[i][0];
				if (row == pos_row && col == pos_col)
					break;

				tiles_to_flip.push_back(col + row*NUM_CELL_COLUMNS);
			}
		}
	}

	if (tiles_to_flip.empty())
		return FALSE;

	return TRUE;
}


BOOL Make_Move(int color, int row, int col)
{
	vector<int> tiles_to_flip;
	vector<int>::iterator iter;

	if (! Is_Valid_Move(color, row, col, tiles_to_flip))
		return FALSE;

	game_board[row][col] = color;

	
	for (iter = tiles_to_flip.begin(); iter != tiles_to_flip.end(); iter++)
	{
		int r = *iter / 8, c = *iter % 8;
		game_board[r][c] = color;
	}

	return TRUE;
}


void Get_Current_Score()
{
	black_score = 0;
	white_score = 0;

	for (int y = 0; y < NUM_CELL_ROWS; y++)
		for (int x = 0; x < NUM_CELL_COLUMNS; x++)
		{
			if (game_board[y][x] == COLOR_BLACK)
				black_score++;
			else if (game_board[y][x] == COLOR_WHITE)
				white_score++;
		}
}

BOOL Get_Valid_Moves(int color, vector<int> &valid_moves)
{
	valid_moves.clear();
	vector<int> tiles_to_flip;
	for (int y = 0; y < NUM_CELL_ROWS; y++)
	{
		for (int x = 0; x < NUM_CELL_COLUMNS; x++)
		{
			if (Is_Valid_Move(color, y, x, tiles_to_flip))
				valid_moves.push_back(x + y*8);
		}
	}

	if (valid_moves.empty())
		return FALSE;

	return TRUE;
}


玩家控制落子(现在黑白方都由玩家控制):

	else if (game_state == GAME_STATE_RUN)
	{
		// draw the info
		Get_Current_Score();
		if (turn_color == COLOR_BLACK)
			wsprintf(buffer, TEXT("轮到黑棋走了,当前比分黑棋:%d,白棋:%d"), black_score, white_score);
		else
			wsprintf(buffer, TEXT("轮到白棋走了,当前比分黑棋:%d,白棋:%d"), black_score, white_score);
		DrawText_GUI(buffer, 8, cyBitmapBoard-16, RGB(255, 255, 128));

		// check if user is trying to exit
		if (KEY_DOWN(VK_ESCAPE))
		{
			// send message to windows to exit
			PostMessage(main_window_handle, WM_DESTROY, 0, 0);
			// set exit state
			game_state = GAME_STATE_SHUTDOWN;
		}

		if (turn_color == player_color && KEY_DOWN(VK_LBUTTON))
		{
			POINT point;
			GetCursorPos(&point);
			ScreenToClient(main_window_handle, &point);

			int row = 0, col = 0;
			if (point.x > BOARD_LEFT && point.x < BOARD_RIGHT &&
				point.y > BOARD_TOP  && point.y < BOARD_BOTTOM)
			{
				row = (point.y-BOARD_TOP)  / CELL_SIZE; col = (point.x-BOARD_LEFT) / CELL_SIZE;
				if (Make_Move(player_color, row, col))
				{
					Draw_Board();
					current_num++;
					
					// 产生对方的所有走法,若对方有合法走法则交换走棋方,否则不交换
					// 产生对方的所有走法,若对方有合法走法则交换走棋方,否则不交换
					vector<int> valid_moves;
					if (Get_Valid_Moves(computer_color, valid_moves))
						turn_color = computer_color;
				}
			}
		}

		if (turn_color == computer_color && KEY_DOWN(VK_LBUTTON))
		{
			POINT point;
			GetCursorPos(&point);
			ScreenToClient(main_window_handle, &point);

			int row = 0, col = 0;
			if (point.x > BOARD_LEFT && point.x < BOARD_RIGHT &&
				point.y > BOARD_TOP  && point.y < BOARD_BOTTOM)
			{
				row = (point.y-BOARD_TOP)  / CELL_SIZE; col = (point.x-BOARD_LEFT) / CELL_SIZE;
				if (Make_Move(computer_color, row, col))
				{
					Draw_Board();
					current_num++;

					// 产生对方的所有走法,若对方有合法走法则交换走棋方,否则不交换
					vector<int> valid_moves;
					if (Get_Valid_Moves(player_color, valid_moves))
						turn_color = player_color;
				}
			}
		}

		if (current_num == NUM_CELL_ROWS*NUM_CELL_COLUMNS-4)
			game_state = GAME_STATE_GAMEOVER;
	}
	else if (game_state == GAME_STATE_GAMEOVER)
	{
		Get_Current_Score();
		if (black_score == white_score)
			wsprintf(buffer, TEXT("和棋!32:32"));
		else if ((player_color == COLOR_BLACK && black_score > white_score) || 
			(player_color == COLOR_WHITE && white_score > black_score))
			wsprintf(buffer, TEXT("恭喜你赢了!黑棋:%d,白棋:%d"), black_score, white_score);
		else
			wsprintf(buffer, TEXT("不好意思,你输了!黑棋:%d,白棋:%d"), black_score, white_score);
		
		MessageBox(main_window_handle, buffer, TEXT("游戏结束"), MB_OK);
		game_state = GAME_STATE_SHUTDOWN;
	}
	else if (game_state == GAME_STATE_SHUTDOWN)
	{
		// in this state shut everything down and release resources
		// switch to exit state
		game_state = GAME_STATE_EXIT;
	}



3. 最简单的AI

下面先实现一个类似于贪心的算法,即程序每次选择得分最多的地方落子,但由于角上的特殊性,让程序优先选择角上落子。

// 设黑方为最大者,白方为最小者,黑棋的分为正,白棋分为负
int Get_Board_Score(int board[NUM_CELL_ROWS][NUM_CELL_COLUMNS])
{
	int score = 0;
	for (int y = 0; y < NUM_CELL_ROWS; y++)
	{
		for (int x = 0; x < NUM_CELL_COLUMNS; x++)
		{
			if (board[y][x] == COLOR_BLACK)
				score++;
			else if (board[y][x] == COLOR_WHITE)
				score--;
		}
	}

	return score;
}

BOOL Is_On_Corner(int y, int x)
{
	if ( (x == 0 && y == 0) || (x == 7 && y == 0) || (x == 7 && y == 7)
		|| (x == 0 && y == 7) )
		return TRUE;

	return FALSE;
}

/* 电脑AI算法**************************************************************/
void Get_Computer_Move_Greedy(int computer_color)
{
	vector<int> valid_moves;
	vector<int>::iterator iter;
	Get_Valid_Moves(computer_color, valid_moves);

	for (iter = valid_moves.begin(); iter != valid_moves.end(); iter++)
	{
		if (Is_On_Corner(*iter / 8, *iter % 8))
		{
			best_move_row = *iter / 8;
			best_move_col = *iter % 8;
			return;
		}
	}

	int best_score, score;
	best_score = computer_color == COLOR_BLACK ? -BEST_SCORE : BEST_SCORE;

	for (iter = valid_moves.begin(); iter != valid_moves.end(); iter++)
	{
		int copy_board[NUM_CELL_ROWS][NUM_CELL_COLUMNS];
		Copy_Board(copy_board, game_board);
		
		Make_Move(copy_board, computer_color, *iter/8, *iter%8);
		score = Get_Board_Score(copy_board);

		if ( (computer_color == COLOR_BLACK && score > best_score) ||
			 (computer_color == COLOR_WHITE && score < best_score) )
		{
			best_move_row = *iter/8;
			best_move_col = *iter%8;
			best_score = score;
		}
	}
}

控制流程:

	else if (game_state == GAME_STATE_RUN)
	{
		// draw the info
		Get_Current_Score();
		if (turn_color == COLOR_BLACK)
			wsprintf(buffer, TEXT("轮到黑棋走了,当前比分黑棋:%d,白棋:%d"), black_score, white_score);
		else
			wsprintf(buffer, TEXT("轮到白棋走了,当前比分黑棋:%d,白棋:%d"), black_score, white_score);
		DrawText_GUI(buffer, 8, cyBitmapBoard-16, RGB(255, 255, 128));

		// check if user is trying to exit
		if (KEY_DOWN(VK_ESCAPE))
		{
			// send message to windows to exit
			PostMessage(main_window_handle, WM_DESTROY, 0, 0);
			// set exit state
			game_state = GAME_STATE_SHUTDOWN;
		}

		if (turn_color == player_color && KEY_DOWN(VK_LBUTTON))
		{
			POINT point;
			GetCursorPos(&point);
			ScreenToClient(main_window_handle, &point);

			int row = 0, col = 0;
			if (point.x > BOARD_LEFT && point.x < BOARD_RIGHT &&
				point.y > BOARD_TOP  && point.y < BOARD_BOTTOM)
			{
				row = (point.y-BOARD_TOP)  / CELL_SIZE; col = (point.x-BOARD_LEFT) / CELL_SIZE;
				if (Make_Move(game_board, player_color, row, col))
				{
					Draw_Board();
					current_num++;
					
					// 产生对方的所有走法,若对方有合法走法则交换走棋方,否则不交换
					vector<int> valid_moves;
					if (Get_Valid_Moves(computer_color, valid_moves))
						turn_color = computer_color;
				}
			}
		}

		if (turn_color == computer_color && KEY_DOWN(VK_LBUTTON))
		{
			Get_Computer_Move_Greedy(computer_color);
			if (Make_Move(game_board, computer_color, best_move_row, best_move_col))
			{
				Draw_Board();
				current_num++;

				// 产生对方的所有走法,若对方有合法走法则交换走棋方,否则不交换
				vector<int> valid_moves;
				if (Get_Valid_Moves(player_color, valid_moves))
					turn_color = player_color;
			}
		}

		if (current_num == NUM_CELL_ROWS*NUM_CELL_COLUMNS-4)
			game_state = GAME_STATE_GAMEOVER;
	}


4. 博弈树搜索

上面的程序电脑AI已经具备了一定的棋力,但我试过仔细下,还是能赢电脑的(我是超级新手),而会下的就能比较轻松的赢电脑。

分析原因也很简单,就是电脑只想了一步棋。就如同玩家只看当前一步而不顾后果下棋一样。

只想一步,选最高分来走棋,由于电脑考虑的严密性,使得程序具有了一定的棋力。但只看一步,就容易给玩家留下可乘之机,比如电脑走了一步高分棋,却导致一个角被玩家占了,这一步棋就是臭棋。而我玩的时候虽然一般情况下只考虑一步,但到了角上,略微仔细考虑些,不给电脑占角的可乘之机,或是故意诱导电脑失角,就可以轻松赢棋。

而要让电脑具有更高的棋力,最简单的办法就是更深入的搜索。

棋类游戏一般可以定义成一棵博弈树,一个节点代表一个局面,例如:



最小最大原理:

将红黑双方,一个看做最大者(如黑方)一个看做最小者(如白方)。最大者追求获得一个最高分局面,最小者追求获得一个最低分局面。两方相互博弈,并假设对方一定会采取最优的走法。

如此以来,一方走棋就不能只看当前一步走法,而需要考虑自己走这一步棋,对方会怎样应对。如下图所示:


假设当前局面下,最大者有两种走法A和B,将导致两个局面的诞生。

假设若最大者采取走法A,最小者会有Aa,Ab两种应对走法;假设最大者采取走法B,则最小者会有Ba,Bb两种走法应对。

则,最大者若走走法A,则最小者一定会选择走法Ab,于是两层搜索,走法A的分数是1分;

而若最大者走走法B,则最下者一定会选择走法Bb,于是两层搜索,走法B的分数是3分;

而最大者一定会尽量选择较大的分数走棋,于是会选择走法B.于是最大者按照走法B走棋


我实在是不擅长叙述。说的乱七八糟的。

记得本科毕设做中国象棋的时候,见过一个很好的比喻。

假设甲乙两人博弈:乙有两个包A和B,A中有物品Aa(价值10),Ab(价值1),B中有物品Ba(价值6),Bb(价值3)。

现在由甲来选一个包,而由乙来从这个包中选一个物品送给甲。

甲当然希望得到一个最好的物品,而乙很吝啬,希望给甲一个最坏的物品。

当然乙不会干涉甲选哪个包,而甲选定包后,选哪个物品由乙做主。

则甲一定想得到价值10的物品,但若甲选A包,乙一定会把价值1的物品给甲,因为乙很吝啬。

于是甲考虑到选B包的话,乙会给他价值为3的Bb,虽然不如人意,但这也是甲能做出的最好选择了。

于是甲选B包,乙从B包中选Bb送给甲。


这就是最小-最大原理。

博弈的结果由两方共同决定。


而一局棋双方要下几十步,搜到最底层的可能不是很大。

下面尝试实现一个按最小最大原理搜索depth层的AI走法:

// 电脑AI走法,使用最大最小原理进行博弈树搜索
int Get_Computer_Move_MaxMin(int color, int board[NUM_CELL_ROWS][NUM_CELL_COLUMNS], int depth)
{
	// 若已搜到要求的最底层,返回盘面分值
	if (depth == 0)
		return Get_Board_Score(board);

	int best_score, score, best_row, best_col, other_color;
	best_score = color == COLOR_BLACK ? -BEST_SCORE : BEST_SCORE;
	other_color = 17 - color;

	// 获取所有合法走法
	vector<int> valid_moves;
	if (! Get_Valid_Moves(color, valid_moves))
	{
		Get_Valid_Moves(other_color, valid_moves);
		color = other_color;
		other_color = 17 - color;
	}

	for (vector<int>::iterator iter = valid_moves.begin(); iter != valid_moves.end(); iter++)
	{
		// 拷贝一份棋盘
		int copy_board[NUM_CELL_ROWS][NUM_CELL_COLUMNS];
		Copy_Board(copy_board, board);
		
		// 执行这个走法
		Make_Move(copy_board, color, *iter/8, *iter%8);
		
		min_max_search_depth++;
		score = Get_Computer_Move_MaxMin(other_color, copy_board, depth-1);
		min_max_search_depth--;

		if ( (color == COLOR_BLACK && score > best_score) ||
			 (color == COLOR_WHITE && score < best_score) )
		{
			best_row = *iter/8;
			best_col = *iter%8;
			best_score = score;
		}
	}

	if (min_max_search_depth == 0)
	{
		best_move_row = best_row;
		best_move_col = best_col;
	}

	return best_score;
}

void Get_Computer_Best_Move_AI(int color)
{
	min_max_search_depth = 0;
	Get_Computer_Move_MaxMin(color, game_board, MIN_MAX_SEARCH_DEPTH);
}


暂时不去深究这些AI算法,以后有空再全面、深入学习~

上面的程序是自己实现的,没有参考别人的程序,而搜索三层程序棋力一般,所以不保证其正确性!



  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值