注:以下程序为本人原创,写的不好,若有好的建议,望留言告知。而若能帮助一二访客,幸甚!
上回用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算法,以后有空再全面、深入学习~
上面的程序是自己实现的,没有参考别人的程序,而搜索三层程序棋力一般,所以不保证其正确性!