内容:走法产生
中国象棋基础搜索AI,
极大值,极小值剪枝搜索,
静态估值函数
理论基础:
(一)人机博弈走法产生:
先遍历某一方的所有棋子,再遍历整个棋盘,得到每个棋子的所有走棋情况(效率不高,可以改进)
1 void SingleGame::getAllPossibleMove(QVector<Step *> &steps) 2 { 3 int min, max; 4 if(this->_bRedTurn) 5 { 6 min = 0, max = 16; 7 } 8 else 9 { 10 min = 16, max = 32; 11 } 12 13 for(int i=min;i<max; i++) 14 { 15 if(this->_s[i]._dead) continue; 16 for(int row = 0; row<=9; ++row) 17 { 18 for(int col=0; col<=8; ++col) 19 { 20 int killid = this->getStoneId(row, col); 21 if(sameColor(i, killid)) continue; 22 23 if(canMove(i, killid, row, col)) 24 { 25 saveStep(i, killid, row, col, steps); 26 } 27 } 28 } 29 } 30 }
(二)棋局博弈树理论:
名词:对抗性搜索(Adversarial Search):敌对双方交替动作的搜索
博弈树:树的根部是棋局的初始局面,根的若干子节点是有甲的每一种可能走法生成的局面,
这些节点的子节点则是由乙的每一种可能走法生成的局面,如此交替直到棋局结束。
博弈树形象表示
基于博弈树的游戏:
计甲胜为WIN,乙胜为LOST,和局为DRAW;
轮到甲走时,甲选择通向WIN或DRAW的节点;换言之,turn 甲,选择所有子节点中最好的(对甲);
轮到乙走时,乙选择通向LOST或DRAW的节点;turn 乙,选择所有子节点中最差的(对甲);
(三)极大极小值算法
在上述博弈树基础上,令甲胜的局面值为1,乙胜的局面值为-1,和局的局面值为0;
轮到甲走时,选择子节点值最大的走法,轮到乙走时,选择子节点值最小的走法;
中间节点的值的确定:该局面轮到甲走,选择其子节点中的最大值,
该局面轮到乙走,选择其子节点中的最小值。
问题:实际的棋局不能简单的以1,-1,0三种状态表示,
需要加入评估棋局局面分数的估值函数,配合博弈树的搜索来确定局面分数。
实际解决方案:
估值函数:暂时以静态估值的方式形成估值函数(评估较为粗糙),
将棋局中的每个棋子按照重要程度赋一个值,
估值函数通过计算一方现存棋子的总分数来确定局面优劣情况。
代码示例:
1 int SingleGame::score() 2 { 3 enum TYPE{CHE, MA, PAO, BING, JIANG, SHI, XIANG}; 4 int s[] = {52, 13, 6, 6, 100000, 6, 6, 13, 52, 22, 22, 2, 2, 2, 2, 2}; 5 //int s[] = {1000,450,501,200,15000,200,200}; 6 /*当头卒比重大*/ 7 int scoreBlack = 0; 8 int scoreRed = 0; 9 /*计算红方分数*/ 10 for(int i=0; i<16; ++i) 11 { 12 if(_s[i]._dead) continue; 13 //scoreRed += s[_s[i]._type]; 14 scoreRed += s[i]; 15 } 16 /*计算黑方分数*/ 17 for(int i=16; i<32; ++i) 18 { 19 if(_s[i]._dead) continue; 20 //scoreBlack += s[_s[i]._type]; 21 scoreBlack += s[i-16]; 22 } 23 return scoreBlack - scoreRed; 24 }
极大值极小值搜索方案:
深度优先搜索,优点是不必在内存中生成整个博弈树,可以将搜索过的部分从内存中去除,
采用递归形式,依次在min(int level,int curMin),max(int level,int curMax)之间递归调用,
剪枝以去除不必要的步数,在求极大值时,若下一步的值小于当前极大值,直接删除这一步,不予考虑,
在求极小值时,若下一步的值大于当前极小值,直接删除这一步,不予考虑。
在所有子节点中选出值最大的走法,就是电脑的最佳走法。
代码示例:
1 int SingleGame::getMinScore(int level, int curMin) 2 { 3 if(level == 0) 4 return score(); 5 6 QVector<Step*> steps; 7 getAllPossibleMove(steps); 8 int minInAllMaxScore = 300000; 9 10 while(steps.count()) 11 { 12 Step* step = steps.last(); 13 steps.removeLast(); 14 15 fakeMove(step); 16 int maxScore = getMaxScore(level-1, minInAllMaxScore); 17 unfakeMove(step); 18 delete step; 19 20 if(maxScore <= curMin) 21 { 22 while(steps.count()) 23 { 24 Step* step = steps.last(); 25 steps.removeLast(); 26 delete step; 27 } 28 return maxScore; 29 } 30 31 if(maxScore < minInAllMaxScore) 32 { 33 minInAllMaxScore = maxScore; 34 } 35 } 36 return minInAllMaxScore; 37 } 38 int SingleGame::getMaxScore(int level, int curMax) 39 { 40 if(level == 0) 41 return score(); 42 43 QVector<Step*> steps; 44 getAllPossibleMove(steps); 45 int maxInAllMinScore = -300000; 46 47 while(steps.count()) 48 { 49 Step* step = steps.last(); 50 steps.removeLast(); 51 52 fakeMove(step); 53 int minScore = getMinScore(level-1, maxInAllMinScore); 54 unfakeMove(step); 55 delete step; 56 57 if(minScore >= curMax) 58 { 59 while(steps.count()) 60 { 61 Step* step = steps.last(); 62 steps.removeLast(); 63 delete step; 64 } 65 return minScore; 66 } 67 if(minScore > maxInAllMinScore) 68 { 69 maxInAllMinScore = minScore; 70 } 71 72 73 } 74 return maxInAllMinScore; 75 }
代码示例:
1 Step* SingleGame::getBestMove() 2 { 3 Step* ret = NULL; 4 QVector<Step*> steps; 5 getAllPossibleMove(steps); 6 int maxInAllMinScore = -300000; 7 8 while(steps.count()) 9 { 10 Step* step = steps.last(); 11 steps.removeLast(); 12 13 fakeMove(step); 14 int minScore = getMinScore(this->_level-1, maxInAllMinScore); 15 unfakeMove(step); 16 17 if(minScore > maxInAllMinScore) 18 { 19 if(ret) delete ret; 20 21 ret = step; 22 maxInAllMinScore = minScore; 23 } 24 else 25 { 26 delete step; 27 } 28 } 29 return ret; 30 }
一个简单的象棋AI,还有诸多优化之处,目前搜索深度最大为4,与初级玩家对弈输多胜少。