1.子力判断

子力判断在局面评估中起着非常重要的作用,在前一篇文章中已经介绍了子力判断的部分,那时相对还比较粗糙,这次会更细致的分析并优化上一次的不足。

pLineup->type用来代表棋子的类型,这里是用枚举变量来表示,要注意级别大的变量值小,如40的值是5,39的值是6,排长的值12,工兵的值是13,所以在级别比较的时候要注意区分变量的大小和级别的大小。本方棋子的类型是明确的,敌方棋子的类型未知所以一开始用DARK表示,但是撞过之后,我们就知道其最大的类型或最小的类型。比如我方37吃掉一个子,那么这个子最大也就是36,如果对方吃掉我方一个38,那么这个棋最小也有39。这时我们用pLineup->type表示最小的估算类型,pLineup->mx_type表示最大的估算类型。

但是如果棋下到中局后会产生一些碰撞,一些暗子就会根据已有已经明了的棋产生一个估算区间,这时新的碰撞后产生的估算不能超出上一次计算的范围。例如双方40打兑,39和2个38都吃过37,那么可以断定其他子最大37,这时如果其他子被本方39吃掉,那么就不能判断它最大38,而应仍然判断为37。

                //这里假定pSrc是本方的子吃掉对方的子pDst//现在pDst->pLineup->mx_type是37//pSrc->pLineup->type是39//以下条件会阻止pDst->pLineup->mx_type被更新if( pDst->pLineup->mx_type < pSrc->pLineup->type+1 ){pDst->pLineup->mx_type = pSrc->pLineup->type+1;}

接下来我们再看一下除了基本碰撞之外的判断,后2排属于雷区,如果动棋或者被工兵飞过,那么可以判断不是地雷

    if( pSrc->pLineup->index>=20 ){pSrc->pLineup->isNotLand = 1;}//前面条件是工兵撞死if( pSrc->type==GONGB && pDst->pLineup->index>=20 ){pDst->pLineup->isNotLand = 1;}

如果出现碰撞,并且是1线以下的棋,则标记不是炸弹,因为1线以下的棋没摸过是有炸弹的可能。

        if( pDst->pLineup->index>=5 ){pDst->pLineup->isNotBomb = 1;}

如果自家的棋撞死了,经过之前的精确评估后发现这个子的最大可能不会比撞死的大,那么可以断定是地雷

            if( pSrc->pLineup->type<=pDst->pLineup->mx_type ){assert( pDst->pLineup->index>=20 );pDst->pLineup->type = DILEI;}

有了这些基本的信息后,我们就要对子力进行计算,如敌方这个棋的最大可能性,地雷还剩几个,炸弹还剩几个。

基本思路就是先假定这个这个子的最大可能性是司令,比如这个子吃掉了36,那么最小37,先查询现在大于等于司令的棋有几个,如果已经有1个了那么不可能是司令,再接着查大于等于39的棋有几个,如果有2个了,说明不可能是39。这里要考虑炸弹和地雷的影响,如果是后2排的棋不要算进去,这样可以排除地雷的影响。如果是是暗棋打兑,会把pLineup->bBomb置1,统计暗棋打兑的数量,根据剩余的炸弹数量,减去较小的,这样可以排除炸弹的影响。很多东西都很难描述,还是直接通过代码来解释吧,子力计算的函数都在AdjustMaxType()中实现。

//计算大于某个级别的数量总和,比如要计算大于37的棋的数量
//就要把当前已知40、39、38、37的数量加起来,再排除炸弹的影响
void GetTypeNum(u8 *aBombNum, u8 *aTpyeNum, u8 *aTypeNumSum)
{int i;int sum = 0;int sum1 = 0;int nBomb = 0;int sub;for(i=SILING; i<=GONGB; i++){sum += aTpyeNum[i];sum1 += aBombNum[i];//这里排除炸弹的影响aTypeNumSum[i] = sum - ((sum1<(2-aTpyeNum[ZHADAN]))?sum1:(2-aTpyeNum[ZHADAN]));//高于当前级别的数量已超出最大值,那么超出的部分必定是炸弹if( (sub=sum-aMaxTypeNum[i])>nBomb ){nBomb = sub;}}aTpyeNum[ZHADAN] += nBomb;assert( aTpyeNum[ZHADAN]<=2 );
}int GetMaxType(int mx_type, int type, u8 *aTypeNumSum)
{enum ChessType tmp;tmp = mx_type;while( tmp<type ){//大于等于tmp的数量已经到最大值,所以mx_type已经不可能是tmp//那这里为什么不退出而要继续搜索呢,这里还是举个例子//司令死掉,有3个子吃掉37,而大于等于39的子并没有到最大数量//那么是否可以判断最大就是39了呢,显然不是,后面发现,大于等于//38的数量也到了最大值,所以当前这个子最大只可能是37if( aTypeNumSum[tmp]>=aMaxTypeNum[tmp] ){mx_type = ++tmp;}else{++tmp;}}return mx_type;
}//待优化
void AdjustMaxType(Junqi *pJunqi, int iDir)
{int i;ChessLineup *pLineup;u8 *aTypeNum = pJunqi->aInfo[iDir].aTypeNum;u8 aBombNum[14] = {0};u8 aTypeNumSum[14] = {0};enum ChessType tmp;memset(aTypeNum, 0, 14);for(i=0; i<30; i++){pLineup = &pJunqi->Lineup[iDir][i];if( pLineup->type==NONE || pLineup->type==DARK ){continue;}//疑似地雷的棋,不要把pLineup->type统计进去if( pLineup->index>=20 && !pLineup->isNotLand ){if( pLineup->type!=DILEI )continue;}//计算该子类型的总和aTypeNum[pLineup->type]++;//计算该子暗打兑的数量,打兑当中有些是炸弹,需要在后续判断排除if( pLineup->bBomb ){aBombNum[pLineup->type]++;}}//工兵大于3,说明多余的飞了炸if( aTypeNum[GONGB]>3 ){log_b("gongb zhad %d %d",aTypeNum[GONGB], aTypeNum[ZHADAN]);aTypeNum[ZHADAN] += aTypeNum[GONGB]-3;assert( aTypeNum[ZHADAN]<=2 );}//获取某个级别以上的数量总和,保存在aTypeNumSum里//这里是先把aTypeNumSum都算好,因为aTypeNumSum是固定的//如果后面再循环中算则重复了GetTypeNum(aBombNum,aTypeNum,aTypeNumSum);//这里先计算好暗子的最大可能性tmp = GetMaxType(SILING, GONGB, aTypeNumSum);for(i=0; i<30; i++){pLineup = &pJunqi->Lineup[iDir][i];//NONE ~ SILINGif( pLineup->type<=SILING && pLineup->type!=DARK ){continue;}//当现在估计的子力比之前算的小的话才更新if( pLineup->type==DARK ){if( pLineup->mx_type<tmp ){pLineup->mx_type = tmp;}}else{assert( pLineup->type>SILING );//这里计算吃过子的棋的最大可能pLineup->mx_type = GetMaxType(pLineup->mx_type,pLineup->type, aTypeNumSum);//这里计算疑似地雷的棋,举个例子,对方司令已经死了//此时38撞雷,我们还不能判断是地雷,也可能是39,//如果又有另一个子吃了38,那么可以判断是地雷//这里比当前棋级别大的数量已经为最大值//后2排的pLineup->type是没有统计到aTypeNumSum里的,所以可以断定为地雷if( aTypeNumSum[pLineup->type]==aMaxTypeNum[pLineup->type] ){//后2排疑似地雷的type不会统计到aTypeNumSum里if( pLineup->index>=20 && !pLineup->isNotLand ){if( pLineup->type != DILEI){pLineup->type = DILEI;aTypeNum[DILEI]++;}}}}
}

2.局面评估

局面评估对于α-β剪枝算法非常重要,如果局面评估不准确,那么很容易漏算好的招法。由于局面评估涉及到的东西比较复杂,现在很难说清到底什么在局面评估中起着关键作用,所以现在就是根据子力判断建立一个基本的评估框架,可能现在对局面的评估很不准确,需要后续和搜索算法一起调试,优化评价结构和子力价值的评估分数。

首先军棋中的每一个子都有一个价值,除了基本价值外还有暗价值,比如二线以下的小子可以装炸弹来吓唬司令,如果军旗位没明可以利用假旗玩空城计,最后2排的棋属于雷区不到最后时刻最好不要动,动了就暴露不是地雷,这些都是暗价值。

现在把所有相关的价值定义在一个结构体里

typedef struct Value_Parameter_t
{int vAllChess;//一家所有棋的子价值u8  vChess[14];//14个作战子力类型的价值u8  vDarkLand;//后2排的暗价值,装地雷u8  vDarkBomb;//非1线棋的暗价值,装炸弹u8  vDarkJunqi;//假旗位的暗价值,装军旗
}Value_Parameter;

在pEngine对象里定义一个Value_Parameter成员变量,之所以不用宏定义是为了后面考虑让这些价值分数动态变化。在创建pEngine对象时会初始化相关价值分数,现在只是随便定个分数,当然可能非常不准确,会在后期调整。

void InitValuePara(Value_Parameter *p)
{p->vAllChess = 1600;p->vChess[SILING] = 100;p->vChess[JUNZH] = 90;p->vChess[SHIZH] = 80;p->vChess[LVZH] = 70;p->vChess[TUANZH] = 60;p->vChess[YINGZH] = 50;p->vChess[LIANZH] = 40;p->vChess[PAIZH] = 30;p->vChess[GONGB] = 55;p->vChess[DILEI] = 60;p->vChess[ZHADAN] = 65;p->vDarkLand =  10;p->vDarkBomb =  4;p->vDarkJunqi = 10;
}

评分现在很简单,就是变量4家的棋,计算每个子的分数。如果自家的棋死了则减去相应的分数,如果是地方的棋死了则加上相应的分数。基本框架如下

    for(i=0; i<4; i++){if( !pJunqi->aInfo[i].bDead){... ...for(j=0; j<30; j++){... ...死了的棋,加减每个子的基本分数,相应的子还要计算相应暗价值的分活着的子看isNotLand和isNotBomb标志位来加减每个子的暗价值分数如果2炸都没了,那么每个子的vDarkBomb分数都要减掉不管isNotBomb有没有置位}}else{//加减p->vAllChess }}

对方的棋不明,所以是Lineup->type和pLineup->mx_type的价值取评价值,如果是暗打兑,则打兑子力和炸弹价值取评价值。如果是被暗吃,则取pLineup->mx_type价值分数的一半

                                if( pLineup->bBomb ){value += (pVal->vChess[pLineup->type]+pVal->vChess[ZHADAN])/2;}else if( pLineup->type==GONGB || pLineup->type==DILEI ){value += pVal->vChess[pLineup->type];}else{value += (pVal->vChess[pLineup->type]+pVal->vChess[pLineup->mx_type])/2;}

上面的考虑还是挺粗糙的,例如有炸和无炸的影响,有没有令子,有些棋根本无法与对方大子接触所以暗价值很小,而有些子可以迫使对方令子偏线或骗对方工兵,暗价值非常大,有些子暗打兑让敌方误以为本方少炸也产生了很大的暗价值,这些都是后期需要考虑的事情。

3.源代码

https://github.com/pfysw/JunQi

四国军棋引擎开发(4)子力判断和局面评估初步相关推荐

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

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

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

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

  3. 四国军棋引擎开发(7)概率分析与搜索优化

    1.概率分析 四国军棋属于不完全信息博弈,我们是看不到敌方的棋子,但是可以通过棋子间的碰撞来判断敌方的子力分布情况和棋子大小的概率. 当棋子产生碰撞后,可能的判决结果有吃子.打兑.撞死3种结果,有时还 ...

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. 详解基于CentOS6.2下DNS主从复制搭建与部署
  2. 常见OJ评判结果对照表
  3. javascript计时原理
  4. vmware nat模式网络不通_笨笨狗教你如何解决VMware虚拟机桥接网络不通问题?
  5. Vue.js 极简小例:读值、样式调用、if判断、a 标签、点击事件、管道
  6. 二层交换机的安全方案与实施
  7. Windows操作系统原理笔记
  8. ipad上的电子阅读器们
  9. can网络管理(osek中的NM)
  10. 真·007!核酸采样机器人现身郑州;Python3面试准备与速查表;实时语音转文字工具库;AI绘画根据文本创建纹理;前沿论文 | ShowMeAI资讯日报
  11. Markdown合并表格单元格
  12. 腾讯网上共享excel使用总结
  13. 这才叫装机必备,这3款高质量电脑软件,内存满了也绝不卸载
  14. 带得动ps和python的笔记本_配台电脑,能玩LOL顶配和能够写一些python脚本能用ps不卡,预算6k到8k?...
  15. php spry文本域_Spry是什么?Spry实例用法总结
  16. 【散文】 岁月留痕遇好友
  17. myFavorite
  18. 在Linux/Unix系统下用iconv命令处理文本文件中文乱码问题
  19. python量化选股策略 源码_常见的十大量化投资策略(附源码)
  20. Linux网络编程之System V消息队列

热门文章

  1. Ubuntu下安装Zeal
  2. TYPORA语法大全
  3. 音频精准切割原理及应用
  4. 【途牛旅游项目】01 - 项目环境准备,实现登录功能
  5. 访问量千万的小程序都是怎么玩的?我总结了一个公式!
  6. 学会这些修图技巧,让你的女神成为朋友圈中的主角
  7. vscode之 Couldn‘t find a tree builder with the features you requested: lxml. Do you need to install a
  8. 【MES】ABB官方49张PPT教你玩转MES!
  9. 第14章 使用打印机
  10. 【搜索入门专题1】hdu2717 H - Catch That Cow 【BFS】