五子棋人机对弈

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

【概述】五子棋是一种大众喜爱的游戏,其规则简单,变化多端,非常富有趣味性何消遣性。

这里设计了一个简单的五子棋程序,采用对空格点进行评分排序的算法。

近来随着计算机的快速发展,各种棋类游戏被纷纷请进了电脑,使得那些喜爱下棋,又常常苦于没有对手的棋迷们能随时过足棋瘾。

而且这类软件个个水平颇高,大有与人脑分庭抗礼之势。

其中战胜过国际象棋世界冠军-卡斯帕罗夫的“深蓝”便是最具说服力的代表;其它像围棋的“手淡”、象棋的“将族”等也以其优秀的人工智能深受棋迷喜爱;而我也做了一个“无比”简单的五子棋算法。

总的来说(我们假定您熟悉五子棋的基本规则),要让电脑知道该在哪一点下子,就要根据盘面的形势,为每一可能落子的点计算其重要程度,也就是当这子落下后会形成什么棋型(如:“冲四”、“活三”等),然后通览全盘选出最重要的一点,这便是最基本的算法。

主程序模块包括:数据结构,评分规则,胜负判断,搜索最优空格的算法过程。

【关键字】人工智能,博弈树,五子棋,无禁手,评分,搜索,C,随机。

【环境】XP/TC3.0
【算法及解析】(无禁手)
一.数据结构:
本程序中只使用了一个19×19的二元结构数组如下定义:
Typedef Struct
{
int player;
int value[8][5];
long int score;
}map[19][19];
其中map[i][j]保存i行j列棋子信息,player为下棋方,value数组记录八个方向的连续5个棋子的信息,为以后评分服务。

Score为空格评分。

以及数据结构可以满足初级人机对弈程序的功用。

对比其他程序结构:
王小春五子棋源码:该程序采用链表节点结构,保存下子信息,该结构主要为悔棋提供方便(虽该源码为开发悔棋功能)
Typedef struct Step
{
int m;
int n;
char side;
};
为链表clist节点,m,n表示两个坐标值,side表示下子方相对于我的程序中的player.
另外该程序还使用一个二维数组map[][],来保存棋盘信息。

二.预定义单元
#define blank 0
#define black 1
#define white 2
#define NUM_HIGH 19
#define NUM_LINE 19
#define man 1
#define bot 2
其中blank表示空白点,black该点放黑子(即man),white该点放白子(即robot)
NUM_HIGH,NUM_LINE分别定义棋盘的高度与宽度。

Turn 为轮流下子方。

三.评分机制
判断是否能成活四或者是双死四或者是死四活三,如果是机器方的话给予10,000,000;
判断是否已成冲四,如果是机器方的话给予6,000,000;
判断是否成死3活3,如果是机器方的话给予1,500,000;;
判断是否已成双活2,如果是机器方的话给予100,00;
判断是否能成活2,如果是机器方的话给予70,000;
判断是否能成死2,如果是机器方的话给予30,000。

四.函数定义及其功能。

1.Void init()
初始化棋盘bgi图形与棋盘棋型。

将棋盘标记map[][].player都置为blank(空白),各点8方向的连续五个棋子信息也初始为0,各点评分数也置为0。

void init()
{
int i, j, m, n;
int gdriver=DETECT,gmode;
initgraph(&gdriver,&gmode,"..\\bgi");
for( i = 0; i < 19; i++)
for( j = 0; j < 19; j++)
{
map[i][j].player = blank;
for( m = 0; m < 8; m++)
for( n = 0; n < 4; n++)
map[i][j].value[m][n] = blank;
map[i][j].score = blank;
}
}
2.Void paint()
画棋盘函数,本五子棋程序使用的是标准围棋棋盘,规格19×19,每格25大小(象素),并且规定man棋子颜色为黑,robot为白色,黑棋优先。

最后初始化光标(开始位置)位置。

void paint()
{
int i, j;
clearviewport();
setbkcolor(BLUE);//设置背景色
setcolor(WHITE);//设置作图色
for(i = 20; i <= 470; i+= 25)
line( 20, i, 470, i);
for(j = 20; j <= 470; j+= 25)
line( j, 20, j, 470);
outtextxy( 525, 160, "Man");
setcolor(DARKGRAY);
setfillstyle( SOLID_FILL, DARKGRAY);
pieslice( 545, 185, 0, 360, 10);
setcolor(WHITE);
outtextxy( 520, 210, "Robot");
setcolor(WHITE);
setfillstyle( SOLID_FILL, WHITE);
pieslice( 545, 235, 0, 360, 10);
moverel( 245, 245);//开始游戏时 man 下棋的位置
}
3.void main()
主函数控制。

初始化图形及棋型,规定黑子优先,画棋盘,开始下子。

(略)
4.Void human()
Man下棋函数,其中添加了防棋子覆盖控制。

void human(void)
{
char ch=1;
int size, x, y;
void *buf;
while( ch != 32 )
{
ch = getch();
putimage( getx()-10, gety()-10, buf, COPY_PUT); if( ch == 27) //ESC退出
exit(1);
if( ch == 'w' || ch == 'W') //移动控制单元
moverel( 0, -25);
else if( ch == 's' || ch == 'S')
moverel( 0, 25);
else if( ch == 'a' || ch == 'A')
moverel( -25, 0);
else if( ch == 'd' || ch == 'D')
moverel( 25, 0);
if( getx() < 20) //防止man下棋越界
moverel( 25, 0);
if( getx() > 470)
moverel( -25, 0);
if( gety() < 20)
moverel( 0, 25);
if( gety() > 470)
moverel( 0, -25);
size = imagesize( getx()-10, gety()-10, getx()+10,
gety()+10);
buf = malloc(size);
getimage( getx()-10, gety()-10, getx()+10, gety()+10, buf); if( man == black)
{
setcolor(DARKGRAY);
setfillstyle(SOLID_FILL, DARKGRAY);
}
else
{
setcolor(WHITE);
setfillstyle(SOLID_FILL, WHITE);
}
pieslice( getx(), gety(), 0, 360, 10); //光标显示
x = ( getx() - 20) / 25; //计算 man 下棋的棋盘位置
y = ( gety() - 20) / 25;
if( ch == 32 && map[y][x].player != 0 ) //防止 man 棋子覆盖
ch = 1;
}
map[y][x].player = man;
mark( x, y);
}
5.Void mark(int,int)
标记棋子,robot标记为白棋,man标记为黑棋。

在程序结束添加了判断下子方是否胜出(结束)判断函数,check(int,int).
void mark( int x, int y) //标记棋子
{
int i, j,m,n;
m=x;
n=y;
if( turn == man)
{
setcolor(DARKGRAY);
setfillstyle(SOLID_FILL, DARKGRAY);
}
else
{
setcolor(WHITE);
setfillstyle(SOLID_FILL, WHITE);
}
pieslice( (x * 25 + 20), (y * 25 + 20), 0, 360, 10); //棋盘前端显示
setcolor(RED);
if( turn == bot)
for( x = 0; x < 19;x++) //bot下完子后,对评分机制初始化
for(y = 0; y < 19; y++)
if( map[x][y].player == blank)
{
for( i = 0; i < 8; i++)
for( j = 0; j < 5; j++)
map[x][y].value[i][j] = blank;
map[x][y].score = blank;
}
check(n,m);
}
6.void check(int,int)
判断程序是否结束函数(一方胜利),共判断了四个方向:水平方向,竖直方向,左斜线方向,右斜线方向。

如果发现存在超过或等于5各连续棋子,则判断一方胜利,调用win(int)函数,显示结果,程序结束。

程序结尾,添加交换下子部分。

void check(int m,int n)
{
int WinTag=map[m][n].player;
int i=m,j=n,level=1;
//outtextxy((m * 25 + 20), (n * 25 + 20),".");
if(WinTag==man) outtextxy((n* 25 + 17), (m * 25 + 17),"M");
else outtextxy((n * 25 + 17), (m * 25 + 17),"R");
//水平位置判断
while(map[i-1][j].player==WinTag&&i>=1)
{
level++; i--;
}
i=m;
while(map[i+1][j].player==WinTag&&i<(NUM_LINE-1)) {
level++; i++;
}
if(level>=5)win(WinTag);
level=1;
//竖直方向判断
i=m;j=n;
while(map[i][j-1].player==WinTag&&j>=1)
{
level++; j--;
}
j=n;
while(map[i][j+1].player==WinTag&&j<(NUM_HIGH-1)) {
level++; j++;
}
if(level>=5)win(WinTag);
level=1;
//左斜线判断
i=m; j=n;
while(map[i-1][j-1].player==WinTag&&i>=1&&j>=1)
{
level++; i--;j--;
}
i=m; j=n;
while(map[i+1][j+1].player==WinTag&&i<(NUM_LINE-1)&&j<(NUM_HIGH-1 ))
{
level++; i++; j++;
}
if(level>=5)win(WinTag);
level=1;
//右斜线判断
i=m; j=n;
while(map[i+1][j-1].player==WinTag&&i<NUM_LINE&&j>=1)
{
level++; i++; j--;
}
i=m; j=n;
while(map[i-1][j+1].player==WinTag&&i>=1&&j<(NUM_HIGH-1))
{
level++; i--; j++;
}
if(level>=5)win(WinTag);
//交换下棋顺序
if( turn == man)
{
turn = bot; robot();
}
else
{
turn = man; human();
}
7.Void win(int)
void win( int winner)
{
char ch;
setbkcolor(LIGHTGRAY);
setcolor(YELLOW);
//ch = getch();
while( ch != 27)
{
if(winner==man)outtextxy( 240, 210, "You are the winner !"); else outtextxy( 240, 210, "You are lost !");
outtextxy( 245, 250, "Press ESC to Exit!");
printf("%d",winner);
ch = getch();
}
closegraph();
exit(1);
}
}
8.void robot()
也是人机对弈五子棋最核心的部分,其中包括了评价机制,估值,搜索部分
搜索空格的八个方向,如:
1 0 2
7 空格 3
6 5 4
最后将权值最大的几个点的位置信息保存于xposition[]与yposition[]数组中,如果极值权重相同,使用随机函数选择其中一点下棋。

void robot(void)
{
int x, y, X, Y, i, j;
int *p, *mp; //用于指向特定数组
long int xposition[38], yposition[38]; //存储相同得权值
long int max; //当前最大权值
//将空格信息填入特定数组,用于bot评分并下子,将 360 度分成 8 个方向
//即同一个空格可能五子相连的八个方向,具体看 readme 文件
for( X = 0; X < 19; X++) //0方向检查
for( Y = 18; Y > 4; Y--)
if( map[Y][X].player == blank)
for( i = 0, x = X, y = Y; y > Y - 5; i++, y--)
map[Y][X].value[0][i] = map[y-1][x].player;
for( X = 0; X < 19; X++) //4 方向检查
for( Y = 0; Y < 10; Y++)
if( map[Y][X].player == blank)
for( i = 0, x = X, y = Y; y < Y + 5; i++, y++)
map[Y][X].value[4][i] = map[y+1][x].player;
// '/' 检查, 1 方向检查开始
for( i = 4; i < 19; i++) //'/'检查, 1 方向检查,左上部分
for( X = 0, Y = i; Y > 3; X++, Y--) //检查一斜行
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (y > Y - 5) || (y > 0); j++, x++, y--)
map[Y][X].value[1][j] = map[y-1][x+1].player;
for( i = 1; i < 11; i++)
for( X = i, Y = 18; X < 11; X++, Y--)// 1 方向检查,右下部分
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (x < X + 5) || (x < 18); j++, x++, y--)
map[Y][X].value[1][j] = map[y-1][x+1].player;
for( i = 18; i > 3; i--)//'/'检查, 5 方向检查,左上部分
for( X = i, Y = 0; X > 3; X--, Y++)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (x > X - 5) || (x > 0); j++, x--, y++)
map[Y][X].value[5][j] = map[y+1][x-1].player;
for( i = 1; i < 11; i++)// 5 方向检查,右下部分
for( X = 18, Y = i; Y < 11; X--, Y++)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (y < Y + 5) || (y < 14); j++, x--, y++)
map[Y][X].value[5][j] = map[y+1][x-1].player;
// '/' 检查结束, 5 方向检查结束
for( Y = 0; Y < 19; Y++)// 2 方向检查,从左到右
for( X = 0; X < 10; X++)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; x < X + 5; j++, x++)
map[Y][X].value[2][j] = map[y][x+1].player;
for( Y = 0; Y < 19; Y++)//6 方向检查,从右到左
for( X = 18; X > 4; X--)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; x > X - 5; j++, x--)
map[Y][X].value[6][j] = map[y][x-1].player;
// '\' 检查开始, 3 方向检查开始
for( i = 0; i < 11; i++)// 3 方向检查,右上部分
for( X = i, Y = 0; X < 11; X++, Y++)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (x < X + 5) || (x < 18); j++, x++, y++)
map[Y][X].value[3][j] = map[y+1][x+1].player;
for( i = 1; i < 11; i++)// 3 方向检查,左下部分
for( X = 0, Y = i; Y < 11; X++, Y++)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (y < Y + 5) || (y < 18); j++, x++, y++)
map[Y][X].value[3][j] = map[y+1][x+1].player;
for( i = 18; i > 3; i--)// 7 方向检查,右上部分
for( X = 18, Y = i; Y > 3; X--, Y--)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (y > Y - 5) || (y > 0); j++, x--, y--)
map[Y][X].value[7][j] = map[y-1][x-1].player;
for( i = 17; i > 3; i--)// 7 方向检查,左下部分
for( X = i, Y = 18; X > 3; X--, Y--)
if( map[Y][X].player == blank)
for( j = 0, x = X, y = Y; (x > X - 5) || (x > 0); j++, x--, y--)
map[Y][X].value[7][j] = map[y-1][x-1].player;
//'\' 检查结束, 7 方向检查结束, bot 检查棋盘空格结束
//进入bot判断,对棋盘上每一个空格进行评分
for( y = 0; y < 19; y++)
for( x = 0; x < 19; x++)
if( map[y][x].player == blank)
for( i = 0; i < 8; i++)
{
// for控制对一个空格的8个方向评分
p = &map[y][x].value[i][0];//mp 与 p 刚好组成空格的两头
if( i < 4)mp = &map[y][x].value[i+4][0];
else mp = &map[y][x].value[i-4][0];
//b 为bot, m 为man, o 为空格, x为评分点
//己方已经可以赢,或是对方可能有四子相连的评分为 100,000,000 一亿的情况
if( *p == bot && *(p+1) == bot && *(p+2) == bot && *(p+3) == bot )
map[y][x].score += 100000000;
if( *p == man && *(p+1) == man && *(p+2) == man && *(p+3) == man )
map[y][x].score += 100000000;
if( *p == bot && *(p+1) == bot && *(p+2) == bot && *mp == bot )
map[y][x].score += 100000000;
if( *p == man && *(p+1) == man && *(p+2) == man && *mp == man )
map[y][x].score += 100000000;
if( *p == bot && *(p+1) == bot && *mp == bot &&*(mp+1) == bot )
map[y][x].score += 100000000;
if( *p == man && *(p+1) == man && *mp == man && *(mp+1) == man )
map[y][x].score += 100000000;
//活四评分为 10,000,000 一千万的情况
if( *p == bot && *(p+1) == bot && *(p+2) == bot && *(p+3) == blank && *mp == blank )
map[y][x].score += 10000000;
if( *p == man && *(p+1) == man && *(p+2) == man && *(p+3) == blank )
map[y][x].score += 10000000;
if( *p == bot && *(p+1) == bot && *(p+2) == blank && *mp == man && *(mp+1) == blank )
map[y][x].score += 10000000;
if( *p == man && *(p+1) == man && *(p+2) == blank && *mp == man && *(mp+1) == blank )
map[y][x].score += 10000000;
if( *p == blank && *(p+1) == bot && *(p+2) == bot && *(p+3) == bot && *(p+4) == blank && *mp == blank )
map[y][x].score += 10000000;
//冲四评分为 8,000,000 八百万的情况
if( *p == bot && *(p+1) == bot && *(p+2) == bot && *(p+3) == man && *mp == blank )
map[y][x].score += 8000000;
if( *p == bot && *(p+1) == bot && *(p+2) == man && *mp == bot && *(mp+1) == blank )
map[y][x].score += 8000000;
if( *p == bot && *(p+1) == man && *mp == bot && *(mp+1) == bot && *(mp+2) == bot && *(mp+3) == blank )
map[y][x].score += 8000000;
if( *mp == blank && *p == blank && *(p+1) == bot && *(p+2) == bot && *(p+3) == bot && *(p+4) == man )
map[y][x].score += 8000000;
if( *mp == bot && *(mp+1) == blank && *p == blank && *(p+1) == bot && *(p+2) == bot && *(p+3) == man )
map[y][x].score += 8000000;
//防对方冲四评分为 6,000,000 的情况
if( *p == man && *(p+1) == man && *(p+2) == man && *(p+3) == bot && *mp == blank )
map[y][x].score += 6000000;
if( *p == man && *(p+1) == man && *(p+2) == bot && *mp == man && *(mp+1) == blank )
map[y][x].score += 6000000;
//活三评分为 4,000,000 四百万的情况
if( *p == bot && *(p+1) == bot && *(p+2) == blank && *mp == blank )
map[y][x].score += 4000000;
if( *p == man && *(p+1) == man && *(p+2) == blank && *mp == blank )
map[y][x].score += 4000000;
if( *p == man && *(p+1) == blank && *(p+2) == man && *(p+3) == blank && *mp == blank )
map[y][x].score += 4000000;
if( *p == bot && *(p+1) == blank && *mp == bot && *(mp+1) == blank )
map[y][x].score += 4000000;
if( *p == man && *(p+1) == blank && *mp == bot && *(mp+1) == blank )
map[y][x].score += 4000000;
//死三评分为 1,500,000 一百五十万的情况,进攻为主
if( *p == bot && *(p+1) == bot && *(p+2) == man && *mp == blank )
map[y][x].score += 1500000;
if( *p == bot && *(p+1) == man && *mp == bot && *(mp+1) == blank )
map[y][x].score += 1500000;
if( *mp == man && *p == bot && *(p+1) == bot && *(p+2) == blank )
map[y][x].score += 1500000;
if( *mp == blank && *p == blank && *(p+1) == bot && *(p+2) == bot && *(p+3) == man )
map[y][x].score += 1500000;
//活二评分为 100,000 十万的情况
if( *p == bot && *(p+1) == blank && *mp == blank )
map[y][x].score += 100000;
if( *p == blank && *(p+1) == bot && *(p+2) == blank && *mp == blank )
map[y][x].score += 100000;
//活二评分为 70,000 七万的情况
if( *p == man && *(p+1) == blank && *mp == blank )
map[y][x].score += 70000;
if( *p == blank && *(p+1) == man && *(p+2) == blank && *mp == blank )
map[y][x].score += 70000;
//冲二评分为 30,000 三万的情况
if( *mp == blank && *p == bot && *(p+1) == man )
map[y][x].score += 30000;
if( *mp == blank && *p == blank && *(p+1) == bot && *(p+2) == man)
map[y][x].score += 30000;
//冲二评分为 10,000 一万的情况,防守为主
if( *mp == blank && *p == man && *(p+1) == bot )
map[y][x].score += 10000;
//开局情况,并限定下子范围
if( step == blank){
do{
x = random(19);
y = random(19);
if( map[y][x].player != blank)x = blank;
}while( x - 5 < 0 || x - 10 > 0 || y - 5 < 0 || y - 10 > 0 ); map[y][x].score += 5000;
step += 1;
}
}
max = blank; //权值评分机制初始化
for( y = 0; y < 19; y++) //搜寻权值最大点
for( x = 0; x < 19; x++)
if( map[y][x].player == blank){
if( map[y][x].score > max)
{
max = map[y][x].score;
for( i = 0; i < 38; i++)
{
xposition[i] = blank;
yposition[i] = blank;
}
xposition[0] = x;
yposition[0] = y;
}
else if( map[y][x].score == max)
{
i+=1;
xposition[i] = x;
xposition[i] = y;
}
}
if( xposition[2] == blank)//选择最大权值点做下子点
{
map[ yposition[0] ][ xposition[0] ].player = bot; mark( xposition[0], yposition[0]);
}
else //在最大权值相等的位置中随机选择下子点*/
{
i = random(10);
x = xposition[i];
y = yposition[i];
map[y][x].player = bot;
mark( x, y);
}
}
【相关知识】篇幅所限制,活三,冲四等棋型在此就不赘述.
【心得体会】通过本次实践,初步掌握了五子棋“人机博弈”的基本算法,由于开发较为智能的程序,算法复杂度较高,固在此只进行了一层搜索。

采用博弈树的算法,搜索最佳位置后,然后剪枝,选择最优。

实际上按照王小春的说法,要进行深度为4搜索,计算机可能要思考15秒左右,算法时间性能较差,况且时间较仓促,没有进行更深层次的思考,期间遇到不少挫折,程序规模较以前相比庞大了不少,参照了不少源码(15×15)。

人工智能算法博大精深,日前在网上看到一种所谓“稀疏矩阵”的算法,采用“易语言”编译,时间性能尚可。

有机会向作者请教请教。

【参考文献及相关资料】
《PC游戏编程(人机博弈)》――――王小春
《算法分析与设计技术》――――马绍汉
CSDN专栏
论坛
baker
五子棋是一种受大众广泛喜爱的游戏,其规则简单,变化多端,非常富有趣味性和消遣性。

这里设计和实现了一个人机对下的五子棋程序,采用了博弈树的方法,应用了剪枝和最大最小树原理进行搜索发现最好的下子位置。

介绍五子棋程序的数据结构、评分规则、胜负判断方法和搜索算法过程。

一、相关的数据结构
关于盘面情况的表示,以链表形式表示当前盘面的情况,目的是可以允许用户进行悔棋、回退等操作。

CList StepList;
其中Step结构的表示为:
struct Step
{
int m; //m,n表示两个坐标值
int n;
char side; //side表示下子方
};
以数组形式保存当前盘面的情况,
目的是为了在显示当前盘面情况时使用:
char FiveArea[FIVE_MAX_LINE][FIVE_MAX_LINE];
其中FIVE_MAX_LINE表示盘面最大的行数。

同时由于需要在递归搜索的过程中考虑时间和空间有效性,只找出就当前情况来说相对比较好的几个盘面,而不是对所有的可下子的位置都进行搜索,这里用变量CountList来表示当前搜索中可以选择的所有新的盘面情况对象的集合:
CList CountList;
其中类CBoardSituiton为:
class CBoardSituation
{
CList StepList; //每一步的列表
char FiveArea[FIVE_MAX_LINE][FIVE_MAX_LINE];
struct Step machineStep; //机器所下的那一步
double value; //该种盘面状态所得到的分数
}
二、评分规则
对于下子的重要性评分,需要从六个位置来考虑当前棋局的情况,分别为:-,¦,/,\,//,\\
实际上需要考虑在这六个位置上某一方所形成的子的布局的情况,对于在还没有子的地方落子以后的当前局面的评分,主要是为了说明在这个地方下子的重要性程度,设定了一个简单的规则来表示当前棋面对机器方的分数。

基本的规则如下:
判断是否能成5, 如果是机器方的话给予100000分,如果是人方的话给予-100000 分;判断是否能成活4或者是双死4或者是死4活3,如果是机器方的话给予10000分,如果是人方的话给予-10000分;
判断是否已成双活3,如果是机器方的话给予5000分,如果是人方的话给予-5000 分;判断是否成死3活3,如果是机器方的话给予1000分,如果是人方的话给予-1000 分;判断是否能成死4,如果是机器方的话给予500分,如果是人方的话给予-500分;
判断是否能成单活3,如果是机器方的话给予200分,如果是人方的话给予-200分;
判断是否已成双活2,如果是机器方的话给予100分,如果是人方的话给予-100分;
判断是否能成死3,如果是机器方的话给予50分,如果是人方的话给予-50分;
判断是否能成双活2,如果是机器方的话给予10分,如果是人方的话给予-10分;
判断是否能成活2,如果是机器方的话给予5分,如果是人方的话给予-5分;
判断是否能成死2,如果是机器方的话给予3分,如果是人方的话给予-3分。

实际上对当前的局面按照上面的规则的顺序进行比较,如果满足某一条规则的话,就给该局面打分并保存,然后退出规则的匹配。

注意这里的规则是根据一般的下棋规律的一个总结,在实际运行的时候,用户可以添加规则和对评分机制加以修正。

三、胜负判断
实际上,是根据当前最后一个落子的情况来判断胜负的。

实际上需要从四个位置判断,以该子为出发点的水平,竖直和两条分别为45度角和135度角的线,目的是看在这四个方向是否最后落子的一方构成连续五个的棋子,如果是的话,就表示该盘棋局已经分出胜负。

具体见下面的图示:
四、搜索算法实现描述
注意下面的核心的算法中的变量currentBoardSituation,表示当前机器最新的盘面情况, CountList表示第一层子节点可以选择的较好的盘面的集合。

核心的算法如下:
void MainDealFunction()
{
value=-MAXINT; //对初始根节点的value赋值
CalSeveralGoodPlace(currentBoardSituation,CountList);
//该函数是根据当前的盘面情况来比较得到比较好的可以考虑的几个盘面的情况,可以根据实际的得分情况选取分数比较高的几个盘面,也就是说在第一层节点选择的时候采用贪婪算法,直接找出相对分数比较高的几个形成第一层节点,目的是为了提高搜索速度和防止堆栈溢出。

pos=CountList.GetHeadPosition();
CBoardSituation*pBoard;
for(i=0;ivalue=Search(pBoard,min,value,0);
Value=Select(value,pBoard->value,max);
//取value和pBoard->value中大的赋给根节点
}
for(i=0;ivalue)
//找出那一个得到最高分的盘面
{
currentBoardSituation=pBoard;
PlayerMode=min; //当前下子方改为人
Break;
}
}
其中对于Search函数的表示如下:实际上核心的算法是一个剪枝过程,其中在这个搜索过程中相关的四个参数为:(1)当前棋局情况;(2)当前的下子方,可以是机器(max)或者是人(min);(3)父节点的值oldValue;(4)当前的搜索深度depth。

double Search(CBoardSituation&
board,int mode,double oldvalue,int depth)
{
CList m_DeepList;
if(deptholdvalue))== TRUE)
{
if(mode==max)
value=select(value,search(successor
Board,min,value,depth+1),max);
else
value=select(value,search(successor
Board,max,value,depth+1),min);
}
return value;
}
else
{
if ( goal(board)<>0)
//这里goal(board)<>0表示已经可以分出胜负
return goal(board);
else
return evlation(board);
}
}
注意这里的goal(board)函数是用来判断当前盘面是否可以分出胜负,而evlation(board)是对当前的盘面从机器的角度进行打分。

下面是Select函数的介绍,这个函数的主要目的是根据PlayerMode情况,即是机器还是用户来返回节点的应有的值。

double Select(double a,double b,int mode)
{
if(a>b &&mode==max)¦¦ (a< b &&mode==min)
return a;
else
return b;
}
五、小结
在Windows操作系统下,用VC++实现了这个人机对战的五子棋程序。

和国内许多只是采用规则或者只是采用简单递归而没有剪枝的那些程序相比,在智力上和时间有效性上都要好于这些程序。

同时所讨论的方法和设计过程为用户设计其他的游戏(如象棋和围棋等)提供了一个参考。

相关文档
最新文档