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

1.通信

和界面类似,socke通信放在一个单独的线程里,但是只接收数据不处理数据,接收到的数据通过消息队列发送给引擎模块处理:

    while(1){recvbytes=recvfrom(socket_fd, buf, 200, 0,NULL ,NULL);mq_send(pJunqi->qid, (char*)buf, recvbytes, 0);}

引擎也是一个单独的线程,一直等待通信线程传来的数据。

    while (1){len = mq_receive(pJunqi->qid, (char *)aBuf, REC_LEN, NULL);if ( len > 0){ProRecMsg(pJunqi, aBuf);}}

当界面先于引擎启动的时候,引擎会主动发送COMM_READY命令来请求初始化。

2.基本框架

当界面发送初始化命令过来时,引擎会初始化一些棋盘上固定的东西,如邻接表、铁路、九宫格等,这些只在引擎第一次启动时初始化,此后不再改变,而棋子和布阵每次新开局时都会重新初始化。

    static int isInitBoard = 0;... ...case COMM_INIT:InitLineup(pJunqi, data, isInitBoard);InitChess(pJunqi, data);if( !isInitBoard ){isInitBoard = 1;InitBoard(pJunqi);}SendHeader(pJunqi, pHead->iDir, COMM_OK);break;

在接收到COMM_START命令后引擎开始下棋,轮到自己下时发送行棋命令给界面,界面会把判定的结果重新发回给引擎。每收到一次界面的判定结果,更换下棋的方位。引擎虽然不能显示,但是自身维护着各棋子的状态,每收到一次行棋结果或事件的命令都会改变棋子的相应状态。

    case COMM_EVNET:event = *((u8*)&pHead[1]);ProMoveEvent(pJunqi, pHead->iDir, event);SendHeader(pJunqi, pHead->iDir, COMM_OK);break;case COMM_MOVE:assert( pHead->iDir==pJunqi->eTurn );data = (u8*)&pHead[1];ProMoveResult(pJunqi, pHead->iDir, data);SendHeader(pJunqi, pHead->iDir, COMM_OK);break;
void ProMoveResult(Junqi* pJunqi, u8 iDir, u8 *data)
{... ...PlayResult(pJunqi, pSrc, pDst, pResult);ChessTurn(pJunqi);
}

3.决策模块

这个是引擎的核心,也就是根据当前的局面,找出获胜概率最大的下法。由于现在刚开始开发,先从最开始的随机决策做起,慢慢的会增加一些其他提高胜率的算法。引擎控制上家和下家,轮到引擎时发送行棋命令。

    if( !pJunqi->bStart || pJunqi->bStop ){return;}if( pJunqi->eTurn%2==1 ){sleep(1);if( pJunqi->aInfo[pJunqi->eTurn].bDead ){ChessTurn(pJunqi);}SendRandMove(pJunqi);}

轮到某一家下棋时,引擎会遍历这一家所有活着的棋子,随机选中一颗,然后再查看这颗棋子能否行棋,如果不能行棋再换下一颗棋子,无棋可走时发送跳过,此时界面已经判负了,将会发送投降的命令过来。

    rand = random_()%30;for(i=0;  i<30; i++){pLineup = &pJunqi->Lineup[pJunqi->eTurn][(rand+i)%30];if( pLineup->bDead ){continue;}pSrc = pLineup->pChess;if(pLineup->type!=NONE && pLineup->type!=JUNQI && pLineup->type!=DILEI ){pDst = GetMoveDst(pJunqi, pSrc);if( pDst!=NULL ){SendMove(pJunqi, pSrc, pDst);return;}}}SendEvent(pJunqi, pJunqi->eTurn, JUMP_EVENT);

下面是和我下棋的截图,当然现在很弱智,随便乱送都能赢

4.源代码

https://github.com/pfysw/JunQi

一个多线程引发的问题

接下来说明一下解决的一个非常棘手的程序崩溃问题,曾一度调的都要放弃。在软件和界面联调时,经常出现莫名奇妙的界面显示异常,也经常出现GTK内部函数的断言错误,还时不时的出现内存引起的程序崩溃而退出。

这个问题不是必现,出现的概率是很高,但是又琢磨不透。自己手工测试很多次,加上打印信息,基本上可以确定是在通信线程执行行棋(即PlayResult)时出现的,但是没发现什么规律,也想不通为什么会这样。

为了增加单位时间内的测试次数,修改代码让引擎自己和自己下,这样几秒就能下一盘棋,测试时间变短了很多。为了确定不是自己代码逻辑的问题,在PlayResult里把界面相关的东西都屏蔽掉,此时概率减小了很多,但是还是会出现崩溃,但是有一个重大的发现,崩溃基本都是出现在最后一方棋被吃光无棋可走5次跳过的时候,此时与界面相关的只有DestroyAllChess()函数,这个函数销毁战败方的棋子。

在下面代码处加入打印信息,查看到底是哪颗棋子的问题

    for(i=0; i<30; i++){if( pJunqi->Lineup[iDir][i].type!=NONE ){for(j=1; j<4; j++){printf("desroy %d %d",i,j);gtk_widget_destroy(pJunqi->Lineup[iDir][i].pImage[j]);}}}

发现竟然不能复现了,此时很容易联想到是gtk_widget_destroy执行太快的问题,所以在后面加上1ms的延时函数Sleep(1),此时貌似问题解决了,但是经过10多次测试,还是有10分之一的概率会出现崩溃。为了确认的确是这个函数的问题,而不是其他问题,让引擎只发送跳过指令,测试结果是加了延时崩溃概率变小,不加延时立即崩溃,此时可以确认是这里出现的问题。

但是我们发现再新建菜单的回调函数里也有DestroyAllChess(),无论测试多少次都不会出现崩溃。

void ReSetChessBoard(Junqi *pJunqi)
{for(int i=0; i<4; i++){if( !pJunqi->aInfo[i].bDead ){DestroyAllChess(pJunqi, i);}SetChess(pJunqi,i);}......
}

由于SetChess(pJunqi,i);会重新画上棋子,为了保持相同的测试情境,把SetChess(pJunqi,i)去掉,这时点”新建”时,棋子会消失,但是略微有延迟感,也就是说gtk_widget_destroy执行后不是立即产生效果,而是在之后才产生效果,在后面加Sleep(2000)延时2s,这证实了我的猜想。但是为什么在通信线程里执行DestroyAllChess()也延时2s,棋子也还是立即消失呢?仔细想一下就知道了,gtk_widget_destroy和真正执行销毁控件的函数其实是在一个线程里,而其他线程执行gtk_widget_destroy后延时只是在自己的线程里延时,并不阻塞控件的销毁。

有了以上分析,解决的方案是怎么把界面有关的东西都放在主线程里执行,而不是在另外的线程里执行。一开始是做一个button按钮,绑定回调函数,然后通过g_signal_emit_by_name发送按钮消息,但是仍然没效果。怀疑是button的优先级太高导致的问题,改为做一个名为”通信“的空菜单绑定回调函数,同样没有效果。然后我把这个菜单显示到通信栏里用鼠标点击就没问题,仔细想一下难道g_signal_emit_by_name并不是向主线程发送消息吗?经过设断点调试发现竟然还在通信线程里,也就是说g_signal_emit_by_name仅仅相当于一个函数调用,而不是模拟鼠标发送事件。

上面方案行不通我又想到了主线程里有一个idle函数,通过g_idle_add() 来添加,通过断点调试确认添加的idle的确是在主线程里。如果idle函数返回0则只执行一次,返回1在空闲时反复执行。现在终于得到了最终的解决方案,把通信处理函数DealRecData()放在idle里,并且返回0,每次收到数据时就调用g_idle_add(),由于通信数据在2个线程里,所以要加上互斥锁对数据进行保护,这样就解决了这个程序崩溃的问题。

四国军棋引擎开发(1)随机下棋相关推荐

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

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

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

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

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

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

  4. 四国军棋引擎开发(3)寻找到军旗的最短路径

    1.问题描述 本来打算接下来做α-β剪枝算法,但是我觉得还是要先解决下棋时一个非常蠢的问题,就是军旗都在旁边了却不懂得去挖.这里要解决的算法就是寻找本方棋子到对方军旗的通路,还要在所有通路中寻找最少步 ...

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

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

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

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

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

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

  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. 四国军棋引擎开发(12)关键步加深搜索

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

最新文章

  1. 【骚气的动效】无限循环往下往复淡入淡出运动,通常用于向下箭头,提示用户可以往下滚动或者点击展开
  2. ZooKeeper概述
  3. pthread_detach 常规使用记录
  4. 应用监控指标采集器 Prometheus 核心介绍
  5. tensorflow 进阶(三),BP神经网络之两层hidden_layer
  6. yii mysql_Yii2框架操作数据库的方法分析【以mysql为例】
  7. 训练日志 2018.11.28
  8. [CareerCup] 14.1 Private Constructor 私有构建函数
  9. 故障处理,no space left on device!几种可能性?
  10. 基于php的微信公众号开发,基于ThinkPHP框架快速构建微信公众号开发框架
  11. paypal android sdk,Android Paypal SDK错误:商家不接受此类付款
  12. 工程制图计算机绘图实训总结感悟,工程制图心得体会.doc
  13. 一文带你了解Zookeeper基本概念、集群搭建、使用方法
  14. Java Geometry空间几何数据的处理应用
  15. Tomcat启动成功,但是访问项目时或者是localhost:8080时出现404
  16. 滴滴8周年:科技让出行更美好!
  17. java七夕实现爱心
  18. 数据库横向扩展和纵向扩展
  19. Xilinx基于PCIE的部分重配置实现(一)
  20. [Machine Learning] 方向导数梯度(Directional Derivative Gradient)

热门文章

  1. Python 中的 urlencode 和 urldecode 操作
  2. 瓶子里的萤火虫动画特效
  3. 凯迪仕智能锁完成C轮6亿融资10亿综合授信,开启全球化战略新篇章
  4. 北邮计算机考研好难吗,北京邮电大学考研难吗?一般要什么水平才可以进入?...
  5. gdb: “No symbol table is loaded”
  6. 移通创联Profinet网关将丹佛斯变频器接入西门子PLC
  7. 学生用计算机在线版,一起作业学生版
  8. 有关操作系统中对换的相关知识
  9. 解析波士顿Handle机器人背后的技术
  10. 软考高级系统架构设计师系列论文一:论软件架构的选择与应用