终结五子棋不是一个很难的问题,和普通的遍历求解问题没多大区别。只是计算量稍微大点,设计的时候需要考虑系统性层次性逐步发展的观点 ,不可能三两天之内完成很精妙的算法。因此终结五子棋算不上卓有成效的工作,只是解了一个状态空间稍微大点的遍历题目而已,所需要的全部知识只是C语言、二叉树和对五子棋规则的了解,并不需要多么好的棋力才可以写程序,只是将想法赋予机器。
五子棋终结者终结五子棋的计算引擎分为三层,上层调用下层,从上至下依次为
>>目标过程层。
受限于计算机能力,五子棋终结者是不可能一次搜索就被全面终结的,而是不断地从主要干支到次要枝叶到全面终结的一个目标渐进过程。此层引擎只是一个for循环,逐步放大终结目标的宽度,从5到10到30到50到225,当到225时,五子棋就被完全地终结了。
>>策略引擎层。
最佳优先与或求解树引擎。不要看到名字这么复杂,其实就是一个不断扩展发展M叉树。让最优的棋子获得CPU,棋子的优先度根据下层的结果动态计算调整,也称作反馈,直到分支在当前的目标宽度下被终结。
>>VC攻击引擎层。
VCF和VCT,简称VC,也就是连续攻击取胜引擎。求解速度和求解严格精确至关重要。
如何在0.01S内进行深度为几十步的攻击?计算攻防时黑与白之间的无关性以及各自的相关性。
无关性的考虑可以化解对方的随意反攻,相关性的考虑可以使自己的进攻关联,保持节奏和组织性,不至盲目,二者结合使自己的进攻如行云流水,长驱直入,势如破竹。
1.00版本的vc设计是近似的,因为在VC中考虑了宽度估计,而不是全面地推理。因此终结也是近似的,很容易打败。1.20版本vc设计是严格的,但是程序中“相邻三子形成的连活三只需防守与两边的两个空白位置”是一条有漏洞的攻防逻辑,因此1.20版本也在出来一个月后被打败。1.22版本改正了1.20版本的上述漏洞,只改动了一个字符,也就是紧邻活三的防守掩码。以前我所认为的很严格的推理逻辑在后来仍然被发现了意外漏洞的存在,因此对于1.22的VC引擎,我也不知道是否还有逻辑漏洞存在,暂时没人打败1.22并不能说明漏洞不存在。
以上算法实现后,执行终结命令,经过半个月的连续计算后,会生成一个完整的地毯必胜树,包含大约百万个棋子节点,树的所有叶子都是可以VC求解的,如果VC求解引擎不存在漏洞,那么五子棋是必胜的。
终结者程序很小,算法部分的代码只有一万多行,编译结果只有一百多K,而且运行只需要几M的内存,必胜树只有百来万个节点。
下面是读取必胜树的代码,你可以用下面的函数操作终结者资源里面的必胜树:
//将资源文件读取到数组book[]中,调用此函数就可以生成一颗必胜树
//int key=1;//!!!!!!!
//传入当前key和指向子孙的指针root,将root指向的树生长完整
//上层调用此函数之时key已经指向[的下一个字符(,我的处理:
//(--添加root的孩子
//)--添加孩子完毕,可有可无
//[--为孩子添加子孙
//]--root树生长完成
//参数为长子的指针的指针和父亲的指针
void make(NODEOFTREE ** root,NODEOFTREE *parent)
{
/* *root指向节点,我们要给*root赋值 */
int rootOK=0;
NODEOFTREE *rightC=NULL; /* rightC总是指向刚刚加入的兄弟 */
NODEOFTREE *rightT=NULL; /* rightT指向新开辟兄弟 */
if(book[key]!='(')
{
F5_PRINT(1," make book[key]!='(' ");
return;
}
do
{
switch(book[key])
{
case '[' : //fprintf(outcourse," 开始生孩子... /n");
key++;
make(&(rightC->down),rightC);
break;
case ']' : //fprintf(outcourse," 孩子完成... /n");
key++;
return ;
case '(' : /* 加一个兄弟 */
rightT=(NODEOFTREE *)malloc(sizeof(NODEDATA));
if(rightT==NULL)
F5_PRINT(1," make rightT==NULL");
node_mum++;
memset(rightT,0,sizeof(NODEDATA));
rightT->data[0]=cover(book[key+1],1);
rightT->data[1]=cover(book[key+2],2);
if(key==1)
rightT->data[2]=1;/*第一个子是黑子*/
else
{/*和父亲的颜色相反*/
if(parent->data[2]==1)rightT->data[2]=2;
if(parent->data[2]==2)rightT->data[2]=1;
}
rightT->right=NULL;
rightT->down=NULL;
if(rightC!=NULL)
{
rightC->right=rightT;
}
rightC=rightT;
if(rootOK!=1)/*兄弟的一个,即老大*/
{*root=rightT; rootOK=1;
}
key=key+3;
// fprintf(outcourse,"加一个兄弟(%c-%c-%c)/n",rightC->data[0],rightC->data[1],rightC->data[2]);
break;
case ')' :
key++;
break;
case '/0': //fprintf(outcourse,"不可能有此一字,在最后一个]的时候返回!/n");
break;
default : key++;
//fprintf(outcourse,"字符串中有错误的字符!/n");
break;
}
} while(book[key]!='/0');
return;
}