为实现人工智能走棋,重新建立一个类,从board派生。board是棋盘类,这个是基本的象棋走法。
在前面已经提到,叫singlegame。这个类从board派生。
#include "Board.h"
class SingleGame : public Board
在人机对战中,玩家下了后,轮到机器下,但是singlegame如何获取玩家已经走了一步这个事情?也就是说当父类board玩家走了一步后,singlegame也要知道。这里用虚函数来记录。在虚函数void mouseReleaseEvent(QMouseEvent *ev);中调用了click函数。这个click函数表示在棋盘上点击的点。
void Board::mouseReleaseEvent(QMouseEvent *ev)
{
if(ev->button() != Qt::LeftButton)
{
return;
}
click(ev->pos());
}
在这个函数中,也调用了另外一个click函数。这个函数是表示点击的棋子和他的坐标点。
void Board::click(QPoint pt)
{
int row, col;
bool bClicked = getClickRowCol(pt, row, col);
if(!bClicked) return;
int id = getStoneId(row, col);
click(id, row, col);
}
这个函数是表示点击的棋子id和它的坐标点。把这个函数设置为虚函数。
virtual void click(int id, int row, int col);
假如将这个函数在子类中重载的话,以后调用的就这个子类的函数,而不是上面那个函数。在子类singlegame中重写这个函数,在这个函数先就让调用父类中的函数,不做修改。这样的话跟以前没区别。
Board::click(id, row, col);
现在实现该函数,虚函数click是人点击后才进来的函数。在此说明一下,人走的是红棋,电脑走的是黑棋。这个函数逻辑是这样的,如果玩家走完后,就进入这个函数,然后是电脑走,电脑走用一个函数Step* getBestMove()实现。
现在实现函数Step* getBestMove();
Step* getBestMove();
该函数实现需要考虑几个步骤:
1、看看可以走哪几步。
2、每一步都尝试走一遍。
3、评估每一步的结果。
4、去最好的结果。
实现每一步,第一个,看看哪一几步可以走。写一个函数实现,并用一个数组保存步骤。
void getAllPossibleMove(QVector<Step*>& steps);
这个函数其实就是遍历那些棋子可以走。 注意的是棋子在被吃掉后,便不能再走了。
void SingleGame::getAllPossibleMove(QVector<Step *> &steps)
{
int min, max;
if(this->_bRedTurn)
{
min = 0, max = 16; //黑棋
}
else
{
min = 16, max = 32; //红棋
}
for(int i=min;i<max; i++)
{
if(this->_s[i]._dead) continue; //如果该棋子死了,继续
for(int row = 0; row<=9; ++row)
{
for(int col=0; col<=8; ++col)
{
int killid = this->getStoneId(row, col); //看遍历的这个行列有没有棋子
if(sameColor(i, killid)) continue; //看是否颜色相同,也就是不能吃自己的棋子
if(canMove(i, killid, row, col))
{
saveStep(i, killid, row, col, steps); //如果能走,就把这步放入数组中去
}
}
}
}
}
函数saveStep()是步数放入数组中去。实现如下。
void Board::saveStep(int moveid, int killid, int row, int col, QVector<Step*>& steps)
{
GetRowCol(row1, col1, moveid);
Step* step = new Step;
step->_colFrom = col1;
step->_colTo = col;
step->_rowFrom = row1;
step->_rowTo = row;
step->_moveid = moveid;
step->_killid = killid;
steps.append(step);
}
实现剩下的步骤,尝试以下每一步的走法和评估每一步。也就是遍历数组中的每一步,然后给每一步打个分数。然后取最高分数的步骤。
Step* SingleGame::getBestMove()
{
// 1.看看有那些步骤可以走
QVector<Step*> steps;
getAllPossibleMove(steps);
// 2.试着走一下
// 3.评估走的结果
int maxScore = -100000;
Step* ret = NULL;
while(steps.count())
{
Step* step = steps.back();
steps.removeLast();
fakeMove(step); //假象实现该步,不在棋盘上显示,就好像是在心中思考
int score = getMinScore(_level-1, maxScore);
unfakeMove(step);
if(score > maxScore)
{
maxScore = score;
if(ret) delete ret;
ret = step;
}
else
{
delete step;
}
}
// 4.取最好的结果作为参考
return ret;
}
一步的人工智能的思路已经实现,但是目前还有以下几个函数没实现。
void fakeMove(Step* step);
void unfakeMove(Step* step);
int calcScore();
实现第一个函数,这个函数用 killStone(step->_killid);实现,直接将id传进去。这个函数以前已经实现。
void Board::killStone(int id)
{
if(id==-1) return;
_s[id]._dead = true;
}
然后将棋子移过去。函数完整如下。
void SingleGame::fakeMove(Step* step)
{
killStone(step->_killid);
moveStone(step->_moveid, step->_rowTo, step->_colTo);
}
void Board::moveStone(int moveid, int row, int col)
{
_s[moveid]._row = row;
_s[moveid]._col = col;
_bRedTurn = !_bRedTurn;
}
将棋子移过去后,还需要将他移回来,因为这个是假象的步骤,不是真正的走棋。先复活棋子,在移回来。
void SingleGame::unfakeMove(Step* step)
{
reliveStone(step->_killid);
moveStone(step->_moveid, step->_rowFrom, step->_colFrom);
}
void Board::reliveStone(int id)
{
if(id==-1) return;
_s[id]._dead = false;
}
最后一个函数,评价局面分。如何评价步骤的优劣?我们给每个棋子给以分数。然后将每一方的棋子分数加起来,用黑色棋子的总分数减去红棋的总分数。
//enum TYPE{CHE, MA, PAO, BING, JIANG, SHI, XIANG};
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;