对于棋类软件的搜索算法经前人的努力已形成了较为成熟的Alpha-Beta搜索算法以及其它一些辅助增强算法。所以小生在自己的程序中直接借鉴了Alpha-Beta搜索算法并辅以了历史启发。对此两者王小春的《PC 游戏编程(人机博弈)》和ElephantBoard的主页 http://www.elephantbase.net/上都有非常详细的介绍,所以这里我只简单介绍一下算法的主体思想:

首先对于棋类游戏存在一棵“博弈树”——树的每一个结点代表棋盘上的一个局面,对每一个局面(结点)根据不同的走法又产生不同的局面(生出新的结点),如此不断直到再无可选择的走法(棋局结束)。

现在假定甲乙两方下棋,甲胜的局面是一个极大值(一个正数),那么乙胜的局面则是一个极小值(极大值的负值),和棋的局面则是零值或是接近零的值。如此,则轮到甲走棋时他会选择生成结点的值最大的走法,相反轮到乙走棋时他会选择生成结点的值最小的走法(当然,搜索会向下推导多步后选择一个分值对当前下棋方有利的着法)。这就是“最大-最小”的基本思想。这样搜索函数可以通过搜索以当前局面为根结点、限定层数以内的整棵树来获得一个最佳的着法。不幸的是,博弈树相当庞大(它会成指数增长),因而搜索整棵树是一件相当费时的工作。

然而,下棋是一个你来我往的交替进行并且相互“较劲”的过程,由于每一方都会尽可能将局面导向对自己有利而对对方不利的形势。所以有些“暂时”看来很不错的局面由于可能会产生很糟糕的局面因而根本没有考虑的价值。所以当你看到某个局面有可能产生很糟糕的局面时(确切地说这里的“很糟糕”是与之前所分析的情况相比较而言的),你应当立刻停止对其剩余子结点的分析——不要对它再报任何幻想了,如果你选择了它,则你必将得到那个很糟糕的局面,甚至更糟……这样一来便可以很大程度上减少搜索的工作量,提高搜索效率。这称为“树的剪裁”。为了便于大家理解,下面我援引ElephantBoard的主页http://www.elephantbase.net/上所翻译的《Alpha-Beta搜索》中的一个“口袋的例子”,原文作者是Bruce Moreland (brucemo@seanet.com)。
口袋的例子:

比如你的死敌面前有很多口袋,他和你打赌赌输了,因此他必须从中给你一样东西,而挑选规则却非常奇怪:
每个口袋里有几件物品,你能取其中的一件,你来挑这件物品所在的口袋,而他来挑这个口袋里的物品。你要赶紧挑出口袋并离开,因为你不愿意一直做在那里翻口袋而让你的死敌盯着你。
假设你一次只能找一只口袋,在找口袋时一次只能从里面摸出一样东西。
很显然,当你挑出口袋时,你的死敌会把口袋里最糟糕的物品给你,因此你的目标是挑出“诸多最糟的物品当中是最好的”那个口袋。
你很容易把最小-最大原理运用到这个问题上。你是最大一方棋手,你将挑出最好的口袋。而你的死敌是最小一方棋手,他将挑出最好的口袋里尽可能差的物品。运用最小-最大原理,你需要做的就是挑一个有“最好的最差的”物品的口袋。
假设你可以估计口袋里每个物品的准确价值的话,最小-最大原理可以让你作出正确的选择。我们讨论的话题中,准确评价并不重要,因为它同最小-最大或Alpha-Beta的工作原理没有关系。现在我们假设你可以正确地评价物品。
最小-最大原理刚才讨论过,它的问题是效率太低。你必须看每个口袋里的每件物品,这就需要花很多时间。
那么怎样才能做得比最小-最大更高效呢?
我们从第一个口袋开始,看每一件物品,并对口袋作出评价。比方说口袋里有一只花生黄油三明治和一辆新汽车的钥匙。你知道三明治更糟,因此如果你挑了这只口袋就会得到三明治。事实上只要我们假设对手也会跟我们一样正确评价物品,那么口袋里的汽车钥匙就是无关紧要的了。
现在你开始翻第二个口袋,这次你采取的方案就和最小-最大方案不同了。你每次看一件物品,并跟你能得到的最好的那件物品(三明治)去比较。只要物品比三明治更好,那么你就按照最小-最大方案来办——去找最糟的,或许最糟的要比三明治更好,那么你就可以挑这个口袋,它比装有三明治的那个口袋好。
比方这个口袋里的第一件物品是一张20美元的钞票,它比三明治好。如果包里其他东西都没比这个更糟了,那么如果你选了这个口袋,它就是对手必须给你的物品,这个口袋就成了你的选择。
这个口袋里的下一件物品是六合装的流行唱片。你认为它比三明治好,但比20美元差,那么这个口袋仍旧可以选择。再下一件物品是一条烂鱼,这回比三明治差了。于是你就说“不谢了”,把口袋放回去,不再考虑它了。
无论口袋里还有什么东西,或许还有另一辆汽车的钥匙,也没有用了,因为你会得到那条烂鱼。或许还有比烂鱼更糟的东西(那么你看着办吧)。无论如何烂鱼已经够糟的了,而你知道挑那个有三明治的口袋肯定会更好。

“最大-最小”的思想再加上“对树的剪裁”就是Alpha-Beta搜索算法的核心。
下面就是CChessSearch.h的代码实现,其中涉及的“历史启发”和“着法排序”的具体实现会在下篇文章中给出。

// CChessSearch.h   #include "HistoryHeuristic.h"
#include "SortMove.h"
#include "CChessEvaluate.h"   /// Data Define ///   int nMaxSearchDepth;  // 最大搜索深度
CCHESSMOVE cmBestMove;  // 存储最佳走法   /// Function Prototype    // 通过AlphaBeta搜索+历史启发搜索得到一部最佳着法并执行之
CCHESSMOVE SearchAGoodMove();   // AlphaBeta搜索+历史启发,nDepth为搜索深度,
// alpha初始为极小值,beta初始为极大值
int AlphaBeta_HH( int nDepth, int alpha, int beta );   // 判断游戏是否结束,若结束则根据当前下棋方返回相应的极值,否则返回0
//  当前下棋方胜则返回极大值,当前下棋方败则返回极小值(下棋方追求极大值)
int IsGameOver( int fWhoseTurn );   // 执行着法,返回ptTo位置的棋子状况。即若吃掉子返回被吃掉的子,没有吃子则返回0
BYTE DoMove( CCHESSMOVE * move );   // 撤销执行着法,恢复原位置的棋子状况。nCChessID为原ptTo位置的棋子状况
void UndoMove( CCHESSMOVE * move, BYTE nCChessID );   // Programmer-Defined Function    CCHESSMOVE SearchAGoodMove()
{ResetHistoryTable();int i;i = AlphaBeta_HH( nMaxSearchDepth, -MaxValue, MaxValue );   DoMove( &cmBestMove );   return cmBestMove;
}   int AlphaBeta_HH( int nDepth, int alpha, int beta )
{int nScore;int nCount;BYTE nCChessID;   int i;i = IsGameOver( ( nMaxSearchDepth - nDepth ) % 2 );if( i != 0 )  // 游戏结束return i;   if( nDepth == 0 )  // 叶子节点return Eveluate( ( nMaxSearchDepth - nDepth ) % 2 );   nCount = GenerateMove( ( nMaxSearchDepth - nDepth ) % 2 , nDepth );   for( i = 0; i < nCount; i ++ ) // 取历史表得分{MoveList[nDepth][i].nScore = GetHistoryScore( & MoveList[nDepth][i] );}   MergeSort( MoveList[nDepth], nCount );  // 对着法进行降序排序   int iBestmove = -1;   for( i = 0; i < nCount; i ++ ){   nCChessID = DoMove( & MoveList[nDepth][i] );  // 执行着法(生成新节点)nScore = - AlphaBeta_HH( nDepth - 1, -beta, -alpha );//递归调用AlphaBeta_HHUndoMove( & MoveList[nDepth][i], nCChessID );  // 撤销执行(删除节点)   if( nScore > alpha ){alpha = nScore;    // 保留最大值   if( nDepth == nMaxSearchDepth )cmBestMove = MoveList[nDepth][i];   iBestmove = i;  // 保存最佳走法的序号}if( alpha >= beta ){iBestmove = i;  // 保存最佳走法的序号break;}   }if( iBestmove != -1 )EnterHistoryScore( & MoveList[nDepth][iBestmove], nDepth ); //记录历史得分   return alpha;
}    int IsGameOver( int fWhoseTurn )
{int x, y ;   if( fWhoseTurn == RED )  // 轮到红方下棋,只可能是红帅已经被吃{// 红方九宫for( x = 3; x <= 5; x ++ )for( y = 0; y <= 2; y ++ ){if( CChessBoard[x][y] == RED_K ){return 0;  // 红帅没被吃,则说明游戏尚未结束}}   return -MaxValue ;  // 返回失败极值(已验证应为 -MaxValue )   }else // 轮到黑方下棋,只可能是黑将已经被吃{// 黑方九宫for( x = 3; x <= 5; x ++ )for( y = 9; y >= 7; y -- ){if( CChessBoard[x][y] == BLACK_K ){return 0;  // 黑将没被吃,则说明游戏尚未结束}}   return -MaxValue ;  // 返回失败极值(已验证应为 -MaxValue )   }   }   BYTE DoMove( CCHESSMOVE * move )
{BYTE nCChessID;   //保留目标位置的棋子状况nCChessID = CChessBoard[ move->ptTo.x ][ move->ptTo.y ] ;   //移动子到目标位置CChessBoard[ move->ptTo.x ][ move->ptTo.y ]= CChessBoard[ move->ptFrom.x ][ move->ptFrom.y ] ;CChessBoard[ move->ptFrom.x ][ move->ptFrom.y ] = 0 ;   return nCChessID;
}   void UndoMove( CCHESSMOVE * move, BYTE nCChessID )
{//将子移动回原处CChessBoard[ move->ptFrom.x ][ move->ptFrom.y ]= CChessBoard[ move->ptTo.x ][ move->ptTo.y ] ;   //恢复目标位置的子CChessBoard[ move->ptTo.x ][ move->ptTo.y ] = nCChessID ;
}   // end of CChessSearch.h

中国象棋软件-引擎实现(四)搜索算法相关推荐

  1. 中国象棋软件-引擎实现(七)测试程序

    之前我们已经讲了实现一个中国象棋软件的所有要素,本篇我们只是粗略地建一个工程再添加一点文件使得我们能看到程序的运行情况如何. 在界面完成之前,我先建了一个Win32控制台项目(学生朋友们对这个最熟悉也 ...

  2. [转载] 中国象棋软件-引擎实现(一)概述

    2005年6月我系第二批科技小组的项目正式确定为实现一款中国象棋对弈软件.基本功能包括人机对战.网络对战.我负责开发人机对战的引擎部分,也就是让计算机下棋.经过了暑假整整两个月的学习与实践,我终于初步 ...

  3. 中国象棋软件-引擎实现(六)局面评估

    前面已经讲过了棋局表示.着法生成.搜索算法(包括搜索辅助), 在象棋程序中如果说搜索算法是心脏,那么局面评估就是大脑.搜索算法负责驱动整个程序,而局面评估则负责对搜索的内容进行判断评价.因而搜索与局面 ...

  4. [转载] 中国象棋软件-引擎实现(二)棋局表示

    对于棋盘的表示当前比较先进的思想是"位棋盘"."位棋盘"用于国际象棋非常便捷,因为国际象棋的棋盘正好有64个格子,可以将一个棋盘的信息用一个64位的变量来表示. ...

  5. 中国象棋软件-引擎实现(二)棋局表示

    对于棋盘的表示当前比较先进的思想是"位棋盘"."位棋盘"用于国际象棋非常便捷,因为国际象棋的棋盘正好有64个格子,可以将一个棋盘的信息用一个64位的变量来表示. ...

  6. 中国象棋软件-引擎实现(一)概述

    程序的基本框架: 从程序的结构上讲,大体上可以将本程序划分为四大部分: 棋局表示. 着法生成. 搜索算法. 局面评估 程序的大概的思想是: 首先使用一个数据结构来描述棋局信息,对某一特定的棋局信息由着 ...

  7. 中国象棋软件-引擎实现(三)着法生成

    我们的程序需要让电脑能够在轮到它走子的时候执行一个它认为对它最有利的着法,那前提就是它要有诸多(也可能是唯一)可供选择的着法,提供所有可选着法的"清单"就是我们的着法生成器所要完成 ...

  8. 中国象棋通用引擎协议 UCCI

    概述 中国象棋通用引擎协议 (Universal Chinese Chess Protocol,简称UCCI),是一种象棋界面和象棋引擎之间的基于文本的通讯协议. 规范通用引擎协议 UCCI 协议,为 ...

  9. 中国象棋通用引擎协议

    一.概述 中国象棋通用引擎协议(Universal Chinese Chess Protocol,简称UCCI),是一种象棋界面和象棋引擎之间的基于文本的通讯协议.设立中国象棋通用引擎协议的目的有: ...

最新文章

  1. Java 二分法查找
  2. Matplotlib使用scatter函数在Python中绘制气泡图(bubble plot)、通过size参数指定数据点的大小
  3. 2013年中国数据库大会PPT
  4. ble gatt核心结构
  5. 查询正在运行的ORACLE进程
  6. Linux运维系统工程师与java基础学习系列-6
  7. Maven学习笔记(待完善)
  8. ubuntu如何进入超级权限模式,退出超级权限模式,sudo su——root权限
  9. 无线服务器密码让别人改了,wifi密码被改了怎么办_wifi密码被别人改了怎么办?-192路由网...
  10. 【Spring Cloud】注册中心-Nacos
  11. warning C4995: “....”: 名称被标记为 #pragma deprecated
  12. 树莓派 —— 树莓派安装字体
  13. FOC中的Clarke变换和Park变换详解(动图+推导+仿真+附件代码)
  14. MD5算法如何被破解
  15. WGS84与GCJ02、BD09经纬度坐标转换介绍
  16. iOS逆向一iPhone手机越狱/APP脱壳/IPA签名
  17. 老派道场普陀山 海天佛国的禅修氛围
  18. 使用Guardium大数据智能解决方案释放Guardium数据的价值
  19. 大数据平台测试-软件测试常见面试回答(持续更新)
  20. 设计模式(Java随笔)—备忘录模式

热门文章

  1. 从补课到“上瘾”,“川味火锅第一股”的上云故事
  2. CutterMan 一款神奇的切图工具
  3. TensorFlow GUP安装
  4. 基于ASP.Net写的一个购物网站
  5. 草莓病虫害数据集免费下载
  6. [RO]机器人传感器 Sensor
  7. 【动手学深度学习】多层感知机(MLP)
  8. 测谎考验 straight-face test
  9. 苹果usbc音频android,Apple USB-C转3.5mm使用感受
  10. 王译潇20162314 实验报告五