FireMonkey3D之中国象棋程序设计(六)完善算法

这篇具有很好参考价值的文章主要介绍了FireMonkey3D之中国象棋程序设计(六)完善算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。  

  这一章主要完善算法。本章目标:

  • 实现开局库;
  • 实现PVS(主要变例搜索);
  • 把根节点的搜索单独处理,增加搜索的随机性;
  • 克服由长将引起的置换表的不稳定性。

  6.1 实现开局库

  开局库几乎是每个象棋程序必备的部件,它的好处是:

  (1) 即使再笨的程序,开局库能使得它们在开局阶段看上去不那么业余;

  (2) 通过随机选择走法,让开局灵活多变,增加对弈的趣味性。

  我们程序使用开源象棋程序 ElephantEye 的开局库Book.dat文件,开局库文件的结构:  

type BookItem=record
  dwLock:Cardinal;
  wmv, wvl:Word;
end;

  其中,dwLock 记录了局面 Zobrist 校验码中的 dwLock1wmv 是走法,wvl 是权重(随机选择走法的几率,仅当两个相同的 dwLock 有不同的 wmv 时,wvl 的值才有意义)

  搜索一个局面时,首先不做Alpha-Beta搜索,而是查找开局库中有没有对应的项,有的话就取出所有相同项,从中随机选择一个 wmvElephantEye 为了压缩开局库的容量,所有对称的局面只用一项,所以当一个局面在开局库中找不到时,还应该试一下它的对称局面是否在 BookTable 中。在这里我们将最新的Book.dat文件转化成了SQLite数据库文件,这样就不需要BookItem。在这里说明下,由于我们程序局面记录使用的是10X9的二维数据,起始是(0,0)。象棋巫师使用的是长度256的一维数组记录局面,转换成二维数组时,纵向、横向均平移了3个单位,在我们程序中相当于从(3,3)点为起始。为了使用象棋巫师的开局库,我们必须与之兼容,也要转换成一维数组,开局库在制作时,wmv 走法也要还原成我们程序的走法,这里我们已经处理好了,直接用就可以。以下函数要做变化(csCommon单元):

function PtToInteger(p:TPoint):Byte;
begin
  Result:=P.X +P.Y shl 4+51;//加51是为了与象棋巫师对应,相当于将起点定为(3,3)
end;

  以下为开局库搜索代码(我们程序使用了SQLiteTable开源文件,需要附带SQLite.dll文件,不想附带DLL文件,可以将其改为FireDAC):

{加载开局库}
procedure LoadBook;
begin
  BookDB:=TSQLiteDatabase.Create('book.db3');
  BookDB.ExecSQL('create temp table TBook as select * from Books');//创建内存表
  Randomize;
end;
{查找开局}
function SearchBook:Integer;
var
  i, vl, nBookMoves,mv:Integer;
  mvs,vls:array[Byte]of Integer;
  bMirror:Boolean;
  dwLock:Cardinal;
  posMirror:TPieceMove;
  s,d:TPoint;
begin
  // 搜索开局库的过程有以下几个步骤
  // 1. 搜索当前局面
  bMirror:= FALSE;
  dwLock:= pcMove.zobr.dwLock1;
  BookTB:=BookDB.GetTable('select * from TBook where dwLock='+Inttostr(dwLock));
  // 2. 如果没有找到,那么搜索当前局面的镜像局面
  if BookTB.RowCount =0 then
  begin
    bMirror:=TRUE;
    pcMove.Mirror(posMirror);
    dwLock:=posMirror.zobr.dwLock1;
    BookTB:=BookDB.GetTable('select * from TBook where dwlock='+Inttostr(dwLock));
  end;
  // 3. 如果镜像局面也没找到,则立即返回

  if BookTB.RowCount =0 then
     Exit(0);
  // 4. 把走法和分值写入到"mvs"和"vls"数组中
  vl:=0;nBookMoves:= 0;
  for i:=0 to BookTB.RowCount-1 do
  begin
    if bMirror then
       mv:=MIRROR_MOVE(BookTB.FI(1))//走法
    else
       mv:=BookTB.FI(1);
    s:=GetSrc(mv);
    d:=GetDest(mv);
    if pcMove.canMove(s,d) then
    begin
      mvs[nBookMoves]:= mv;
      vls[nBookMoves]:= BookTB.FI(2);//权重
      vl:=vl+vls[nBookMoves];
      Inc(nBookMoves);
      if nBookMoves= 256 then  // 防止"book.db3"中含有异常数据
        break;
	
    end;
  BookTB.Next;
  end;
  if vl = 0 then
    Exit(0); // 防止"BOOK.db3"中含有异常数据
  // 5. 根据权重随机选择一个走法
  vl:= Random(vl);//这样权重也是随机的,有什么区别?
  for i:= 0 to nBookMoves-1 do
  begin
    vl:=vl-vls[i];
    if vl < 0 then
      break;
  end;
  Result:= mvs[i];
end;

  6.2 根节点的特殊处理

  现在我们的程序一开局不会总是跳正马了,根据 ElephantEye 提供的开局库,它大部分时候走中炮,有时也走仙人指路(进兵)或飞相。可是当它脱离开局库时,仍然摆脱不了思维的单一性,例如我们第一步走边兵(开局库中当然没有这个局面),它仍旧只会跳同一边的正马。

  一个解决办法是:在根节点处,让一个不是最好的走法也能在一定的几率取代前一个走法。

  我们把根节点的搜索函数单独分离,这样做有很多好处:

  (1) 处理思考的随机性;

  (2) 没有必要尝试 Beta 截断(根节点处 Beta 始终是 +MATE_VALUE)

  (3) 省略了检查重复局面、获取置换表、空步裁剪等步骤。

  代码如下:

// 根节点的Alpha-Beta搜索过程
function SearchRoot(nDepth:Integer):Integer;
var
  vl, vlBest, mv, nNewDepth:Integer;
  Sort:SortStruct;
  s,d:TPoint;
begin
  vlBest:= -MATE_VALUE;
  Sort.Init(Search.mvResult);
  with pcMove do
  while True do
  begin
    mv:=Sort.Next;
    if mv=0 then Break;
    s:=GetSrc(mv);d:=GetDest(mv);
    if MakeMove(s,d) then
    begin
      nNewDepth:= InCheck.ToInteger+nDepth- 1;// 如果老将被攻击,就多搜索一层
      if vlBest = -MATE_VALUE then// 主要变例搜索
        vl:= -SearchFull(-MATE_VALUE, MATE_VALUE, nNewDepth, True)
      else
      begin
        vl:= -SearchFull(-vlBest - 1, -vlBest, nNewDepth);
        if vl > vlBest then
          vl:= -SearchFull(-MATE_VALUE, -vlBest, nNewDepth, True);
      end;
      UndoMakeMove;
      if vl > vlBest then
      begin
        vlBest:= vl;
        Search.mvResult:= mv;
        if (vlBest >-WIN_VALUE)and(vlBest < WIN_VALUE) then
        begin
           //// 增加电脑走棋的随机性
           vlBest:=vlBest + random(RANDOM_MASK) - random(RANDOM_MASK);
           if vlBest=DrawValue then
              vlBest:=vlBest - 1;
        end;
      end;
    end;
  end;
  RecordHash(HASH_PV, vlBest, nDepth, Search.mvResult);
  SetBestMove(Search.mvResult, nDepth);
  Result:=vlBest;
end;

  6.3  PVS主要变例搜索

 文章来源地址https://www.toymoban.com/news/detail-807416.html

  经过前面的工作,走法已经得到了很好的排序,好的走法会先被搜索。这是PVS的基础。

FireMonkey3D之中国象棋程序设计(六)完善算法 

                  图a                                                                                       图b

  假设第一个走法是最好的走法,没有引发剪枝,A点的搜索区间为(0, 100),走法1得到估值30。由于30  > 0,所以A点的alpha变为30,以后的搜索区间变为(30, 100),所以B2点的搜索区间为(-100, -30)。

  可以进一步大胆地考虑,假设第1个走法就是最好的走法,那么后面走法得到的估值不会落在区间(30, 100)。所以从A点的第2个走法开始,要做的就是验证这种假设,搜索区间为(30, 31)。由于搜索区间很小,搜索速度会很快。返回值vl有3种情况。

  (1)vl <= 30。说明走法不比第1个走法好,假设成立。

  (2)vl >= 100。返回值比A点的原有搜索边界beta还大,应该剪枝,假设成立。

  (3)30 < vl < 100。走法比第1个走法好,假设不成立。

   第3种情况时,走法不成立,应该对该走法重新以(30, 100)区间进行搜索。如果得到40,则该走法就是最好的走法,后续搜索又对该走法进行假设验证,区间为(40, 41)。

  6.4 长将判负策略

  由于单方面长将不变作负的规则,以前的版本如果发生这种情况,想当然地给予-MATE_VALUE的值,再根据杀棋步数作调整。但是由于长将判负并不是对某个单纯局面的评分,而是跟路线有关的,所以使用置换表时就会产生非常严重的后果——某个局面的信息可能来自另一条不同的路线。

  解决办法就是:获取置换表时把“利用长将判负策略搜索到的局面”过滤掉。为此这个版本中我们把长将判负的局面定为BAN_VALUE(MATE_VALUE - 100),如果某个局面分值在WIN_VALUE(MATE_VALUE - 200)BAN_VALUE之间,那么这个局面就是“利用长将判负策略搜索到的局面”。

  我们仍旧把部分“利用长将判负策略搜索到的局面”记录到置换表,因为这些局面提供的最佳走法是有启发价值的。反过来说,如果“利用长将判负策略搜索到的局面”没有最佳走法,那么这种局面就没有必要记录到置换表了。经经过这种处理,我们的程序在杀棋阶段不再会走出莫名其妙的走法了。

以上程序未经充分测试,发现问题请及时反馈。

本章节源码百度云盘(测试程序打包在里面):

链接:中国象棋程序设计(六)置换表

提取码:1234

 

到了这里,关于FireMonkey3D之中国象棋程序设计(六)完善算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FireMonkey3D之中国象棋程序设计(四)水平效应、检查重复局面

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 上一章我们的程序终于会走棋了,不过很多时候它很低能。由于水平线效应,任何变化都只搜索固定的深度。还有,有时它会长将。我们能做哪些改进呢? 本章的目标: 用Zobrist校验码技术

    2024年01月20日
    浏览(38)
  • Qt CMake 中国象棋程序实现

    C++自学精简实践教程 目录(必读) C++数据结构与算法实现(目录) Qt 入门实战教程(目录) 为学习 Qt 的人提供一个合适的有一定难度的综合型练习项目。 在学会写代码之前,先看别人怎么写的代码。深入其中,扩展完善。 最大限度的模拟企业开发的真实场景。 运行效果 中

    2024年02月09日
    浏览(40)
  • 从0开始写中国象棋-创建棋盘与棋子

    考虑到象棋程序,其实就是数据结构与算法实现。 所以和界面相关的QT部分我们先放一放。 我们从控制台版本开始。这样大家更容易接受,也不影响开发。 后面我们会把控制台嫁接到QT上完成完整的游戏,那时候自然就水到渠成了。 中国象棋的棋盘是一个宽9列,长 5+5 = 10

    2024年02月07日
    浏览(37)
  • 中国象棋AI库AlphaZero_ChineseChess

    AlphaZero_ChineseChess是一个基于AlphaZero算法的中国象棋AI库,它是开源的,使用Python语言编写,托管在GitHub上。以下是对AlphaZero_ChineseChess库的详细介绍: 算法原理 AlphaZero_ChineseChess基于AlphaZero算法,这是一种基于自我对弈的强化学习算法,能够让AI自主学习棋局的优劣、评估策略

    2024年02月14日
    浏览(122)
  • C++实现双人中国象棋(一)——算法篇(附完整代码)

    最近突发奇想,要使用C++做一个双人象棋的程序,昨天肝了一天,终于把算法部分完成了,下面把开发过程中的经验分享一下。 开发环境:Visual Studio 2019 语言标准:C++11及以上 纠错:暂无 知识要求: 熟练掌握C++语言面向对象编程的知识(继承,多态) 掌握STL的基本操作 了

    2024年02月04日
    浏览(61)
  • C++900行代码实现中国象棋游戏规则以及相关功能

    本文章通过C++中的900行代码实现中国象棋游戏以及相关功能,主要的内容如下: 1.设置未进入游戏前的主页面; 2.绘制棋盘(如果有刚好尺寸的图片也可直接加载),包括棋盘网格,炮与兵的特殊标记绘制; 3.绘制和创建棋子,并令其初始化在棋盘的相应位置; 4.游戏开始,

    2024年02月09日
    浏览(49)
  • AI人工智能(调包侠)速成之路十五(中国象棋AI网络机器人:AI模型部署)

    神经网络模型动态加解密的技术这个以后再写吧  书接上文: AI人工智能(调包侠)速成之路十四(中国象棋AI网络机器人:AI技术综合应用实现) 神经网络模型的存储格式         我们训练的神经网络就是一堆模拟神经元的参数集合,给(他/她/它)一个输入信息就会得到

    2024年01月23日
    浏览(50)
  • 从0实现基于Alpha zero的中国象棋AI(会分为多个博客,此处讲解蒙特卡洛树搜索)

    ​ 题主对于阿尔法狗的实现原理好奇,加上毕业在即,因此选择中国象棋版的阿尔法zero,阿尔法zero是阿尔法狗的升级版。在完成代码编写的历程中,深刻感受到深度学习环境的恶劣,网络上固然资料繁多,但要么水平不行,不知所云,要么国外课程,门槛过高。因而碰壁良

    2024年02月06日
    浏览(52)
  • html Antv L7 + mapbox 实现3D地图 3D中国地图 不限于中国地图

    echarts的3D地图实在太丑了,还一堆bug 使用阿里的Antv可视化库L7,实现3D地图,底图是mapbox 参考示例:https://l7.antv.antgroup.com/zh/examples/polygon/3d#floatMap 如果不需要底图样式,可把Scene的style设置为blank 直接上代码了,vue的就不说了,项目是html的 mapbox依赖 L7依赖 body元素 实现

    2024年02月14日
    浏览(52)
  • echarts 5.0——3d中国地图和飞线

    echarts 5.0的版本样式语法与4.0及以下的语法有个别差异,使用旧语法浏览器会警告提示。 3d地图常用的实现方法有两种。一种是使用GL来实现,一种是使用叠层和阴影来实现,本文将使用叠层和阴影来实现。先看效果图: 1. 一定要使用 echarts 5.0及以上的版本; 2. echarts 5.0没有

    2024年04月13日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包