上一篇我们完成了整个程序的基础框架,那么在讲到真正的搜索算法前,我们先来看看五子棋如何评估当前局势,以及如何计算某个位置的价值。

一、五子棋

在五子棋中,包括成五,活三,活二等定势,下图为山东师范大学董红安在2005年的硕士毕业论文中使用的的评分表,可以供我们来参考。

但是对于四子棋来说,上述评分却并不适用,因为棋盘空间大小的原因,任何一个维度只有4子的空间,一旦没有落成,或是任意一个位置被对方下了,那么该位置将没有任何价值。

二、潜在可能性评估

我们以这张图来举例,当黑棋在(0,3,0)这个位置落子后,我们来分析之后的可能性。

首先在xy平面内,有如下三种方式取胜:

除xy平面内之外,我们还需要考虑立体斜着四子连成的情况:

最后还要考虑垂直四子连成的情况:

并且在计算价值时,我们要注意,在上述任何一种情况中,只要预计的路线上有对方棋子出现,那么这条线(仅仅是单条线,不是整体)的评分将为0,因为他已经不能实现胜利。

三、展平化

为了更方便的计算,我们通过将chessBoard[x][y][z]符合规则的任意连续的四个子编入序列,并通过计数的方式实现计分。

我们通过一段代码来了解这个过程:

struct flatData{
int a;
int b;
int c;
int d;
}; typedef std::vector<flatData> PicesFlatDataList; int isWin(int board[][][]); int ChessBoard::isWin(int board[][][])
{
PicesFlatDataList flats; for(int y = ;y < ;y++)
{
for(int z = ;z < ;z++)
{
flatData data;
data.a = board[][y][z];
data.b = board[][y][z];
data.c = board[][y][z];
data.d = board[][y][z];
flats.push_back(data);
}
} for(int x = ;x < ;x++)
{
for(int z = ;z < ;z++)
{
flatData data;
data.a = board[x][][z];
data.b = board[x][][z];
data.c = board[x][][z];
data.d = board[x][][z];
flats.push_back(data);
}
} for(int x = ;x < ;x++)
{
for(int y = ;y < ;y++)
{
flatData data;
data.a = board[x][y][];
data.b = board[x][y][];
data.c = board[x][y][];
data.d = board[x][y][];
flats.push_back(data);
}
} for(int y = ;y < ;y++)
{
flatData data;
data.a = board[][y][];
data.b = board[][y][];
data.c = board[][y][];
data.d = board[][y][];
flats.push_back(data);
} for(int x = ;x < ;x++)
{
flatData data;
data.a = board[x][][];
data.b = board[x][][];
data.c = board[x][][];
data.d = board[x][][];
flats.push_back(data);
} for(int y = ;y < ;y++)
{
flatData data;
data.a = board[][y][];
data.b = board[][y][];
data.c = board[][y][];
data.d = board[][y][];
flats.push_back(data);
} for(int x = ;x < ;x++)
{
flatData data;
data.a = board[x][][];
data.b = board[x][][];
data.c = board[x][][];
data.d = board[x][][];
flats.push_back(data);
} for(int z = ;z < ;z++)
{
flatData data;
data.a = board[][][z];
data.b = board[][][z];
data.c = board[][][z];
data.d = board[][][z];
flats.push_back(data);
} for(int z = ;z < ;z++)
{
flatData data;
data.a = board[][][z];
data.b = board[][][z];
data.c = board[][][z];
data.d = board[][][z];
flats.push_back(data);
} flatData data;
data.a = board[][][];
data.b = board[][][];
data.c = board[][][];
data.d = board[][][];
flats.push_back(data); data.a = board[][][];
data.b = board[][][];
data.c = board[][][];
data.d = board[][][];
flats.push_back(data); data.a = board[][][];
data.b = board[][][];
data.c = board[][][];
data.d = board[][][];
flats.push_back(data); data.a = board[][][];
data.b = board[][][];
data.c = board[][][];
data.d = board[][][];
flats.push_back(data);
}

不难看出逻辑简单粗暴,直接遍历整个棋盘,并且将所有横竖斜的可能性加入FlatData的abcd四个位置中,再把该条加入到整个list中,为后续其他功能提供数据。

四、局势评分及输赢判断

在上一步的基础上,我们要做的是根据每组FlatData(展平后的数据格式)来给出我们评估的分数。

int whiteNum = ,blackNum = ;

    for(auto iter = flats.begin();iter != flats.end();iter++)
{
whiteNum = ;
blackNum = ; if(iter->a == chessPicesStatus::black)
blackNum++;
else if(iter->a == chessPicesStatus::white)
whiteNum++; if(iter->b == chessPicesStatus::black)
blackNum++;
else if(iter->b == chessPicesStatus::white)
whiteNum++; if(iter->c == chessPicesStatus::black)
blackNum++;
else if(iter->c == chessPicesStatus::white)
whiteNum++; if(iter->d == chessPicesStatus::black)
blackNum++;
else if(iter->d == chessPicesStatus::white)
whiteNum++; if(whiteNum == )
return chessPicesStatus::white; if(blackNum == )
return chessPicesStatus::black; } return chessPicesStatus::empty;

以上为判断输赢的代码,可以看出就是在上一步的基础上添加了abcd四个位置白棋黑棋的统计,并且判断是否已经获胜。对于获取当前局面分评估的逻辑,其实只是在最后一步统计的时候更加细分一些:

int ChessBoard::getFlatPicesValue(PicesFlatDataList flats, chessPicesStatus status)
{
int value = ,whiteNum = ,blackNum = ; for(auto iter = flats.begin();iter != flats.end();iter++)
{
whiteNum = ;
blackNum = ; if(iter->a == chessPicesStatus::black)
blackNum++;
else if(iter->a == chessPicesStatus::white)
whiteNum++; if(iter->b == chessPicesStatus::black)
blackNum++;
else if(iter->b == chessPicesStatus::white)
whiteNum++; if(iter->c == chessPicesStatus::black)
blackNum++;
else if(iter->c == chessPicesStatus::white)
whiteNum++; if(iter->d == chessPicesStatus::black)
blackNum++;
else if(iter->d == chessPicesStatus::white)
whiteNum++; if(status == chessPicesStatus::white)
{
//Calculating White Picess if(blackNum != )
continue; if(whiteNum == )
value += ;
else if(whiteNum == )
value += ;
else if(whiteNum == )
value += ;
else if(whiteNum == )
value += ;
else if(whiteNum == )
{
value += ;
} }
else
{
//Calculating Black Picess if(whiteNum != )
continue; if(blackNum == )
value += ;
else if(blackNum == )
value += ;
else if(blackNum == )
value += ;
else if(blackNum == )
value += ;
else if(blackNum == )
{
value += ;
} } //cout<<iter->a<<" "<<iter->b<<" "<<iter->c<<" "<<iter->d<<" "<<value<<endl;
}
return value;
}

可以看出我们对于任何一个FlatData,如果该条数据有对方棋子存在,价值即为零,进入下一条。价值从本方棋子数目为零到四分别为1,5,100,5000,10000。

五、单个位置价值评分

为了实现后续的启发式搜索算法,我们需要计算出每个可下位置的得分,单个位置价值评分大体逻辑与上面两步基本一样,是先获取当前棋盘可落子的位置,接着遍历与之相连的各种可能性。但由于立体四子棋支持斜着四子连成,所以需要额外注意,此时展开需要分为SimpleFlat与Non-SimpleFlat。具体区别在于:

红色位置需要计算斜着连成的各种情况,而黑色区域的棋子不需要。所以在遍历时如果判断需要Non-SimpleFlat,则需要把更多的(斜着的若干种可能)添加到list中进行计算。

bool ifSimpleFlat(PicesPos pos)
{
if(pos.x == && pos.y == ||
pos.x == && pos.y == ||
pos.x == && pos.y == ||
pos.x == && pos.y == ||
pos.x == && pos.y == ||
pos.x == && pos.y == ||
pos.x == && pos.y == ||
pos.x == && pos.y == )
return true;
else
return false;
} int ChessBoard::getPosValue(int board[][][],PicesPos *pos, chessPicesStatus side)
{
PicesFlatDataList flatList; // cout<<"x:"<<pos->x<<" y:"<<pos->y<<" z:"<<pos->z<<endl;
if(ifSimpleFlat(*pos))
{
/*
[0] -
[1] |
[2] /-
[3] -\
[4] /_
[5] _\
*/
flatData temp0;
temp0.a = board[][pos->y][pos->z];
temp0.b = board[][pos->y][pos->z];
temp0.c = board[][pos->y][pos->z];
temp0.d = board[][pos->y][pos->z];
flatList.push_back(temp0); flatData temp1;
temp1.a = board[pos->x][][pos->z];
temp1.b = board[pos->x][][pos->z];
temp1.c = board[pos->x][][pos->z];
temp1.d = board[pos->x][][pos->z];
flatList.push_back(temp1); flatData temp2;
temp2.a = board[][pos->y][];
temp2.b = board[][pos->y][];
temp2.c = board[][pos->y][];
temp2.d = board[][pos->y][];
flatList.push_back(temp2); flatData temp3;
temp3.a = board[][pos->y][];
temp3.b = board[][pos->y][];
temp3.c = board[][pos->y][];
temp3.d = board[][pos->y][];
flatList.push_back(temp3); flatData temp4;
temp4.a = board[pos->x][][];
temp4.b = board[pos->x][][];
temp4.c = board[pos->x][][];
temp4.d = board[pos->x][][];
flatList.push_back(temp4); flatData temp5;
temp5.a = board[pos->x][][];
temp5.b = board[pos->x][][];
temp5.c = board[pos->x][][];
temp5.d = board[pos->x][][];
flatList.push_back(temp5); flatData temp6;
temp6.a = board[pos->x][pos->y][];
temp6.b = board[pos->x][pos->y][];
temp6.c = board[pos->x][pos->y][];
temp6.d = board[pos->x][pos->y][];
flatList.push_back(temp6);
}
else
{
/*
[0] -
[1] |
[2] /-
[3] -\
[4] /_
[5] _\
[6] -|/
[7] -|\
*/
flatData temp0;
temp0.a = board[][pos->y][pos->z];
temp0.b = board[][pos->y][pos->z];
temp0.c = board[][pos->y][pos->z];
temp0.d = board[][pos->y][pos->z];
flatList.push_back(temp0); flatData temp1;
temp1.a = board[pos->x][][pos->z];
temp1.b = board[pos->x][][pos->z];
temp1.c = board[pos->x][][pos->z];
temp1.d = board[pos->x][][pos->z];
flatList.push_back(temp1); flatData temp2;
temp2.a = board[][pos->y][];
temp2.b = board[][pos->y][];
temp2.c = board[][pos->y][];
temp2.d = board[][pos->y][];
flatList.push_back(temp2); flatData temp3;
temp3.a = board[][pos->y][];
temp3.b = board[][pos->y][];
temp3.c = board[][pos->y][];
temp3.d = board[][pos->y][];
flatList.push_back(temp3); flatData temp4;
temp4.a = board[pos->x][][];
temp4.b = board[pos->x][][];
temp4.c = board[pos->x][][];
temp4.d = board[pos->x][][];
flatList.push_back(temp4); flatData temp5;
temp5.a = board[pos->x][][];
temp5.b = board[pos->x][][];
temp5.c = board[pos->x][][];
temp5.d = board[pos->x][][];
flatList.push_back(temp5); flatData temp6;
temp6.a = board[pos->x][pos->y][];
temp6.b = board[pos->x][pos->y][];
temp6.c = board[pos->x][pos->y][];
temp6.d = board[pos->x][pos->y][];
flatList.push_back(temp6); if(pos->x == && pos->y == ||
pos->x == && pos->y == ||
pos->x == && pos->y == ||
pos->x == && pos->y == )
{
flatData temp7;
temp7.a = board[][][];
temp7.b = board[][][];
temp7.c = board[][][];
temp7.d = board[][][];
flatList.push_back(temp7); flatData temp8;
temp8.a = board[][][];
temp8.b = board[][][];
temp8.c = board[][][];
temp8.d = board[][][];
flatList.push_back(temp8);
}
else
{
flatData temp7;
temp7.a = board[][][];
temp7.b = board[][][];
temp7.c = board[][][];
temp7.d = board[][][];
flatList.push_back(temp7); flatData temp8;
temp8.a = board[][][];
temp8.b = board[][][];
temp8.c = board[][][];
temp8.d = board[][][];
flatList.push_back(temp8);
} } int val = getFlatPicesValue(flatList,side); return val; }

至此,我们已经完成了棋盘的评估函数,下一章我们将讨论极值搜索算法,也就是我们真正的博弈树函数。

新手立体四子棋AI教程(2)——价值评估函数的更多相关文章

  1. 新手立体四子棋AI教程(1)——基础扫盲

    一.引言 最近身边好几个朋友开始玩立体四子棋,激起了我的好奇心.那么首先来说什么是[立体四子棋],规则又是如何呢? 上图即为立体四子棋,规则类似于五子棋四子连在一起,但是四子棋更加多样.丰富.不仅可以 ...

  2. 新手立体四子棋AI教程(3)——极值搜索与Alpha-Beta剪枝

    上一篇我们讲了评估函数,这一篇我们来讲讲立体四子棋的搜索函数. 一.极值搜索 极值搜索是game playing领域里非常经典的算法,它使用深度优先搜索(因为限制最大层数,所以也可以称为迭代加深搜索) ...

  3. 新手立体四子棋AI教程(4)——启发式搜索与主程序

    通过前面几篇文章的学习,我们的四子棋程序已经有了框架.搜索几大部分,但是还有着不少问题,我们的程序只能迭代很有限的步骤,导致棋力低下,在这一篇我们将通过启发式搜索极大的优化搜索效率. 一.原因 我们之 ...

  4. swing桌面四子棋程序开发过程中遇到的一些问题记录(二)

    第二个遇到的问题是将JButton按钮设置成透明的按钮.首先UI给我一张透明的图片,如果我直接给Button按钮设置背景图片的话,是没有透明的效果的,只会留下白色的底,设置前后的效果如下图 制作透明的 ...

  5. codevs1004四子连棋[BFS 哈希]

    1004 四子连棋   时间限制: 1 s   空间限制: 128000 KB   题目等级 : 黄金 Gold   题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗 ...

  6. Codevs p1004 四子连棋

                          四子连棋 题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋子,7颗黑色棋子,有两个空白地带,任何一颗黑白棋子都可以向 ...

  7. 【宽度优先搜索】神奇的状态压缩 CodeVs1004四子连棋

    一.写在前面 其实这是一道大水题,而且还出在了数据最水的OJ上,所以实际上这题并没有什么难度.博主写这篇blog主要是想写下一个想法--状态压缩.状态压缩在记录.修改状态以及判重去重等方面有着极高的( ...

  8. codevs 1004 四子连棋

    1004 四子连棋  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold     题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白 ...

  9. codevs 1004 四子连棋 BFS、hash判重

    004 四子连棋 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold       题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋 ...

随机推荐

  1. 硬盘分区表格式GUID和MBR知识普及

    我们的电脑硬盘分区格式一共有两种,一种是GUID(GPT),一种是MBR 如果你的电脑原装系统是win8或者以上的,那么他的硬盘分区表格式为GUID(GPT)格式的:如果是win7以下的,那么一般就是 ...

  2. EFI、UEFI、MBR、GPT的区别

    UEFI.GPT.MBR是什么?这些专业术语不难理解,UEFI属于主板类名词,其作用类似于BIOS.GPT.MBR则属于硬盘类名词,它们的作用类似一艘航母的骨架,有了这个骨架,我们才可以进行细致到诸如 ...

  3. FusionWidgets Cylinder图

    1.数据源 Cylinder.xml: <?xml version="1.0" encoding="UTF-8"?> <chart palet ...

  4. Caused by: java.lang.ClassNotFoundException: net.sf.ezmorph.Morpher

    1.错误描述 Exception in thread "main" java.lang.NoClassDefFoundError: net/sf/ezmorph/Morpher a ...

  5. IE浏览器兼容的常见问题及解决方案

    常见6个问题及解决方案 1 IE6/IE7对display:inline-block的支持还欠缺 就是我们做导航栏时通常会用到<ul><li>标签去写,在现在一些主流的浏览器中 ...

  6. 利用scrapy模拟登录知乎

    闲来无事,写一个模拟登录知乎的小demo. 分析网页发现:登录需要的手机号,密码,_xsrf参数,验证码 实现思路: 1.获取验证码 2.获取_xsrf 参数 3.携带参数,请求登录 验证码url : ...

  7. 前台序列化传过来的值,后台获取之后封装到map当中,让后在转化成json格式,最后在把json里面的参数里面的某一个值进行分割,最后在存到json格式的数据中去。

    一,html脚本 <script type="text/javascript"> $(function() { $(".btn-submit").c ...

  8. tomcat原理(三)结合公司tomcat的用法的在理解

    一,server.xml文件 <?xml version="1.0" encoding="UTF-8"?> <Server port=&quo ...

  9. httppost的用法(NameValuePair(简单名称值对节点类型)核心对象)

    一,案例一 定义了一个list,该list的数据类型是NameValuePair(简单名称值对节点类型),这个代码多处用于Java像url发送Post请求.在发送post请求时用该list来存放参数. ...

  10. 吐血整理:人工智能PDF中文教材资源包2.73G基本包含全部学习资料-人工智能学习书单

    吐血整理:人工智能PDF中文教材资源包2.73G基本包含全部学习资料 人工智能学习书单(关注微信公众号:aibbtcom获取更多资源) 文末附百度网盘下载地址 人工神经网络与盲信号处理 人工神经网络与 ...