象棋人工智能算法的C++实现(四)——人工智能的开端

百家 作者:程序人生 2018-11-11 03:50:30

点击上方“程序人生”,选择“置顶公众号”

第一时间关注程序猿(媛)身边的故事


作者

De掉所有bug 

来源

https://blog.csdn.net/weixin_41676881/article/details/83340873 

如需转载,请联系我们。


前面几篇文章详细介绍了棋盘类的封装、棋子类的封装以及各种类型的棋子的走棋算法的实现。有了前面的铺垫,就能迈出人工智能的第一步了。本系列博客还是重点介绍实现方法,很多的代码都不再过多解释了。


象棋人工智能算法的C++实现(三)——注重功能分区!!!

象棋人工智能算法的C++实现(二)

象棋人工智能算法的C++实现(一)


人机对战类:

#ifndef SINGLEGAME_H
#define SINGLEGAME_H

#include "Board.h"

class SingleGame : public Board
{
public:
   virtual void click(int id,int row,int col);

   //获取所有走棋路径存放到steps中
   void getAllPossibleMove(QVector& steps);
   //评估局面分
   int calcScore();
   //假装走一步
   void fakeMove(Step* step);
   //将假装走的那一步挪回来
   void unfakeMove(Step* step);
   //获取最佳走棋路径
   Step* getBestMove();
   //电脑走棋
   void computerMove();
};

#endif // SINGLEGAME_H


由以上代码可以看出,人机对战类继承自棋盘类,重载了棋盘类中的click函数。其中,Step是一个QVector容器,其内部情况是这样的:


#ifndef STEP_H
#define STEP_H

#include

class Step : public QObject
{
   Q_OBJECT
public:
   explicit Step(QObject *parent = 0);
   ~Step();

   //行走棋子的id
   int _moveid;
   //杀掉棋子的id
   int _killid;
   //起始行坐标
   int _rowFrom;
   //起始列坐标
   int _colFrom;
   //目标位置行坐标
   int _rowTo;
   //目标位置列坐标
   int _colTo;

signals:

public slots:
};

#endif // STEP_H


由以上代码可以看出Step类是用来存放走棋信息的。

人机对战类中重载的click函数源代码:

void SingleGame::click(int id,int row,int col)
{
   if(!this->_bRedTurn)
       return;

   Board::click(id,row,col);

   if(!this->_bRedTurn)
   {
       Step* step=getBestMove();
       moveStone(step->_moveid,step->_killid,step->_rowTo,step->_colTo);
   }
}


由以上代码可以看出,人类方走红棋,电脑方走黑棋。当轮到红方走棋的时候,与人人对战的时候没有区别,于是调用父类中的click函数;当轮到黑方走棋的时候,就获取对电脑最有利的走棋路径,让电脑走棋。


----这里是华丽的分割线----


人工智能的实现分3步走


1.获取所有走得通的路径


上getAllPossibleMove函数的源代码:


void SingleGame::getAllPossibleMove(QVector& steps)
{
   //遍历所有黑方棋子
   for(int i=16;i< 32;i++)
   {
       //如果棋子已死则直接跳过
       if(_s[i]._dead) continue;
       //遍历所有行坐标
       for(int row=0;row< =9;row++)
       {
           //遍历所有列坐标
           for(int col=0;col< =8;col++)
           {
               //获取想要杀死的棋子的id
               int killid=this->getStoneId(row,col);
               //若想要杀死的棋子与行走的棋子颜色相同则跳过
               if(sameColor(killid,i)) continue;
               //判断某一棋子能不能行走
               if(canMove(i,killid,row,col))
               {
                   //将可以行走的“步”存放到steps中
                   saveStep(i,killid,row,col,steps);
               }
           }
       }
   }
}


算法解析:遍历所有黑方的棋子,遍历到某一棋子时全方位无死角地遍历棋盘上的所有位置,把每个位置的信息都输入canMove函数,将canMove函数返回true的“步”存放到容器steps中。

2.从所有能走的路径中找到对电脑最有利的路径

上getBestMove函数的源代码:

Step* SingleGame::getBestMove()
{
   QVector steps;
   //看看有哪些步骤可以走
   getAllPossibleMove(steps);
   int maxScore=-100000;
   Step* ret;
   for(QVector
::iterator it=steps.begin();it!=steps.end();++it)
   {
       Step* step=*it;
       //试着走一下
       fakeMove(step);
       //评估局面分
       int score=calcScore();
       //再走回来
       unfakeMove(step);
       //取最高的分数
       if(score>maxScore)
       {
           maxScore=score;
           ret=step;
       }
   }
   return ret;
}


算法解析:这便是模拟人的思维的过程,从小跟爷爷下棋的时候爷爷就对我说,要走一步看三步,然而这里先实现走一步看一步。路边两个老爷爷在下棋,老爷爷虽然不会摆弄棋盘上的棋子,然而他的脑子里是在推演的,他也会权衡一下选择对自己最有利的路径走棋。fakeMove和unfakeMove便是推演的过程,假想自己走一步,评估完局面分再走回来。找到走完后局面分最高的路径作为返回值返回。


关于fakeMove函数和unfakeMove函数:


void SingleGame::fakeMove(Step* step)
{
   killStone(step->_killid);
   moveStone(step->_moveid,step->_killid,step->_rowTo,step->_colTo);
}

void SingleGame::unfakeMove(Step* step)
{
   reliveStone(step->_killid);
   moveStone(step->_moveid,step->_rowFrom,step->_colFrom);
}

其中killStone函数是用来杀死棋子的,relieveStone函数是用来复活棋子的。 

获取最优走棋路径这一步中有个很重要的步骤就是评估局面分,上calcScore函数的源代码:

int SingleGame::calcScore()
{
   //枚举的 车=0 马=1 炮=2 兵=3 将=4 士=5 相=6
   static int chessScore[]={100,50,50,20,1500,10,10};
   int redTotalScore=0;
   int blackTotalScore=0;
   //计算红棋总分
   for(int i=0;i< 16;i++)
   {
       //如果棋子已死则跳过累加
       if(_s[i]._dead)
           continue;
       redTotalScore+=chessScore[_s[i]._type];
   }
   //计算黑棋总分
   for(int i=16;i< 32;i++)
   {
       //如果棋子已死则跳过累加
       if(_s[i]._dead)
           continue;
       blackTotalScore+=chessScore[_s[i]._type];
   }
   //返回黑棋总分-红棋总分
   return blackTotalScore-redTotalScore;
}

算法分析:先给所有棋子分配权重,根据棋子的重要程度来分配。将是最重要的棋子,因此将的权重最高,置为1500;车其次,置为100,马和炮再其次,置为50;兵再再其次,置为20;士和相最不重要,置为10。遍历红方所有的棋子,将红方活着的棋子的权重累加出一个总分;遍历黑方所有的棋子,将黑方活着的棋子的权重累加出一个总分。因为黑方是电脑,所以返回的局面分应该以黑方的角度计算,返回黑棋总分-红棋总分。


3.电脑走棋


这一部分在上面的click函数中有体现,请自行往上翻。


(完)

「若你有原创文章想与大家分享,欢迎投稿。」

加编辑微信ID,备注#投稿#:

程序 丨 druidlost  

小七 丨 duoshangshuang


推荐阅读:


print_r('点个赞吧');
var_dump('点个赞吧');
NSLog(@"点个赞吧!")
System.out.println("点个赞吧!");
console.log("点个赞吧!");
print("点个赞吧!");
printf("点个赞吧!n");
cout < < "点个赞吧!" < < endl;
Console.WriteLine("点个赞吧!");
fmt.Println("点个赞吧!")
Response.Write("点个赞吧");
alert(’点个赞吧’)

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接
百度热搜榜
排名 热点 搜索指数