Esempio n. 1
0
// 点击格子事件处理
static void ClickSquare(int sq) {
  int pc;
  Xqwl.hdc = GetDC(Xqwl.hWnd);
  Xqwl.hdcTmp = CreateCompatibleDC(Xqwl.hdc);
  sq = Xqwl.bFlipped ? SQUARE_FLIP(sq) : sq;
  pc = pos.ucpcSquares[sq];

  if ((pc & SIDE_TAG(pos.sdPlayer)) != 0) {
    // 如果点击自己的子,那么直接选中该子
    if (Xqwl.sqSelected != 0) {
      DrawSquare(Xqwl.sqSelected);
    }
    Xqwl.sqSelected = sq;
    DrawSquare(sq, DRAW_SELECTED);
    if (Xqwl.mvLast != 0) {
      DrawSquare(SRC(Xqwl.mvLast));
      DrawSquare(DST(Xqwl.mvLast));
    }
    PlayResWav(IDR_CLICK); // 播放点击的声音

  } else if (Xqwl.sqSelected != 0) {
    // 如果点击的不是自己的子,但有子选中了(一定是自己的子),那么走这个子
    Xqwl.mvLast = MOVE(Xqwl.sqSelected, sq);
    pos.MakeMove(Xqwl.mvLast);
    DrawSquare(Xqwl.sqSelected, DRAW_SELECTED);
    DrawSquare(sq, DRAW_SELECTED);
    Xqwl.sqSelected = 0;
    PlayResWav(pc == 0 ? IDR_MOVE : IDR_CAPTURE); // 播放走子或吃子的声音
  }
  DeleteDC(Xqwl.hdcTmp);
  ReleaseDC(Xqwl.hWnd, Xqwl.hdc);
}
 void DelPiece(int sq, int pc) {
   ucpcSquares[sq] = 0;
   if (pc < 16) {
     vlWhite -= cucvlPiecePos[pc - 8][sq];
     zobr.Xor(Zobrist.Table[pc - 8][sq]);
   } else {
     vlBlack -= cucvlPiecePos[pc - 16][SQUARE_FLIP(sq)];
     zobr.Xor(Zobrist.Table[pc - 9][sq]);
   }
 }
Esempio n. 3
0
// 绘制格子
static void DrawSquare(int sq, BOOL bSelected = FALSE) {
  int sqFlipped, xx, yy, pc;

  sqFlipped = Xqwl.bFlipped ? SQUARE_FLIP(sq) : sq;
  xx = BOARD_EDGE + (FILE_X(sqFlipped) - FILE_LEFT) * SQUARE_SIZE;
  yy = BOARD_EDGE + (RANK_Y(sqFlipped) - RANK_TOP) * SQUARE_SIZE;
  SelectObject(Xqwl.hdcTmp, Xqwl.bmpBoard);
  BitBlt(Xqwl.hdc, xx, yy, SQUARE_SIZE, SQUARE_SIZE, Xqwl.hdcTmp, xx, yy, SRCCOPY);
  pc = pos.ucpcSquares[sq];
  if (pc != 0) {
    DrawTransBmp(Xqwl.hdc, Xqwl.hdcTmp, xx, yy, Xqwl.bmpPieces[pc]);
  }
  if (bSelected) {
    DrawTransBmp(Xqwl.hdc, Xqwl.hdcTmp, xx, yy, Xqwl.bmpSelected);
  }
}
Esempio n. 4
0
// 翻转棋盘
void FlipBoard(PositionStruct &pos) {
  int i, sq;
  uint8_t ucsqList[32];
  for (i = 16; i < 48; i ++) {
    sq = pos.ucsqPieces[i];
    ucsqList[i - 16] = sq;
    if (sq != 0) {
      pos.AddPiece(sq, i, DEL_PIECE);
    }
  }
  for (i = 16; i < 48; i ++) {
    sq = ucsqList[i - 16]; // 这行不同于ExchangeSide
    if (sq != 0) {
      pos.AddPiece(SQUARE_FLIP(sq), i);
    }
  }
}
Esempio n. 5
0
// 将内部着法表示转换为纵线符号
uint32_t Move2File(int mv, const PositionStruct &pos) {
  int i, j, sq, pc, pt, nPieceNum;
  int xSrc, ySrc, xDst, yDst;
  int nFileList[9], nPieceList[5];
  C4dwStruct Ret;

  if (SRC(mv) == 0 || DST(mv) == 0) {
    return 0x20202020;
  }
  pc = pos.ucpcSquares[SRC(mv)];
  if (pc == 0) {
    return 0x20202020;
  }
  pt = PIECE_TYPE(pc);
  Ret.c[0] = PIECE_BYTE(pt);
  if (pos.sdPlayer == 0) {
    xSrc = FILESQ_FILE_X(SQUARE_FILESQ(SRC(mv)));
    ySrc = FILESQ_RANK_Y(SQUARE_FILESQ(SRC(mv)));
    xDst = FILESQ_FILE_X(SQUARE_FILESQ(DST(mv)));
    yDst = FILESQ_RANK_Y(SQUARE_FILESQ(DST(mv)));
  } else {
    xSrc = FILESQ_FILE_X(SQUARE_FILESQ(SQUARE_FLIP(SRC(mv))));
    ySrc = FILESQ_RANK_Y(SQUARE_FILESQ(SQUARE_FLIP(SRC(mv))));
    xDst = FILESQ_FILE_X(SQUARE_FILESQ(SQUARE_FLIP(DST(mv))));
    yDst = FILESQ_RANK_Y(SQUARE_FILESQ(SQUARE_FLIP(DST(mv))));
  }
  if (pt >= KING_TYPE && pt <= BISHOP_TYPE) {
    Ret.c[1] = Digit2Byte(xSrc);
  } else {
    for (i = 0; i < 9; i ++) {
      nFileList[i] = 0;
    }
    j = (pt == PAWN_TYPE ? 5 : 2);
    for (i = 0; i < j; i ++) {
      sq = FILESQ_SIDE_PIECE(pos, FIRST_PIECE(pt, i));
      if (sq != -1) {
        nFileList[FILESQ_FILE_X(sq)] ++;
      }
    }
    // 提示:处理“两条的纵线上有多个兵(卒)”的问题上,可参阅"File2Move()"函数。
    if (nFileList[xSrc] > 1) {
      nPieceNum = 0;
      for (i = 0; i < j; i ++) {
        sq = FILESQ_SIDE_PIECE(pos, FIRST_PIECE(pt, i));
        if (sq != -1) {
          if (nFileList[FILESQ_FILE_X(sq)] > 1) {
            nPieceList[nPieceNum] = FIRST_PIECE(pt, i);
            nPieceNum ++;
          }
        }
      }
      for (i = 0; i < nPieceNum - 1; i ++) {
        for (j = nPieceNum - 1; j > i; j --) {
          if (FILESQ_SIDE_PIECE(pos, nPieceList[j - 1]) > FILESQ_SIDE_PIECE(pos, nPieceList[j])) {
            SWAP(nPieceList[j - 1], nPieceList[j]);
          }
        }
      }
      sq = FILESQ_COORD_XY(xSrc, ySrc);
      for (i = 0; i < nPieceNum; i ++) {
        if (FILESQ_SIDE_PIECE(pos, nPieceList[i]) == sq) {
          break;
        }
      }
      Ret.c[1] = (nPieceNum == 2 && i == 1 ? ccPos2Byte[2 + DIRECT_TO_POS] :
          ccPos2Byte[nPieceNum > 3 ? i : i + DIRECT_TO_POS]);
    } else {
      Ret.c[1] = Digit2Byte(xSrc);
    }
  }
  if (pt >= ADVISOR_TYPE && pt <= KNIGHT_TYPE) {
    if (SRC(mv) == DST(mv)) {
      Ret.c[2] = '=';
      Ret.c[3] = 'P';
    } else {
      Ret.c[2] = (yDst > ySrc ? '-' : '+');
      Ret.c[3] = Digit2Byte(xDst);
    }
  } else {
    Ret.c[2] = (yDst == ySrc ? '.' : yDst > ySrc ? '-' : '+');
    Ret.c[3] = (yDst == ySrc ? Digit2Byte(xDst) : Digit2Byte(ABS(ySrc - yDst) - 1));
  }
  return Ret.dw;
}
Esempio n. 6
0
/* "File2Move()"函数将纵线符号表示转换为内部着法表示。
 *
 * 这个函数以及后面的"Move2File()"函数是本模块最难处理的两个函数,特别是在处理“两条的纵线上有多个兵(卒)”的问题上。
 * 在棋谱的快速时,允许只使用数字键盘,因此1到7依次代表帅(将)到兵(卒)这七种棋子,"File2Move()"函数也考虑到了这个问题。
 */
int File2Move(uint32_t dwFileStr, const PositionStruct &pos) {
  int i, j, nPos, pt, sq, nPieceNum;
  int xSrc, ySrc, xDst, yDst;
  C4dwStruct FileStr;
  int nFileList[9], nPieceList[5];
  // 纵线符号表示转换为内部着法表示,通常分为以下几个步骤:

  // 1. 检查纵线符号是否是仕(士)相(象)的28种固定纵线表示,在这之前首先必须把数字、小写等不统一的格式转换为统一格式;
  FileStr.dw = dwFileStr;
  switch (FileStr.c[0]) {
  case '2':
  case 'a':
    FileStr.c[0] = 'A';
    break;
  case '3':
  case 'b':
  case 'E':
  case 'e':
    FileStr.c[0] = 'B';
    break;
  default:
    break;
  }
  if (FileStr.c[3] == 'p') {
    FileStr.c[3] = 'P';
  }
  for (i = 0; i < MAX_FIX_FILE; i ++) {
    if (FileStr.dw == cdwFixFile[i]) {
      if (pos.sdPlayer == 0) {
        return MOVE(cucFixMove[i][0], cucFixMove[i][1]);
      } else {
        return MOVE(SQUARE_FLIP(cucFixMove[i][0]), SQUARE_FLIP(cucFixMove[i][1]));
      }
    }
  }

  // 2. 如果不是这28种固定纵线表示,那么把棋子、位置和纵线序号(列号)解析出来
  nPos = Byte2Direct(FileStr.c[0]);
  if (nPos == MAX_DIRECT) {
    pt = Byte2Piece(FileStr.c[0]);
    nPos = Byte2Pos(FileStr.c[1]);
  } else {
    pt = Byte2Piece(FileStr.c[1]);
    nPos += DIRECT_TO_POS;
  }
  if (nPos == MAX_POS) {

    // 3. 如果棋子是用列号表示的,那么可以直接根据纵线来找到棋子序号;
    xSrc = Byte2Digit(FileStr.c[1]);
    if (pt == KING_TYPE) {
      sq = FILESQ_SIDE_PIECE(pos, 0);
    } else if (pt >= KNIGHT_TYPE && pt <= PAWN_TYPE) {
      j = (pt == PAWN_TYPE ? 5 : 2);
      for (i = 0; i < j; i ++) {
        sq = FILESQ_SIDE_PIECE(pos, FIRST_PIECE(pt, i));
        if (sq != -1) {
          if (FILESQ_FILE_X(sq) == xSrc) {
            break;
          }
        }
      }
      sq = (i == j ? -1 : sq);
    } else {
      sq = -1;
    }
  } else {

    // 4. 如果棋子是用位置表示的,那么必须挑选出含有多个该种棋子的所有纵线,这是本函数最难处理的地方;
    if (pt >= KNIGHT_TYPE && pt <= PAWN_TYPE) {
      for (i = 0; i < 9; i ++) {
        nFileList[i] = 0;
      }
      j = (pt == PAWN_TYPE ? 5 : 2);
      for (i = 0; i < j; i ++) {
        sq = FILESQ_SIDE_PIECE(pos, FIRST_PIECE(pt, i));
        if (sq != -1) {
          nFileList[FILESQ_FILE_X(sq)] ++;
        }
      }
      nPieceNum = 0;
      for (i = 0; i < j; i ++) {
        sq = FILESQ_SIDE_PIECE(pos, FIRST_PIECE(pt, i));
        if (sq != -1) {
          if (nFileList[FILESQ_FILE_X(sq)] > 1) {
            nPieceList[nPieceNum] = FIRST_PIECE(pt, i);
            nPieceNum ++;
          }
        }
      }

      // 5. 找到这些纵线以后,对这些纵线上的棋子进行排序,然后根据位置来确定棋子序号;
      for (i = 0; i < nPieceNum - 1; i ++) {
        for (j = nPieceNum - 1; j > i; j --) {
          if (FILESQ_SIDE_PIECE(pos, nPieceList[j - 1]) > FILESQ_SIDE_PIECE(pos, nPieceList[j])) {
            SWAP(nPieceList[j - 1], nPieceList[j]);
          }
        }
      }
      // 提示:如果只有两个棋子,那么“后”表示第二个棋子,如果有多个棋子,
      // 那么“一二三四五”依次代表第一个到第五个棋子,“前中后”依次代表第一个到第三个棋子。
      if (nPieceNum == 2 && nPos == 2 + DIRECT_TO_POS) {
        sq = FILESQ_SIDE_PIECE(pos, nPieceList[1]);
      } else {
        nPos -= (nPos >= DIRECT_TO_POS ? DIRECT_TO_POS : 0);
        sq = (nPos >= nPieceNum ? -1 : FILESQ_SIDE_PIECE(pos, nPieceList[nPos]));
      }
    } else {
      sq = -1;
    }
  }
  if (sq == -1) {
    return 0;
  }

  // 6. 现在已知了着法的起点,就可以根据纵线表示的后两个符号来确定着法的终点;
  xSrc = FILESQ_FILE_X(sq);
  ySrc = FILESQ_RANK_Y(sq);
  if (pt == KNIGHT_TYPE) {
    // 提示:马的进退处理比较特殊。
    xDst = Byte2Digit(FileStr.c[3]);
    if (FileStr.c[2] == '+') {
      yDst = ySrc - 3 + ABS(xDst - xSrc);
    } else {
      yDst = ySrc + 3 - ABS(xDst - xSrc);
    }
  } else {
    if (FileStr.c[2] == '+') {
      xDst = xSrc;
      yDst = ySrc - Byte2Digit(FileStr.c[3]) - 1;
    } else if (FileStr.c[2] == '-') {
      xDst = xSrc;
      yDst = ySrc + Byte2Digit(FileStr.c[3]) + 1;
    } else {
      xDst = Byte2Digit(FileStr.c[3]);
      yDst = ySrc;
    }
  }
  // 注意:yDst有可能超过范围!
  if (yDst < 0 || yDst > 9) {
    return 0;
  }

  // 7. 把相对走子方的坐标转换为固定坐标,得到着法的起点和终点。
  if (pos.sdPlayer == 0) {
    return MOVE(FILESQ_SQUARE(FILESQ_COORD_XY(xSrc, ySrc)), FILESQ_SQUARE(FILESQ_COORD_XY(xDst, yDst)));
  } else {
    return MOVE(SQUARE_FLIP(FILESQ_SQUARE(FILESQ_COORD_XY(xSrc, ySrc))),
        SQUARE_FLIP(FILESQ_SQUARE(FILESQ_COORD_XY(xDst, yDst))));
  }
}
Esempio n. 7
0
// 获得某个棋子对于本方视角的纵线优先坐标,棋子编号从0到15
inline int FILESQ_SIDE_PIECE(const PositionStruct &pos, int nPieceNum) {
  int sq;
  sq = pos.ucsqPieces[SIDE_TAG(pos.sdPlayer) + nPieceNum];
  return (sq == 0 ? -1 : pos.sdPlayer == 0 ? SQUARE_FILESQ(sq) : SQUARE_FILESQ(SQUARE_FLIP(sq)));
}
Esempio n. 8
0
void PositionStruct::PreEvaluate(void) {
  int i, sq, nMidgameValue, nWhiteAttacks, nBlackAttacks, nWhiteSimpleValue, nBlackSimpleValue;
  uint8_t ucvlPawnPiecesAttacking[256], ucvlPawnPiecesAttackless[256];

  if (!bInit) {
    bInit = true;
    // 初始化"PreEvalEx.cPopCnt16"数组,只需要初始化一次
    for (i = 0; i < 65536; i ++) {
      PreEvalEx.cPopCnt16[i] = PopCnt16(i);
    }
  }

  // 首先判断局势处于开中局还是残局阶段,方法是计算各种棋子的数量,按照车=6、马炮=3、其它=1相加。
  nMidgameValue = PopCnt32(this->dwBitPiece & BOTH_BITPIECE(ADVISOR_BITPIECE | BISHOP_BITPIECE | PAWN_BITPIECE)) * OTHER_MIDGAME_VALUE;
  nMidgameValue += PopCnt32(this->dwBitPiece & BOTH_BITPIECE(KNIGHT_BITPIECE | CANNON_BITPIECE)) * KNIGHT_CANNON_MIDGAME_VALUE;
  nMidgameValue += PopCnt32(this->dwBitPiece & BOTH_BITPIECE(ROOK_BITPIECE)) * ROOK_MIDGAME_VALUE;
  // 使用二次函数,子力很少时才认为接近残局
  nMidgameValue = (2 * TOTAL_MIDGAME_VALUE - nMidgameValue) * nMidgameValue / TOTAL_MIDGAME_VALUE;
  __ASSERT_BOUND(0, nMidgameValue, TOTAL_MIDGAME_VALUE);
  PreEval.vlAdvanced = (TOTAL_ADVANCED_VALUE * nMidgameValue + TOTAL_ADVANCED_VALUE / 2) / TOTAL_MIDGAME_VALUE;
  __ASSERT_BOUND(0, PreEval.vlAdvanced, TOTAL_ADVANCED_VALUE);
  for (sq = 0; sq < 256; sq ++) {
    if (IN_BOARD(sq)) {
      PreEval.ucvlWhitePieces[0][sq] = PreEval.ucvlBlackPieces[0][SQUARE_FLIP(sq)] = (uint8_t)
          ((cucvlKingPawnMidgameAttacking[sq] * nMidgameValue + cucvlKingPawnEndgameAttacking[sq] * (TOTAL_MIDGAME_VALUE - nMidgameValue)) / TOTAL_MIDGAME_VALUE);
      PreEval.ucvlWhitePieces[3][sq] = PreEval.ucvlBlackPieces[3][SQUARE_FLIP(sq)] = (uint8_t)
          ((cucvlKnightMidgame[sq] * nMidgameValue + cucvlKnightEndgame[sq] * (TOTAL_MIDGAME_VALUE - nMidgameValue)) / TOTAL_MIDGAME_VALUE);
      PreEval.ucvlWhitePieces[4][sq] = PreEval.ucvlBlackPieces[4][SQUARE_FLIP(sq)] = (uint8_t)
          ((cucvlRookMidgame[sq] * nMidgameValue + cucvlRookEndgame[sq] * (TOTAL_MIDGAME_VALUE - nMidgameValue)) / TOTAL_MIDGAME_VALUE);
      PreEval.ucvlWhitePieces[5][sq] = PreEval.ucvlBlackPieces[5][SQUARE_FLIP(sq)] = (uint8_t)
          ((cucvlCannonMidgame[sq] * nMidgameValue + cucvlCannonEndgame[sq] * (TOTAL_MIDGAME_VALUE - nMidgameValue)) / TOTAL_MIDGAME_VALUE);
      ucvlPawnPiecesAttacking[sq] = PreEval.ucvlWhitePieces[0][sq];
      ucvlPawnPiecesAttackless[sq] = (uint8_t)
          ((cucvlKingPawnMidgameAttackless[sq] * nMidgameValue + cucvlKingPawnEndgameAttackless[sq] * (TOTAL_MIDGAME_VALUE - nMidgameValue)) / TOTAL_MIDGAME_VALUE);
    }
  }
  for (i = 0; i < 16; i ++) {
    PreEvalEx.vlHollowThreat[i] = cvlHollowThreat[i] * (nMidgameValue + TOTAL_MIDGAME_VALUE) / (TOTAL_MIDGAME_VALUE * 2);
    __ASSERT_BOUND(0, PreEvalEx.vlHollowThreat[i], cvlHollowThreat[i]);
    PreEvalEx.vlCentralThreat[i] = cvlCentralThreat[i];
  }

  // 然后判断各方是否处于进攻状态,方法是计算各种过河棋子的数量,按照车马2炮兵1相加。
  nWhiteAttacks = nBlackAttacks = 0;
  for (i = SIDE_TAG(0) + KNIGHT_FROM; i <= SIDE_TAG(0) + ROOK_TO; i ++) {
    if (this->ucsqPieces[i] != 0 && BLACK_HALF(this->ucsqPieces[i])) {
      nWhiteAttacks += 2;
    }
  }
  for (i = SIDE_TAG(0) + CANNON_FROM; i <= SIDE_TAG(0) + PAWN_TO; i ++) {
    if (this->ucsqPieces[i] != 0 && BLACK_HALF(this->ucsqPieces[i])) {
      nWhiteAttacks ++;
    }
  }
  for (i = SIDE_TAG(1) + KNIGHT_FROM; i <= SIDE_TAG(1) + ROOK_TO; i ++) {
    if (this->ucsqPieces[i] != 0 && WHITE_HALF(this->ucsqPieces[i])) {
      nBlackAttacks += 2;
    }
  }
  for (i = SIDE_TAG(1) + CANNON_FROM; i <= SIDE_TAG(1) + PAWN_TO; i ++) {
    if (this->ucsqPieces[i] != 0 && WHITE_HALF(this->ucsqPieces[i])) {
      nBlackAttacks ++;
    }
  }
  // 如果本方轻子数比对方多,那么每多一个轻子(车算2个轻子)威胁值加2。威胁值最多不超过8。
  nWhiteSimpleValue = PopCnt16(this->wBitPiece[0] & ROOK_BITPIECE) * 2 + PopCnt16(this->wBitPiece[0] & (KNIGHT_BITPIECE | CANNON_BITPIECE));
  nBlackSimpleValue = PopCnt16(this->wBitPiece[1] & ROOK_BITPIECE) * 2 + PopCnt16(this->wBitPiece[1] & (KNIGHT_BITPIECE | CANNON_BITPIECE));
  if (nWhiteSimpleValue > nBlackSimpleValue) {
    nWhiteAttacks += (nWhiteSimpleValue - nBlackSimpleValue) * 2;
  } else {
    nBlackAttacks += (nBlackSimpleValue - nWhiteSimpleValue) * 2;
  }
  nWhiteAttacks = MIN(nWhiteAttacks, TOTAL_ATTACK_VALUE);
  nBlackAttacks = MIN(nBlackAttacks, TOTAL_ATTACK_VALUE);
  PreEvalEx.vlBlackAdvisorLeakage = TOTAL_ADVISOR_LEAKAGE * nWhiteAttacks / TOTAL_ATTACK_VALUE;
  PreEvalEx.vlWhiteAdvisorLeakage = TOTAL_ADVISOR_LEAKAGE * nBlackAttacks / TOTAL_ATTACK_VALUE;
  __ASSERT_BOUND(0, nWhiteAttacks, TOTAL_ATTACK_VALUE);
  __ASSERT_BOUND(0, nBlackAttacks, TOTAL_ATTACK_VALUE);
  __ASSERT_BOUND(0, PreEvalEx.vlBlackAdvisorLeakage, TOTAL_ADVISOR_LEAKAGE);
  __ASSERT_BOUND(0, PreEvalEx.vlBlackAdvisorLeakage, TOTAL_ADVISOR_LEAKAGE);
  for (sq = 0; sq < 256; sq ++) {
    if (IN_BOARD(sq)) {
      PreEval.ucvlWhitePieces[1][sq] = PreEval.ucvlWhitePieces[2][sq] = (uint8_t) ((cucvlAdvisorBishopThreatened[sq] * nBlackAttacks +
          (PreEval.bPromotion ? cucvlAdvisorBishopPromotionThreatless[sq] : cucvlAdvisorBishopThreatless[sq]) * (TOTAL_ATTACK_VALUE - nBlackAttacks)) / TOTAL_ATTACK_VALUE);
      PreEval.ucvlBlackPieces[1][sq] = PreEval.ucvlBlackPieces[2][sq] = (uint8_t) ((cucvlAdvisorBishopThreatened[SQUARE_FLIP(sq)] * nWhiteAttacks +
          (PreEval.bPromotion ? cucvlAdvisorBishopPromotionThreatless[SQUARE_FLIP(sq)] : cucvlAdvisorBishopThreatless[SQUARE_FLIP(sq)]) * (TOTAL_ATTACK_VALUE - nWhiteAttacks)) / TOTAL_ATTACK_VALUE);
      PreEval.ucvlWhitePieces[6][sq] = (uint8_t) ((ucvlPawnPiecesAttacking[sq] * nWhiteAttacks +
          ucvlPawnPiecesAttackless[sq] * (TOTAL_ATTACK_VALUE - nWhiteAttacks)) / TOTAL_ATTACK_VALUE);
      PreEval.ucvlBlackPieces[6][sq] = (uint8_t) ((ucvlPawnPiecesAttacking[SQUARE_FLIP(sq)] * nBlackAttacks +
          ucvlPawnPiecesAttackless[SQUARE_FLIP(sq)] * (TOTAL_ATTACK_VALUE - nBlackAttacks)) / TOTAL_ATTACK_VALUE);
    }
  }
  for (i = 0; i < 16; i ++) {
    PreEvalEx.vlWhiteBottomThreat[i] = cvlBottomThreat[i] * nBlackAttacks / TOTAL_ATTACK_VALUE;
    PreEvalEx.vlBlackBottomThreat[i] = cvlBottomThreat[i] * nWhiteAttacks / TOTAL_ATTACK_VALUE;
  }

  // 检查预评价是否对称
#ifndef NDEBUG
  for (sq = 0; sq < 256; sq ++) {
    if (IN_BOARD(sq)) {
      for (i = 0; i < 7; i ++) {
        __ASSERT(PreEval.ucvlWhitePieces[i][sq] == PreEval.ucvlWhitePieces[i][SQUARE_MIRROR(sq)]);
        __ASSERT(PreEval.ucvlBlackPieces[i][sq] == PreEval.ucvlBlackPieces[i][SQUARE_MIRROR(sq)]);
      }
    }
  }
  for (i = FILE_LEFT; i <= FILE_RIGHT; i ++) {
    __ASSERT(PreEvalEx.vlWhiteBottomThreat[i] == PreEvalEx.vlWhiteBottomThreat[FILE_FLIP(i)]);
    __ASSERT(PreEvalEx.vlBlackBottomThreat[i] == PreEvalEx.vlBlackBottomThreat[FILE_FLIP(i)]);
  }
#endif

  // 调整不受威胁方少掉的仕(士)相(象)分值
  this->vlWhite = ADVISOR_BISHOP_ATTACKLESS_VALUE * (TOTAL_ATTACK_VALUE - nBlackAttacks) / TOTAL_ATTACK_VALUE;
  this->vlBlack = ADVISOR_BISHOP_ATTACKLESS_VALUE * (TOTAL_ATTACK_VALUE - nWhiteAttacks) / TOTAL_ATTACK_VALUE;
  // 如果允许升变,那么不受威胁的仕(士)相(象)分值就少了一半
  if (PreEval.bPromotion) {
    this->vlWhite /= 2;
    this->vlBlack /= 2;
  }
  // 最后重新计算子力位置分
  for (i = 16; i < 32; i ++) {
    sq = this->ucsqPieces[i];
    if (sq != 0) {
      __ASSERT_SQUARE(sq);
      this->vlWhite += PreEval.ucvlWhitePieces[PIECE_TYPE(i)][sq];
    }
  }
  for (i = 32; i < 48; i ++) {
    sq = this->ucsqPieces[i];
    if (sq != 0) {
      __ASSERT_SQUARE(sq);
      this->vlBlack += PreEval.ucvlBlackPieces[PIECE_TYPE(i)][sq];
    }
  }
}