1.概率分析

四国军棋属于不完全信息博弈,我们是看不到敌方的棋子,但是可以通过棋子间的碰撞来判断敌方的子力分布情况和棋子大小的概率。

当棋子产生碰撞后,可能的判决结果有吃子、打兑、撞死3种结果,有时还会附加是否亮军旗的信息,之前的处理只是简单的把所有情况取平均值,这是不对的,因为某些情况虽然存在,但是概率特别小,如果取平均值就会对着法评分的准确性造成很大的影响,所以更好的方式应该是对每一种情况生成一个概率与对应的分数相乘最后得到一个期望分数。

概率是一个0~1的小数值,如果用float变量会严重影响速度,所以把实际概率乘以256得到一个最后的结果p<<8,在最后计算期望分数时再除以256,即sum>>8。

至于概率的计算真的是非常繁琐的一件事,代码的实现全部在以下3个函数里

  • GetEatPercent
  • GetBombPercent
  • GetKilledPercent

所有的结果都划分为吃子、打兑、撞死3种情况,最后这3个概率相加应该是1,考虑到除法的误差,最后相加的结果乘以256应该在250~256左右。

在计算概率之前先要收集2个信息,在AdjustMaxType里计算:

  • aLiveTypeSum [14]
    表示大于某个级别的并且还活着的明棋的总数,即敌方吃过子的棋
  • aLiveTypeAll[14]
    表示大于某个级别的并且还活着的明棋总数再加上所有大于该级别的暗棋包括被暗吃的

计算的时候,考虑的情况非常多,这里只举个例子简要说明一下,比如我方37吃掉对方一个暗子,那么这个概率是多少呢?首先要计算这个子的所有可能情况,这个子肯定大于等于工兵,而且不是明棋,也可能是地雷或炸弹,总数应该是

num = (aLiveTypeAll[GONGB]-aLiveTypeSum[GONGB])+nBomb+nLand;

根据之前的一些碰撞情况,我们应该知道这个棋子的最大可能,例如如果已经确定其他子是40、39,那么这个子最大的可能也就38,所以要把大于38的子排除,这里假设mxDstType是38(枚举变量是SHIZH),得到的分母是num-mxNum。

 mxNum = (aLiveTypeAll[mxDstType-1]-aLiveTypeSum[mxDstType-1]);

再来计算分子,由于37必须要吃的动,那么这个子必须要小于37,先来计算大于等于37的数量nSrc,这里src是37

nSrc = aLiveTypeAll[src]-aLiveTypeSum[src]+nBomb+nLand;

于是就得到了分子num-nSrc。考虑到敌方可能最大的子都比37小,那么这个时候是必然可以吃掉的,概率是100%,不能算出来大于100%,所以还要再处理一下

nSrc = (nSrc>mxNum)?nSrc:mxNum;

最后的结果就是

percent = ((num-nSrc)<<8)/(num-mxNum);

其他需要考虑的情况非常多非常繁琐,不再一一细说。如果是碰到大本营,就要考虑是否是军旗,这种情况要单独处理,由于出现的频率比较低,所以大概设定了一个合理的概率,并没有严格计算。

2.搜索优化

之前在生成着法的时候,效率太低,需要遍历129个棋盘位置,每个位置都要执行IsEnableMove()函数,在IsEnableMove()函数中又要搜索整个棋盘来判断着法是否合法。现在改进后每个棋子只搜索一次,和路径生成的函数类似,递归搜索相邻或铁路上直通的棋子,加入到着法链表里并做上标记,如果下次再遇到直接跳过,实现在SearchMovePath()函数里。

每一次生成着法时生成的数量非常巨大,很多都是类似重复的或者是废招,这些着法就不用向下继续递归搜索了。那么如何判断呢?现在有2步棋可以选择,下完后,敌方行棋所产生的碰撞效果不一样,那么就认为这2步棋的效果不同,否则认为这2步棋是相同的,只搜索其中一步棋即可。

如下图,排长进营和营长进营的效果是一样的,因为并不改变敌方对棋子的碰撞。

而如果司令上抬一步和上面的走法产生的效果就不一样了,因为已经改变了地方棋子与我方棋子碰撞的可能性,如下图所示,38可以和37直接接触,而在上面的局面中是无法直接接触的。

在程序中我们通过把所以如黄色箭头这样的接触全部异或起来得到一个key值并加入到hash表中,每一次搜索时先查找hash表中有每一key值,如果key值已经有了说明之前已经搜索过了就不用再继续搜索,如果还没有key值,那么把key值加入到hash表中。

每一次碰撞用4个字节表示即原棋子的dir和index、目标棋子的dir和index,用异或是为了满足结合律,异或的先后次序并不影响最后的结果

    u8 val[4];val[0] = pSrc->iDir;val[1] = pSrc->pLineup->index;val[2] = pDst->iDir;val[3] = pDst->pLineup->index;pJunqi->iKey ^= *((int*)val);

如果有2对这样的碰撞,其中一对与另一对的val中,其他相同,只是pSrc->iDir与pDst->iDir交换,最后得到的key值是相同的,因为这2对是不同的碰撞却产生的相同的key值,这不是我们所希望的,做如下修改就可以避免这种情况

    val[0] = pSrc->pLineup->index<<pSrc->iDir;val[1] = pSrc->pLineup->index;val[2] = pDst->pLineup->index<<pDst->iDir;val[3] = pDst->pLineup->index;pJunqi->iKey ^= *((int*)val);

总的步骤是,在GenerateMoveList生成着法链表后,每移动一步后,通过GetHashKey产生一个key值,最后通过CheckMoveHash检查key值是否存在决定是否继续搜索,hash表的查询和插入属于基础算法,这里就不介绍了。

     MakeNextMove(pJunqi,&p->move);iKey = GetHashKey(pJunqi);if( CheckMoveHash(&paHash,iKey) &&IsNotSameMove(p) ){if( p->move.result>MOVE ){while( !memcmp( &p->move, &p->pNext->move, 4) ){if( p->pNext->isHead ) break;p = p->pNext;}}//把局面撤回到上一步UnMakeMove(pJunqi,&p->move);goto continue_search;}else{val = CallAlphaBeta(pJunqi,depth-1,alpha,beta,iDir);//把局面撤回到上一步UnMakeMove(pJunqi,&p->move);}

另外每一次搜索时GenerateMoveList都会生成全部着法,不管有没有剪枝都会生成全部着法,之前考虑到层数深度较深时这里也是一个很大的消耗,所以重写一个AlphaBeta1函数,不先生成全部着法,每产生一步就往下搜索,产生剪枝后剩下的着法就不用生成了。但是后来发现,增加了GetHashKey函数后,GetHashKey的调用次数要数十倍多于GenerateMoveLis的调用,所以着法生成的时间已经微不足道了,修改后性能的提升也十分有限。由于改动较大,相关的代码放在了search1.c里,不影响原来AlphaBeta函数。

3.源代码

https://github.com/pfysw/JunQi

四国军棋引擎开发(7)概率分析与搜索优化相关推荐

  1. 四国军棋引擎开发(9)子力概率判断分析

    本文分为2部分,第1部分继续深入分析子力的概率问题,第2部分记录下刚刚碰到的一个非常棘手的bug,解决这个bug后,目前这个版本基本上没有什么明显的bug,可以作为版本为2.0.如果全部着法都搜索的话 ...

  2. 四国军棋引擎开发(1)随机下棋

    现在开始来开发四国军棋的引擎,所谓引擎就是根据当前的局面给出最佳的下法,而界面只是一个显示的功能.目前由玩家控制自家和对家的棋,由引擎控制上家和下家的棋. 1.通信 和界面类似,socke通信放在一个 ...

  3. 四国军棋引擎开发(6)alpha-beta剪枝算法

    在讲alpha-beta剪枝算法之前先要了解最大最小算法,在棋类游戏中,给每一个局面打一个分数,轮到自己下时会选择有利于自己的下法,即选择局面分数高的,而对手会选择更加不利于自己的局面,即分数最低的. ...

  4. 四国军棋引擎开发(8)主要变例提取

    alpha-beta剪枝算法对着法的搜索顺序有比较高的要求,最好是先搜索好的着法再搜索坏的着法,这样就可以最大程度的进行剪枝. 在搜索前我们当然不知道着法的优劣,如果知道了那就不需要搜索了,但我们搜索 ...

  5. 四国军棋引擎开发(11)多线程搜索

    由于现在没有什么好的办法优化剪枝来增加搜索深度,所以现在通过不同的方法进行搜索,最后综合各种搜索方法的结果选择最佳着法.每一种搜索方法是独立的,所以单独放在一个线程里搜索,如果CPU是多核的,操作系统 ...

  6. 四国军棋引擎开发(12)关键步加深搜索

    调了很久终于能够更新一个版本了,这东西是越来越难调了,每一次输棋都要处理茫茫多的复杂逻辑,而且有些bug隐藏在递归的最深处很难定位,真希望软件可以像人一样自己学会想算法调代码做验证. 这次更新大的框架 ...

  7. 四国军棋引擎开发(5)着法生成与棋谱分析

    1.着法生成 软件下棋时需要搜索大量的局面并对局面进行评估从而选出最好的着法,每一次行棋时生成所有可行的着法,每个着法产生后对应一个新的局面,然后下一家在新的局面基础上再生成所有着法. 军棋软件和普通 ...

  8. 四国军棋引擎开发(10)局面评估优化

    这次对局面评估做了一些优化,棋力有了一些提升,可以定为2.1版本,测试结果如下: 引擎A vs 引擎B 战绩(胜:负:和) 1.1 vs 1.0 8:2:0 1.2 vs 1.1 8:2:0 1.2 ...

  9. 四国军棋引擎开发(2)简单的事件驱动模型下棋

    这次在随机乱下的基础上加上了一些简单的处理,如进营.炸棋.吃子等功能,在和敌方棋子产生碰撞之后会获取敌方棋子大小的一些信息,目前采用的是事件驱动模型,当下完一步棋界面返回结果后会判断是否触发了相关事件 ...

最新文章

  1. 删除u盘mbr多余启动项 linux,高手教你怎么修复U盘的MBR
  2. 回填用土好还是砂石料好_养羊喂撒粉料好还是颗粒料好?
  3. JavaScript获取节点类型、节点名称和节点值
  4. before css 旋转_七夕,当然少不了纯CSS的点缀啦
  5. jQuery之简单的表单验证
  6. 转 Intellij中的常用快捷键
  7. 六十八、SpringBoot连接MongoDB操作
  8. boost::sort模块实现spreadsort 字符串函子排序示例
  9. SocketLog-微信调试、API调试和AJAX的调试的工具,能将日志通过WebSocket输出到Chrome浏览器的console中
  10. 重构——30以类取代类型码(Replace Type Code with Class)
  11. 洛谷——P1219 八皇后
  12. 机器学习12推荐系统
  13. python 获取表格中的空数据_在python beautifulsoup中获取表格单元格值为空时的文本输出...
  14. 教大家怎么把百度网盘的分享链接共享出来
  15. 《俞军产品方法论》阅读笔记2020-08-07
  16. CentOS 7.6使用Percona XtraBackup 2.4备份恢复MySQL 5.7
  17. View inflate 原理
  18. 高性能分布式缓存redis(持久化原理 安全策略 过期删除内存淘汰策略 性能压测 高可用 Redis Cluster)
  19. 1986年图灵奖--约翰·霍普克洛夫特和罗伯特·陶尔扬简介
  20. vue项目国际化 vue-i18n以及踩坑解决 小姐姐手把手教你VUE国际化~

热门文章

  1. 微机原理实验 实验一 简单I/O口扩展 8 位 I/O 扩展
  2. 开发利器 Emeditor
  3. android登录窗口化,【精】【转】安卓实现窗口化不再是梦想
  4. EIDcenter 为金山毒霸进行2009年新版本设计
  5. 第2章 第一个Spring Boot项目
  6. 47 颜色对话框(QColorDialog)
  7. 【Linux】基础IO —— 动静态库的制作与使用
  8. 新库上线 | CnOpenData儒家文化数据
  9. ZTE ME3630 4G模块在Hi3559AV100平台上拨号指令流程
  10. 网吧游戏服务器虚拟,网吧的16G内存虚拟服务器组建之路