中国象棋软件 您所在的位置:网站首页 象棋软件打分优势一百多分怎么办 中国象棋软件

中国象棋软件

2024-07-10 09:32| 来源: 网络整理| 查看: 265

前面已经讲过了棋局表示、着法生成、搜索算法(包括搜索辅助), 在象棋程序中如果说搜索算法是心脏,那么局面评估就是大脑。搜索算法负责驱动整个程序,而局面评估则负责对搜索的内容进行判断评价。因而搜索与局面评估是整个程序的核心。

首先,先介绍一下在局面评估中需要考虑的因素。就不同的棋类可能要考虑的因素略有差异。在中国象棋中所要考虑的最基本的几个因素包括如下四点:

1、子力总和

子力是指某一棋子本身所具有的价值。通俗地讲就是一个棋子它值个什么价。例如,车值500的话,那可能马值300,卒值80等等。所以在评估局面时,我们首先要考虑双方的子力总和的对比。比如红方拥有士象全加车马炮,而黑方只有残士象加双马,则红方明显占优。

2、棋子位置(控制区域)

棋子位置(或称控制区域)是指某一方的棋子在棋盘上所占据(控制)的位置。例如,沉底炮、过河卒、以及车占士角等都是较好的棋子位置状态,而窝心马等则是较差的棋子位置状态。

3、棋子的机动性

棋子的机动性指棋子的灵活度(可移动性)。例如,起始位置的车机动性较差,所以我们下棋讲究早出车。同样四面被憋马腿的死马机动性也较差。

4、棋子的相互关系(包括攻击关系和保护关系)

这一点的分析较为复杂,因为一个棋子与不同的子之间往往存在多重关系。如:一个马可能在对方的炮的攻击之下同时它又攻击着对方的车。

在我的程序中,估值函数最后返回的是双方总分的差值,而各方的总分就是上面所提到的四个因素的打分的总和。

对于子力打分和控制区域打分,只要遍历棋盘,当遇到棋子时简单地去查事先定义好的“子力价值表”和“控制区域价值表”,取出相对应的值进行累加即可(这些具体值参考了前人的程序并作了适当的调整,今后仍应根据电脑下棋所反映出的实际问题对这些值作适当修改)。对于机动性打分,需要求出各个子总共有多少种走法,然后根据各个子所不同的机动性价值每多一种走法就加一次相应的分数。

对棋子相互关系的打分,我先定义了一个关系表的结构类型

typedef struct _relationtable{ BYTE nCChessID ; int nUAttackCount ; int nUGuardCount ; BYTE UnderAttack[5]; BYTE UnderGurad[5]; } RelationTable; RelationTable RelationOfMan[9][10]; // 关系表

其中nCChessID表明棋子类型,nUAttackCount和nUGuardCount分别记录该正攻击该子的棋子数量和正保护该子的棋子数量,UnderAttack[5]和UnderGuard[5]则存放攻击该子和保护该子的具体棋子的类型。因考虑实战中很难出现同时有超过五个棋子攻击/保护一个子的情况,故数组下标设定为5。当遍历一遍棋盘之后,子力打分、控制区域打分和机动性打分都可以完成,而关系表也可以填完。之后,再根据关系表来具体考察棋子的相互关系,进行关系打分。分析关系时,首先,对王的攻击保护应分离出来单独考虑,因为对王的保护没有任何意义,一旦王被吃掉整个游戏就结束了。其次,对一个普通子,当它既受到攻击又受到保护的时候要注意如下几个问题:1、 攻击者子力小于被攻击者子力,攻击方将愿意换子。比如,一个车正遭受一个炮的攻击,那么任何对车的保护都将失去意义——对方肯定乐意用一个炮来换一个车。2、 多攻击单保护的情况,并且攻击者最小子力小于被攻击者子力与保护者子力之和,则攻击方可能以一子换两子。3、 三攻击两保护的情况,并且攻击者子力较小的二者之和小于被攻击者子力与保护者子力之和,则攻击方可能以两子换三子。4、 攻击方与保护方数量相同,并且攻击者子力小于被攻击者子力与保护者子力之和再减去保护者中最大子力,则攻击方可能以n子换n子。

可能上面这几条说的大家有点晕,待会结合具体程序可能更易于理解。当然,上述四条只是覆盖了最常见的几种情况,覆盖并不全面。而且,并没有直接地重新考虑双方兑子之后的控制区域及机动性变化情况(之所以说没有直接考虑,是因为搜索继续展开结点后仍会考虑这些因素,只是目前小生尚不知这样效果是否受影响)。所以,如果下一步要对程序进行改进的话,应当在此多做文章……

下面是CChessEvaluate.h的代码实现:

// CChessEvaluate.h // Type Define typedef struct _relationtable{ BYTE nCChessID ; int nUAttackCount ; int nUGuardCount ; BYTE UnderAttack[5]; BYTE UnderGurad[5]; } RelationTable; /// Data Define /// POINT PointList[20]; // 目标点队列 int nPointCount; // 目标点数目 RelationTable RelationOfMan[9][10]; // 关系表 const int MaxValue = 10000; // 最大极值(最小极值为最大极值的负值) //各子的基本价值(子力价值) // 将, 士, 象, 马, 车, 炮, 卒 const int BasicValues[15] = { 0, 0, 250, 250, 300, 500, 300, 80, 0, 250, 250, 300, 500, 300, 80 }; //各子的机动性价值(每多一步走法所加的分) // 将, 士, 象, 马, 车, 炮, 卒 const int MobilityValues[8] = { 0, 0, 1, 1, 12, 6, 6, 15 }; //各子的控制区域价值(所在的位置的价值) const int PositionValues[8][90] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { // 帅 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -8, -9, 0, 0, 0, 0, 0, 0, 0, 5, -8, -9, 0, 0, 0, 0, 0, 0, 0, 1, -8, -9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { // 士 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { // 相 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0 }, { // 马 0, -3, 5, 4, 2, 2, 5, 4, 2, 2, -3, 2, 4, 6, 10, 12, 20, 10, 8, 2, 2, 4, 6, 10, 13, 11, 12, 11, 15, 2, 0, 5, 7, 7, 14, 15, 19, 15, 9, 8, 2,-10, 4, 10, 15, 16, 12, 11, 6, 2, 0, 5, 7, 7, 14, 15, 19, 15, 9, 8, 2, 4, 6, 10, 13, 11, 12, 11, 15, 2, -3, 2, 4, 6, 10, 12, 20, 10, 8, 2, 0, -3, 5, 4, 2, 2, 5, 4, 2, 2 }, { // 车 -6, 5, -2, 4, 8, 8, 6, 6, 6, 6, 6, 8, 8, 9, 12, 11, 13, 8, 12, 8, 4, 6, 4, 4, 12, 11, 13, 7, 9, 7, 12, 12, 12, 12, 14, 14, 16, 14, 16, 13, 0, 0, 12, 14, 15, 15, 16, 16, 33, 14, 12, 12, 12, 12, 14, 14, 16, 14, 16, 13, 4, 6, 4, 4, 12, 11, 13, 7, 9, 7, 6, 8, 8, 9, 12, 11, 13, 8, 12, 8, -6, 5, -2, 4, 8, 8, 6, 6, 6, 6 }, { // 炮 0, 0, 1, 0, -1, 0, 0, 1, 2, 4, 0, 1, 0, 0, 0, 0, 3, 1, 2, 4, 1, 2, 4, 0, 3, 0, 3, 0, 0, 0, 3, 2, 3, 0, 0, 0, 2, -5, -4, -5, 3, 2, 5, 0, 4, 4, 4, -4, -7, -6, 3, 2, 3, 0, 0, 0, 2, -5, -4, -5, 1, 2, 4, 0, 3, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 1, 2, 4, 0, 0, 1, 0, -1, 0, 0, 1, 2, 4 }, { // 兵 0, 0, 0, -2, 3, 10, 20, 20, 20, 0, 0, 0, 0, 0, 0, 18, 27, 30, 30, 0, 0, 0, 0, -2, 4, 22, 30, 45, 50, 0, 0, 0, 0, 0, 0, 35, 40, 55, 65, 2, 0, 0, 0, 6, 7, 40, 42, 55, 70, 4, 0, 0, 0, 0, 0, 35, 40, 55, 65, 2, 0, 0, 0, -2, 4, 22, 30, 45, 50, 0, 0, 0, 0, 0, 0, 18, 27, 30, 30, 0, 0, 0, 0, -2, 3, 10, 20, 20, 20, 0 } }; /// Function Prototype // 估值函数,返回对当前局面的估值。fWhoseTurn标志当前轮到哪一方走棋 int Eveluate( int fWhoseTurn ); // 将目标点加入PointList队列 inline void AddPointToQueue( BYTE x, BYTE y ); // Programmer-Defined Function int Eveluate( int fWhoseTurn ) { int RedValues = 0; // 红方总的分值 int BlackValues = 0; // 黑方总的分值 int nBasicVal[2] = { 0 , 0 }; // 双方的子力值 int nMobilityVal[2] = { 0 , 0 }; // 双方的机动性值 int nPositionVal[2] = { 0 , 0 }; // 双方的控制区域值 int nRelationVal[2] = { 0 , 0 }; // 双方的关系值(攻击或保护) BYTE nCChessID; BYTE nTargetType; int fSide; int nPosition; int i; bool bHaveHalf; //**** Reset RelationTable **** memset( RelationOfMan, 0, sizeof(RelationTable)*90 ); int x, y; for( x = 0; x 3 ) AddPointToQueue( x - 1, y ); //向右 if( x < 5 ) AddPointToQueue( x + 1, y ); break; case BLACK_K: // 将帅碰面 i = IsKingFaceToFace( x, y, fSide ); if( i != -1 ) AddPointToQueue( x, i ); //向前 if( y > 7 ) AddPointToQueue( x, y - 1 ); //向后 if( y < 9 ) AddPointToQueue( x, y + 1 ); //向左 if( x < 5 ) AddPointToQueue( x + 1, y ); //向右 if( x > 3 ) AddPointToQueue( x - 1, y ); break; case RED_J: // fall through case BLACK_J: //纵向 for( i = y + 1; i = 0; i -- ) { AddPointToQueue( x, i ); if( HaveMan( x, i ) ) break; } //横向 for( i = x - 1; i >= 0; i -- ) { AddPointToQueue( i, y ); if( HaveMan( i, y ) ) break; } for( i = x + 1; i = 2 && ! HaveMan( x, y - 1 ) ) { //5点方向 if( x < 8 ) AddPointToQueue( x + 1, y - 2 ); //7点方向 if( x > 0 ) AddPointToQueue( x - 1, y - 2 ); } if( x >= 2 && ! HaveMan( x - 1, y ) ) { //8点方向 if( y > 0 ) AddPointToQueue( x - 2, y - 1 ); //10点方向 if( y < 9 ) AddPointToQueue( x - 2, y + 1 ); } break; case RED_P: // fall through case BLACK_P: //纵向 bHaveHalf = false; //标志尚未发现中间子 for( i = y + 1; i = 0; i -- ) { if( ! bHaveHalf ) // 无中间子 { if( ! HaveMan( x, i ) ) { AddPointToQueue( x, i ); } else // if( HaveMan( x, i ) ) { bHaveHalf = true; } } else // 已有中间子 { if( HaveMan( x, i ) ) { AddPointToQueue( x, i ); break; } } } //横向 bHaveHalf = false; //标志尚未发现中间子 for( i = x - 1; i >= 0; i -- ) { if( ! bHaveHalf ) // 无中间子 { if( ! HaveMan( i, y ) ) { AddPointToQueue( i, y ); } else // if( HaveMan( i, y ) ) { bHaveHalf = true; } } else // 已有中间子 { if( HaveMan( i, y ) ) { AddPointToQueue( i, y ); break; } } } bHaveHalf = false; //标志尚未发现中间子 for( i = x + 1; i = 5 ) //兵已过河 { //向左 if( x > 0 ) AddPointToQueue( x - 1, y ); //向右 if( x < 8 ) AddPointToQueue( x + 1, y ); } break; case BLACK_B: //向前 if( y > 0 ) AddPointToQueue( x, y - 1 ); if( y 0 ) AddPointToQueue( x - 1, y ); //向左 if( x < 8 ) AddPointToQueue( x + 1, y ); } break; } // end switch for( i = 0; i < nPointCount; i ++ ) { //保存目标位置的棋子状况 nTargetType = CChessBoard[ PointList[i].x ][ PointList[i].y ]; //++++++++++++++求得棋子的机动性价值++++++++++++++++++++++++++++++++++ // 目标位置为空,机动性加分 if( nTargetType == 0 ) { if( fSide == RED ) nMobilityVal[fSide] += MobilityValues[nCChessID]; else nMobilityVal[fSide] += MobilityValues[nCChessID - 7]; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // 目标位置为对方棋子,将信息存入RelationOfMan中的UnderAttack else if( SideOfMan[nTargetType] != fSide ) { //对王受攻击的情况作单独处理 if( nTargetType == RED_K ) { if( fWhoseTurn == BLACK ) // 红帅即将被将死 { return MaxValue - 10; // 返回失败极值(已验证应为 MaxValue ) // 减去10表示此种情况稍好于王已经被吃的情况 } else // 仅仅是将军而已,关系值扣一点分 { nRelationVal[RED] -= 20; continue; } } if( nTargetType == BLACK_K ) { if( fWhoseTurn == RED ) // 黑将即将被将死 { return MaxValue - 10; // 返回失败极值(已验证应为 MaxValue ) // 减去10表示此种情况稍好于王已经被吃的情况 } else // 仅仅是将军而已,关系值扣一点分 { nRelationVal[BLACK] -= 20; continue; } } RelationOfMan[ PointList[i].x ][ PointList[i].y ] .nCChessID = nTargetType ; RelationOfMan[ PointList[i].x ][ PointList[i].y ] .UnderAttack[RelationOfMan[ PointList[i].x ][ PointList[i].y ].nUAttackCount++] = nCChessID ; } // 目标位置为己方棋子,将信息存入RelationOfMan中的UnderGuard else // if( SideOfMan[nTargetType] == fSide ) { // 若受保护的是王,则不进行处理。因为王受保护毫无意义 if( nTargetType == RED_K || nTargetType == BLACK_K ) continue; RelationOfMan[ PointList[i].x ][ PointList[i].y ] .nCChessID = nTargetType ; RelationOfMan[ PointList[i].x ][ PointList[i].y ] .UnderGurad[RelationOfMan[ PointList[i].x ][ PointList[i].y ].nUGuardCount ++] = nCChessID ; } } // end for( i = 0; i < nPointCount; i ++ ) } // end if( CChessBoard[x][y] != 0 ) } // end for x 0 to 8, y 0 to 9 //+++++++++++求得棋子的关系价值(受攻击和受保护)++++++++++++++++++++++ for( x = 0; x 3; // 单位价值取基本价值的1/8 // 统计攻击方的子力 for( i = 0; i < RelationOfMan[x][y].nUAttackCount; i ++ ) { // 查看是否有攻击者子力小于被攻击者子力,若有记录其中子力最小的 if( BasicValues[ RelationOfMan[x][y].UnderAttack[i] ] < BasicValues[nCChessID] && BasicValues[ RelationOfMan[x][y].UnderAttack[i] ] < nflagValue ) { nflagValue = BasicValues[ RelationOfMan[x][y].UnderAttack[i] ] ; } if( BasicValues[ RelationOfMan[x][y].UnderAttack[i] ] < nMinAttack ) nMinAttack = BasicValues[ RelationOfMan[x][y].UnderAttack[i] ]; if( BasicValues[ RelationOfMan[x][y].UnderAttack[i] ] > nMaxAttack ) nMaxAttack = BasicValues[ RelationOfMan[x][y].UnderAttack[i] ]; nAttack += BasicValues[ RelationOfMan[x][y].UnderAttack[i] ]; } // 统计防守方的子力 for( i = 0; i < RelationOfMan[x][y].nUGuardCount; i ++ ) { //if( BasicValues[ RelationOfMan[x][y].UnderGurad[i] ] < nMinGuard ) // nMinGuard = BasicValues[ RelationOfMan[x][y].UnderGurad[i] ]; if( BasicValues[ RelationOfMan[x][y].UnderGurad[i] ] > nMaxGuard ) nMaxGuard = BasicValues[ RelationOfMan[x][y].UnderGurad[i] ]; nGuard += BasicValues[ RelationOfMan[x][y].UnderGurad[i] ]; } if( nAttack == 0 ) // 没受攻击,而是只有保护 { nRelationVal[fSide] += 5 * RelationOfMan[x][y].nUGuardCount ; } else // 遭受攻击 { if( nGuard == 0) // 没有保护 { if( fWhoseTurn != fSide ) // 轮到对方行棋 { nRelationVal[fSide] -= 5 * nUnitValue ; } else // 轮到己方行棋 { nRelationVal[fSide] -= nUnitValue ; } } else // 有保护 { // 攻击者子力小于被攻击者子力,对方将愿意换子 if( nflagValue != 777 ) { if( fWhoseTurn != fSide ) // 轮到对方行棋 { nRelationVal[fSide] -= 5 * nUnitValue ; nRelationVal[1 - fSide] -= 5 * ( nflagValue >> 3 ); } else // 轮到己方行棋 { nRelationVal[fSide] -= nUnitValue ; nRelationVal[1 - fSide] -= ( nflagValue >> 3 ); } } // 多攻击单保护的情况并且攻击者最小子力小于被攻击者子力与 // 保护者子力之和,则对方可能以一子换两子 else if( RelationOfMan[x][y].nUGuardCount == 1 && RelationOfMan[x][y].nUAttackCount > 1 && nMinAttack < BasicValues[nCChessID] + nGuard ) { if( fWhoseTurn != fSide ) // 轮到对方行棋 { nRelationVal[fSide] -= 5 * nUnitValue ; nRelationVal[fSide] -= 5 * ( nGuard >> 3 ); nRelationVal[1 - fSide] -= 5 * ( nMinAttack >> 3 ); } else // 轮到己方行棋 { nRelationVal[fSide] -= nUnitValue ; nRelationVal[fSide] -= ( nGuard >> 3 ); nRelationVal[1 - fSide] -= ( nMinAttack >> 3 ); } } // 三攻击两保护的情况并且攻击者子力较小的二者之和小于被攻击者子力与 // 保护者子力之和,则对方可能以两子换三子 else if( RelationOfMan[x][y].nUGuardCount == 2 && RelationOfMan[x][y].nUAttackCount == 3 && (nAttack - nMaxAttack) < (BasicValues[nCChessID] + nGuard) ) { if( fWhoseTurn != fSide ) // 轮到对方行棋 { nRelationVal[fSide] -= 5 * nUnitValue ; nRelationVal[fSide] -= 5 * ( nGuard >> 3 ); nRelationVal[1 - fSide] -= 5 * ((nAttack - nMaxAttack) >> 3); } else // 轮到己方行棋 { nRelationVal[fSide] -= nUnitValue ; nRelationVal[fSide] -= ( nGuard >> 3 ); nRelationVal[1 - fSide] -= ( ( nAttack - nMaxAttack ) >> 3 ); } } // 攻击方与保护方数量相同并且攻击者子力小于被攻击者子力与保护者子力 // 之和,再减去保护者中最大子力,则对方可能以n子换n子 else if( RelationOfMan[x][y].nUAttackCount == RelationOfMan[x][y].nUGuardCount && nAttack < BasicValues[nCChessID] + nGuard - nMaxGuard ) { if( fWhoseTurn != fSide ) // 轮到对方行棋 { nRelationVal[fSide] -= 5 * nUnitValue ; nRelationVal[fSide] -= 5 * ( ( nGuard - nMaxGuard ) >> 3 ); nRelationVal[1 - fSide] -= 5 * ( nAttack >> 3 ); } else // 轮到己方行棋 { nRelationVal[fSide] -= nUnitValue ; nRelationVal[fSide] -= ( ( nGuard - nMaxGuard ) >> 3 ); nRelationVal[1 - fSide] -= ( nAttack >> 3 ); } } else { //上述情况已基本涵盖最常见情况,因而其它情况暂时不做考虑 } } // end 有保护 } // end 遭受攻击 } // end if( RelationOfMan[x][y].nCChessID != 0 ) } // end for x 0 to 8, y 0 to 9 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // 统计红方总分值 RedValues = nBasicVal[RED] + nPositionVal[RED] + nMobilityVal[RED] + nRelationVal[RED] ; // 统计黑方总分值 BlackValues = nBasicVal[BLACK] + nPositionVal[BLACK] + nMobilityVal[BLACK] + nRelationVal[BLACK] ; if( fWhoseTurn == RED ) return ( RedValues - BlackValues ) + rand()%5 ; //此处 +rand()%X 是通过 else //加一个不会对分值造成大的影响的随机量以期电脑在应对同 return ( BlackValues - RedValues ) + rand()%5 ; //一种下法时不会每次都有 //相同的回应。这样做以及X取5是否合适还有待实践检验…… } inline void AddPointToQueue( BYTE x, BYTE y ) { PointList[nPointCount].x = x ; PointList[nPointCount].y = y ; nPointCount ++; } // end of CChessEvaluate.h


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有