/** Record move as a failure. */ public void addFail(Position pos, Move m, int depth) { int p = pos.getPiece(m.from); int cnt = depth; countFail[p][m.to] += cnt; score[p][m.to] = -1; }
/** * Return true if the side to move can take the opponents king. */ public static bool canTakeKing(Position pos) { pos.setWhiteMove(!pos.whiteMove); bool ret = inCheck(pos); pos.setWhiteMove(!pos.whiteMove); return ret; }
/** * Prints board to console */ public static void DispBoard(Position pos) { string ll = " +----+----+----+----+----+----+----+----+"; SystemHelper.println(ll); for (int y = 7; y >= 0; y--) { string ret = " |"; for (int x = 0; x < 8; x++) { ret+=' '; int p = pos.getPiece(Position.getSquare(x, y)); if (p == Piece.EMPTY) { bool dark = Position.darkSquare(x, y); ret+=(dark ? ".. |" : " |"); } else { ret+=(Piece.isWhite(p) ? ' ' : '*'); string pieceName = pieceToChar(p); if (pieceName.Length == 0) pieceName = "P"; ret+=pieceName; ret+=" |"; } } SystemHelper.println(ret); SystemHelper.println(ll); } }
public string getCommand(Position pos, bool drawOffer, List<Position> history) { // Create a search object ulong[] posHashList = new ulong[200 + history.Count]; int posHashListSize = 0; for(int i=0;i<history.Count;i++) { Position p = history[i]; posHashList[posHashListSize++] = p.zobristHash(); } tt.nextGeneration(); Search sc = new Search(pos, posHashList, posHashListSize, tt); // Determine all legal moves MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); sc.scoreMoveList(moves, 0); // Test for "game over" if (moves.size == 0) { // Switch sides so that the human can decide what to do next. return "swap"; } if (bookEnabled) { Move bookMove = book.getBookMove(pos); if (bookMove != null) { SystemHelper.printf("Book moves: " + book.getAllBookMoves(pos)); return TextIO.moveTostring(pos, bookMove, true); } } // Find best move using iterative deepening currentSearch = sc; sc.setListener(listener); Move bestM; if ((moves.size == 1) && (canClaimDraw(pos, posHashList, posHashListSize, moves.m[0]) == "")) { bestM = moves.m[0]; bestM.score = 0; } else if (randomMode) { bestM = findSemiRandomMove(sc, moves); } else { sc.timeLimit(minTimeMillis, maxTimeMillis); bestM = sc.iterativeDeepening(moves, maxDepth, maxNodes, verbose); } currentSearch = null; // tt.printStats(); string strMove = TextIO.moveTostring(pos, bestM, true); bestmv = bestM; // Claim draw if appropriate if (bestM.score <= 0) { string drawClaim = canClaimDraw(pos, posHashList, posHashListSize, bestM); if (drawClaim != "") strMove = drawClaim; } return strMove; }
// To get all valid moves in one text-string public static string AllMovesTostring(Position pos, bool ulongForm) { string ret = ""; MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); for (int i = 0; i < moves.size; i++) { ret += moveTostring(pos, moves.m[i], ulongForm, moves) + ";"; } return ret; }
/** Record move as a success. */ public void addSuccess(Position pos, Move m, int depth) { int p = pos.getPiece(m.from); int cnt = depth; int val = countSuccess[p][m.to] + cnt; if (val > 1000) { val /= 2; countFail[p][m.to] /= 2; } countSuccess[p][m.to] = val; score[p][m.to] = -1; }
/** Return a string describing all book moves. */ public string getAllBookMoves(Position pos) { string ret = ""; List<BookEntry> bookMoves = bookMap[pos.zobristHash()]; if (bookMoves != null) { BookEntry be; for (int i = 0; i < bookMoves.Count; i++) { be = bookMoves[i]; string moveStr = TextIO.moveTostring(pos, be.move, false); ret = ret + (moveStr + "(" + be.count.ToString() + ") "); } } return ret; }
//@Override public string getCommand(Position pos, bool drawOffer, List<Position> history) { try { string moveStr = inp; if (moveStr == null) return "quit"; if (moveStr.Length == 0) { return lastCmd; } else { lastCmd = moveStr; } return moveStr; } catch (IOException ex) { return "quit"; } }
/** Get a score between 0 and 49, depending of the success/fail ratio of the move. */ public int getHistScore(Position pos, Move m) { int p = pos.getPiece(m.from); int ret = score[p][m.to]; if (ret >= 0) return ret; int succ = countSuccess[p][m.to]; int fail = countFail[p][m.to]; if (succ + fail > 0) { ret = succ * 49 / (succ + fail); } else { ret = 0; } score[p][m.to] = ret; return ret; }
/** Extract the PV starting from pos, using hash entries, both exact scores and bounds. */ public string extractPV(Position pos) { string ret = ""; pos = new Position(pos); // To avoid modifying the input parameter bool first = true; TTEntry ent = probe(pos.historyHash()); UndoInfo ui = new UndoInfo(); List<ulong> hashHistory = new List<ulong>(); bool repetition = false; while (ent.type != TTEntry.T_EMPTY) { string type = ""; if (ent.type == TTEntry.T_LE) { type = "<"; } else if (ent.type == TTEntry.T_GE) { type = ">"; } Move m = new Move(0,0,0); ent.getMove(m); MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); bool contains = false; for (int mi = 0; mi < moves.size; mi++) if (moves.m[mi].equals(m)) { contains = true; break; } if (!contains) break; string moveStr = TextIO.moveTostring(pos, m, false); if (repetition) break; if (!first) { ret += " "; } ret += type + moveStr; pos.makeMove(m, ui); if (hashHistory.Contains(pos.zobristHash())) { repetition = true; } hashHistory.Add(pos.zobristHash()); ent = probe(pos.historyHash()); first = false; } return ret; }
/** Return a random book move for a position, or null if out of book. */ public Move getBookMove(Position pos) { long t0 = SystemHelper.currentTimeMillis(); Random rndGen = new Random((int)t0); ulong key = pos.zobristHash(); bool iskey = bookMap.ContainsKey(key); List<BookEntry> bookMoves = (iskey ? bookMap[key] : null); if (bookMoves == null) { return null; } MoveGen.MoveList legalMoves = new MoveGen().pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, legalMoves); int sum = 0; for (int i = 0; i < bookMoves.Count; i++) { BookEntry be = bookMoves[i]; bool contains = false; for (int mi = 0; mi < legalMoves.size; mi++) if (legalMoves.m[mi].equals(be.move)) { contains = true; break; } if (!contains) { // If an illegal move was found, it means there was a hash collision. return null; } sum += getWeight(bookMoves[i].count); } if (sum <= 0) { return null; } int rnd = rndGen.Next(sum); sum = 0; for (int i = 0; i < bookMoves.Count; i++) { sum += getWeight(bookMoves[i].count); if (rnd < sum) { return bookMoves[i].move; } } // Should never get here throw new RuntimeException(); }
/** Remove pseudo-legal EP square if it is not legal, ie would leave king in check. */ public static void fixupEPSquare(Position pos) { int epSquare = pos.getEpSquare(); if (epSquare >= 0) { MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); bool epValid = false; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; if (m.to == epSquare) { if (pos.getPiece(m.from) == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { epValid = true; break; } } } if (!epValid) { pos.setEpSquare(-1); } } }
/** Compute king safety for both kings. */ private int kingSafety(Position pos) { int minM = rV + bV; int m = (pos.wMtrl - pos.wMtrlPawns + pos.bMtrl - pos.bMtrlPawns) / 2; if (m <= minM) return 0; int maxM = qV + 2 * rV + 2 * bV + 2 * nV; int score = kingSafetyKPPart(pos); if (Position.getY(pos.wKingSq) == 0) { if (((pos.pieceTypeBB[Piece.WKING] & 0x60L) != 0) && // King on f1 or g1 ((pos.pieceTypeBB[Piece.WROOK] & 0xC0L) != 0) && // Rook on g1 or h1 ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[6]) != 0) && ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[7]) != 0)) { score -= 6 * 15; } else if (((pos.pieceTypeBB[Piece.WKING] & 0x6L) != 0) && // King on b1 or c1 ((pos.pieceTypeBB[Piece.WROOK] & 0x3L) != 0) && // Rook on a1 or b1 ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[0]) != 0) && ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[1]) != 0)) { score -= 6 * 15; } } if (Position.getY(pos.bKingSq) == 7) { if (((pos.pieceTypeBB[Piece.BKING] & 0x6000000000000000L) != 0) && // King on f8 or g8 ((pos.pieceTypeBB[Piece.BROOK] & 0xC000000000000000L) != 0) && // Rook on g8 or h8 ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[6]) != 0) && ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[7]) != 0)) { score += 6 * 15; } else if (((pos.pieceTypeBB[Piece.BKING] & 0x600000000000000L) != 0) && // King on b8 or c8 ((pos.pieceTypeBB[Piece.BROOK] & 0x300000000000000L) != 0) && // Rook on a8 or b8 ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[0]) != 0) && ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[1]) != 0)) { score += 6 * 15; } } score += (bKingAttacks - wKingAttacks) * 4; int kSafety = interpolate(m, minM, 0, maxM, score); return kSafety; // FIXME! g pawn is valuable (avoid g5, g4, gxf5) }
/** Implements special knowledge for some endgame situations. */ private int endGameEval(Position pos, int oldScore) { int score = oldScore; if (pos.wMtrl + pos.bMtrl > 6 * rV) return score; int wMtrlPawns = pos.wMtrlPawns; int bMtrlPawns = pos.bMtrlPawns; int wMtrlNoPawns = pos.wMtrl - wMtrlPawns; int bMtrlNoPawns = pos.bMtrl - bMtrlPawns; bool handled = false; if ((wMtrlPawns + bMtrlPawns == 0) && (wMtrlNoPawns < rV) && (bMtrlNoPawns < rV)) { // King + minor piece vs king + minor piece is a draw return 0; } if (!handled && (pos.wMtrl == qV) && (pos.bMtrl == pV) && (BITS.bitCount(pos.pieceTypeBB[Piece.WQUEEN]) == 1)) { int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]); int wq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WQUEEN]); int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]); int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]); score = evalKQKP(wk, wq, bk, bp); handled = true; } if (!handled && (pos.bMtrl == qV) && (pos.wMtrl == pV) && (BITS.bitCount(pos.pieceTypeBB[Piece.BQUEEN]) == 1)) { int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]); int bq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BQUEEN]); int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]); int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]); score = -evalKQKP(63-bk, 63-bq, 63-wk, 63-wp); handled = true; } if (!handled && (score > 0)) { if ((wMtrlPawns == 0) && (wMtrlNoPawns <= bMtrlNoPawns + bV)) { if (wMtrlNoPawns < rV) { return -pos.bMtrl / 50; } else { score /= 8; // Too little excess material, probably draw handled = true; } } else if ((pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WKNIGHT] | pos.pieceTypeBB[Piece.WQUEEN]) == 0) { // Check for rook pawn + wrong color bishop if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskBToHFiles) == 0) && ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) == 0) && ((pos.pieceTypeBB[Piece.BKING] & 0x0303000000000000L) != 0)) { return 0; } else if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskAToGFiles) == 0) && ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) == 0) && ((pos.pieceTypeBB[Piece.BKING] & 0xC0C0000000000000L) != 0)) { return 0; } } } if (!handled) { if (bMtrlPawns == 0) { if (wMtrlNoPawns - bMtrlNoPawns > bV) { int wKnights = BITS.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]); int wBishops = BITS.bitCount(pos.pieceTypeBB[Piece.WBISHOP]); if ((wKnights == 2) && (wMtrlNoPawns == 2 * nV) && (bMtrlNoPawns == 0)) { score /= 50; // KNNK is a draw } else if ((wKnights == 1) && (wBishops == 1) && (wMtrlNoPawns == nV + bV) && (bMtrlNoPawns == 0)) { score /= 10; score += nV + bV + 300; int kSq = pos.getKingSq(false); int x = Position.getX(kSq); int y = Position.getY(kSq); if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) { score += (7 - distToH1A8[7-y][7-x]) * 10; } else { score += (7 - distToH1A8[7-y][x]) * 10; } } else { score += 300; // Enough excess material, should win } handled = true; } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (wMtrlPawns == pV)) { // KPK int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]); score = kpkEval(pos.getKingSq(true), pos.getKingSq(false), wp, pos.whiteMove); handled = true; } } } if (!handled && (score < 0)) { if ((bMtrlPawns == 0) && (bMtrlNoPawns <= wMtrlNoPawns + bV)) { if (bMtrlNoPawns < rV) { return pos.wMtrl / 50; } else { score /= 8; // Too little excess material, probably draw handled = true; } } else if ((pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BKNIGHT] | pos.pieceTypeBB[Piece.BQUEEN]) == 0) { // Check for rook pawn + wrong color bishop if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskBToHFiles) == 0) && ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) == 0) && ((pos.pieceTypeBB[Piece.WKING] & 0x0303L) != 0)) { return 0; } else if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskAToGFiles) == 0) && ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) == 0) && ((pos.pieceTypeBB[Piece.WKING] & 0xC0C0L) != 0)) { return 0; } } } if (!handled) { if (wMtrlPawns == 0) { if (bMtrlNoPawns - wMtrlNoPawns > bV) { int bKnights = BITS.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]); int bBishops = BITS.bitCount(pos.pieceTypeBB[Piece.BBISHOP]); if ((bKnights == 2) && (bMtrlNoPawns == 2 * nV) && (wMtrlNoPawns == 0)) { score /= 50; // KNNK is a draw } else if ((bKnights == 1) && (bBishops == 1) && (bMtrlNoPawns == nV + bV) && (wMtrlNoPawns == 0)) { score /= 10; score -= nV + bV + 300; int kSq = pos.getKingSq(true); int x = Position.getX(kSq); int y = Position.getY(kSq); if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) { score -= (7 - distToH1A8[7-y][7-x]) * 10; } else { score -= (7 - distToH1A8[7-y][x]) * 10; } } else { score -= 300; // Enough excess material, should win } handled = true; } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (bMtrlPawns == pV)) { // KPK int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]); score = -kpkEval(63-pos.getKingSq(false), 63-pos.getKingSq(true), 63-bp, !pos.whiteMove); handled = true; } } } return score; // FIXME! Add evaluation of KRKP // FIXME! Add evaluation of KRPKR : eg 8/8/8/5pk1/1r6/R7/8/4K3 w - - 0 74 // FIXME! KRBKR is very hard to draw }
/** Compute pawn hash data for pos. */ private void computePawnHashData(Position pos, PawnHashData ph) { int score = 0; // Evaluate double pawns and pawn islands ulong wPawns = pos.pieceTypeBB[Piece.WPAWN]; ulong wPawnFiles = BitBoard.southFill(wPawns) & 0xff; int wDouble = BITS.bitCount(wPawns) - BITS.bitCount(wPawnFiles); int wIslands = BITS.bitCount(((~wPawnFiles) >> 1) & wPawnFiles); int wIsolated = BITS.bitCount(~(wPawnFiles<<1) & wPawnFiles & ~(wPawnFiles>>1)); ulong bPawns = pos.pieceTypeBB[Piece.BPAWN]; ulong bPawnFiles = BitBoard.southFill(bPawns) & 0xff; int bDouble = BITS.bitCount(bPawns) - BITS.bitCount(bPawnFiles); int bIslands = BITS.bitCount(((~bPawnFiles) >> 1) & bPawnFiles); int bIsolated = BITS.bitCount(~(bPawnFiles<<1) & bPawnFiles & ~(bPawnFiles>>1)); score -= (wDouble - bDouble) * 25; score -= (wIslands - bIslands) * 15; score -= (wIsolated - bIsolated) * 15; // Evaluate backward pawns, defined as a pawn that guards a friendly pawn, // can't be guarded by friendly pawns, can advance, but can't advance without // being captured by an enemy pawn. ulong wPawnAttacks = (((wPawns & BitBoard.maskBToHFiles) << 7) | ((wPawns & BitBoard.maskAToGFiles) << 9)); ulong bPawnAttacks = (((bPawns & BitBoard.maskBToHFiles) >> 9) | ((bPawns & BitBoard.maskAToGFiles) >> 7)); ulong wBackward = wPawns & ~((wPawns | bPawns) >> 8) & (bPawnAttacks >> 8) & ~BitBoard.northFill(wPawnAttacks); wBackward &= (((wPawns & BitBoard.maskBToHFiles) >> 9) | ((wPawns & BitBoard.maskAToGFiles) >> 7)); wBackward &= ~BitBoard.northFill(bPawnFiles); ulong bBackward = bPawns & ~((wPawns | bPawns) << 8) & (wPawnAttacks << 8) & ~BitBoard.southFill(bPawnAttacks); bBackward &= (((bPawns & BitBoard.maskBToHFiles) << 7) | ((bPawns & BitBoard.maskAToGFiles) << 9)); bBackward &= ~BitBoard.northFill(wPawnFiles); score -= (BITS.bitCount(wBackward) - BITS.bitCount(bBackward)) * 15; // Evaluate passed pawn bonus, white ulong passedPawnsW = wPawns & ~BitBoard.southFill(bPawns | bPawnAttacks | (wPawns >> 8)); int[] ppBonus = {-1,24,26,30,36,47,64,-1}; int passedBonusW = 0; if (passedPawnsW != 0) { ulong guardedPassedW = passedPawnsW & (((wPawns & BitBoard.maskBToHFiles) << 7) | ((wPawns & BitBoard.maskAToGFiles) << 9)); passedBonusW += 15 * BITS.bitCount(guardedPassedW); ulong m = passedPawnsW; while (m != 0) { int sq = /*long.numberOfTrailingZeros(m) */ BitBoard.numberOfTrailingZeros(m); int y = Position.getY(sq); passedBonusW += ppBonus[y]; m &= m-1; } } // Evaluate passed pawn bonus, black ulong passedPawnsB = bPawns & ~BitBoard.northFill(wPawns | wPawnAttacks | (bPawns << 8)); int passedBonusB = 0; if (passedPawnsB != 0) { ulong guardedPassedB = passedPawnsB & (((bPawns & BitBoard.maskBToHFiles) >> 9) | ((bPawns & BitBoard.maskAToGFiles) >> 7)); passedBonusB += 15 * BITS.bitCount(guardedPassedB); ulong m = passedPawnsB; while (m != 0) { int sq = /*long.numberOfTrailingZeros(m) */ BitBoard.numberOfTrailingZeros(m); int y = Position.getY(sq); passedBonusB += ppBonus[7-y]; m &= m-1; } } // Connected passed pawn bonus. Seems logical but doesn't help in tests // if (passedPawnsW != 0) // passedBonusW += 15 * BITS.bitCount(passedPawnsW & ((passedPawnsW & BitBoard.maskBToHFiles) >> 1)); // if (passedPawnsB != 0) // passedBonusB += 15 * BITS.bitCount(passedPawnsB & ((passedPawnsB & BitBoard.maskBToHFiles) >> 1)); ph.key = pos.pawnZobristHash(); ph.score = score; ph.passedBonusW = (short)passedBonusW; ph.passedBonusB = (short)passedBonusB; ph.passedPawnsW = passedPawnsW; ph.passedPawnsB = passedPawnsB; }
/** Score castling ability. */ private int castleBonus(Position pos) { if (pos.getCastleMask() == 0) return 0; int k1 = kt1b[7*8+6] - kt1b[7*8+4]; int k2 = kt2b[7*8+6] - kt2b[7*8+4]; int t1 = qV + 2 * rV + 2 * bV; int t2 = rV; int t = pos.bMtrl - pos.bMtrlPawns; int ks = interpolate(t, t2, k2, t1, k1); int castleValue = ks + rt1b[7*8+5] - rt1b[7*8+7]; if (castleValue <= 0) return 0; ulong occupied = pos.whiteBB | pos.blackBB; int tmp = (int) (occupied & 0x6E); if (pos.a1Castle()) tmp |= 1; if (pos.h1Castle()) tmp |= (1 << 7); int wBonus = (castleValue * castleFactor[tmp]) >> 10; tmp = (int) ((occupied >> 56) & 0x6E); if (pos.a8Castle()) tmp |= 1; if (pos.h8Castle()) tmp |= (1 << 7); int bBonus = (castleValue * castleFactor[tmp]) >> 10; return wBonus - bBonus; }
private static bool isCapture(Position pos, Move move) { if (pos.getPiece(move.to) == Piece.EMPTY) { int p = pos.getPiece(move.from); if ((p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) && (move.to == pos.getEpSquare())) { return true; } else { return false; } } else { return true; } }
/** Compute score based on piece square tables. Positive values are good for white. */ private int pieceSquareEval(Position pos) { int score = 0; int wMtrl = pos.wMtrl; int bMtrl = pos.bMtrl; int wMtrlPawns = pos.wMtrlPawns; int bMtrlPawns = pos.bMtrlPawns; // Kings { int t1 = qV + 2 * rV + 2 * bV; int t2 = rV; { int k1 = pos.psScore1[Piece.WKING]; int k2 = pos.psScore2[Piece.WKING]; int t = bMtrl - bMtrlPawns; score += interpolate(t, t2, k2, t1, k1); } { int k1 = pos.psScore1[Piece.BKING]; int k2 = pos.psScore2[Piece.BKING]; int t = wMtrl - wMtrlPawns; score -= interpolate(t, t2, k2, t1, k1); } } // Pawns { int t1 = qV + 2 * rV + 2 * bV; int t2 = rV; int wp1 = pos.psScore1[Piece.WPAWN]; int wp2 = pos.psScore2[Piece.WPAWN]; if ((wp1 != 0) || (wp2 != 0)) { int tw = bMtrl - bMtrlPawns; score += interpolate(tw, t2, wp2, t1, wp1); } int bp1 = pos.psScore1[Piece.BPAWN]; int bp2 = pos.psScore2[Piece.BPAWN]; if ((bp1 != 0) || (bp2 != 0)) { int tb = wMtrl - wMtrlPawns; score -= interpolate(tb, t2, bp2, t1, bp1); } } // Knights { int t1 = qV + 2 * rV + 1 * bV + 1 * nV + 6 * pV; int t2 = nV + 8 * pV; int n1 = pos.psScore1[Piece.WKNIGHT]; int n2 = pos.psScore2[Piece.WKNIGHT]; if ((n1 != 0) || (n2 != 0)) { score += interpolate(bMtrl, t2, n2, t1, n1); } n1 = pos.psScore1[Piece.BKNIGHT]; n2 = pos.psScore2[Piece.BKNIGHT]; if ((n1 != 0) || (n2 != 0)) { score -= interpolate(wMtrl, t2, n2, t1, n1); } } // Bishops { score += pos.psScore1[Piece.WBISHOP]; score -= pos.psScore1[Piece.BBISHOP]; } // Queens { ulong occupied = pos.whiteBB | pos.blackBB; score += pos.psScore1[Piece.WQUEEN]; ulong m = pos.pieceTypeBB[Piece.WQUEEN]; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); ulong atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied); wAttacksBB |= atk; score += queenMobScore[BITS.bitCount(atk & ~pos.whiteBB)]; bKingAttacks += BITS.bitCount(atk & bKingZone) * 2; m &= m-1; } score -= pos.psScore1[Piece.BQUEEN]; m = pos.pieceTypeBB[Piece.BQUEEN]; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); ulong atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied); bAttacksBB |= atk; score -= queenMobScore[BITS.bitCount(atk & ~pos.blackBB)]; wKingAttacks += BITS.bitCount(atk & wKingZone) * 2; m &= m-1; } } // Rooks { int r1 = pos.psScore1[Piece.WROOK]; if (r1 != 0) { int nP = bMtrlPawns / pV; int s = r1 * Math.Min(nP, 6) / 6; score += s; } r1 = pos.psScore1[Piece.BROOK]; if (r1 != 0) { int nP = wMtrlPawns / pV; int s = r1 * Math.Min(nP, 6) / 6; score -= s; } } return score; }
/** Implement the "when ahead trade pieces, when behind trade pawns" rule. */ private int tradeBonus(Position pos) { int wM = pos.wMtrl; int bM = pos.bMtrl; int wPawn = pos.wMtrlPawns; int bPawn = pos.bMtrlPawns; int deltaScore = wM - bM; int pBonus = 0; pBonus += interpolate((deltaScore > 0) ? wPawn : bPawn, 0, -30 * deltaScore / 100, 6 * pV, 0); pBonus += interpolate((deltaScore > 0) ? bM : wM, 0, 30 * deltaScore / 100, qV + 2 * rV + 2 * bV + 2 * nV, 0); return pBonus; }
/** Compute rook bonus. Rook on open/half-open file. */ private int rookBonus(Position pos) { int score = 0; ulong wPawns = pos.pieceTypeBB[Piece.WPAWN]; ulong bPawns = pos.pieceTypeBB[Piece.BPAWN]; ulong occupied = pos.whiteBB | pos.blackBB; ulong m = pos.pieceTypeBB[Piece.WROOK]; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); int x = Position.getX(sq); if ((wPawns & BitBoard.maskFile[x]) == 0) { // At least half-open file score += (bPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12; } ulong atk = BitBoard.rookAttacks(sq, occupied); wAttacksBB |= atk; score += rookMobScore[BITS.bitCount(atk & ~pos.whiteBB)]; if ((atk & bKingZone) != 0) bKingAttacks += BITS.bitCount(atk & bKingZone); m &= m-1; } ulong r7 = pos.pieceTypeBB[Piece.WROOK] & 0x00ff000000000000L; if (((r7 & (r7 - 1)) != 0) && ((pos.pieceTypeBB[Piece.BKING] & 0xff00000000000000L) != 0)) score += 20; // Two rooks on 7:th row m = pos.pieceTypeBB[Piece.BROOK]; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); int x = Position.getX(sq); if ((bPawns & BitBoard.maskFile[x]) == 0) { score -= (wPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12; } ulong atk = BitBoard.rookAttacks(sq, occupied); bAttacksBB |= atk; score -= rookMobScore[BITS.bitCount(atk & ~pos.blackBB)]; if ((atk & wKingZone) != 0) wKingAttacks += BITS.bitCount(atk & wKingZone); m &= m-1; } r7 = pos.pieceTypeBB[Piece.BROOK] & 0xff00L; if (((r7 & (r7 - 1)) != 0) && ((pos.pieceTypeBB[Piece.WKING] & 0xffL) != 0)) score -= 20; // Two rooks on 2:nd row return score; }
/*throws ChessParseError */ private static void safeSetPiece(Position pos, int col, int row, int p) { if (row < 0) throw new ChessParseError(/* "Too many rows" */); if (col > 7) throw new ChessParseError(/* "Too many columns" */); if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) { if ((row == 0) || (row == 7)) throw new ChessParseError(/* "Pawn on first/last rank" */); } pos.setPiece(Position.getSquare(col, row), p); }
private static string moveTostring(Position pos, Move move, bool ulongForm, MoveGen.MoveList moves) { string ret = ""; int wKingOrigPos = Position.getSquare(4, 0); int bKingOrigPos = Position.getSquare(4, 7); if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) { // Check white castle if (move.to == Position.getSquare(6, 0)) { ret += ("O-O"); } else if (move.to == Position.getSquare(2, 0)) { ret += ("O-O-O"); } } else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) { // Check white castle if (move.to == Position.getSquare(6, 7)) { ret += ("O-O"); } else if (move.to == Position.getSquare(2, 7)) { ret += ("O-O-O"); } } if (ret.Length == 0) { int p = pos.getPiece(move.from); ret += pieceToChar(p); int x1 = Position.getX(move.from); int y1 = Position.getY(move.from); int x2 = Position.getX(move.to); int y2 = Position.getY(move.to); if (ulongForm) { ret += ((char)(x1 + 'a')); ret += ((char)(y1 + '1')); ret += (isCapture(pos, move) ? 'x' : '-'); } else { if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { if (isCapture(pos, move)) { ret += ((char)(x1 + 'a')); } } else { int numSameTarget = 0; int numSameFile = 0; int numSameRow = 0; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; if (m == null) break; if ((pos.getPiece(m.from) == p) && (m.to == move.to)) { numSameTarget++; if (Position.getX(m.from) == x1) numSameFile++; if (Position.getY(m.from) == y1) numSameRow++; } } if (numSameTarget < 2) { // No file/row info needed } else if (numSameFile < 2) { ret += ((char)(x1 + 'a')); // Only file info needed } else if (numSameRow < 2) { ret += ((char)(y1 + '1')); // Only row info needed } else { ret += ((char)(x1 + 'a')); // File and row info needed ret += ((char)(y1 + '1')); } } if (isCapture(pos, move)) { ret += ('x'); } } ret += ((char)(x2 + 'a')); ret += ((char)(y2 + '1')); if (move.promoteTo != Piece.EMPTY) { ret += (pieceToChar(move.promoteTo)); } } UndoInfo ui = new UndoInfo(); if (MoveGen.givesCheck(pos, move)) { pos.makeMove(move, ui); MoveGen MG = new MoveGen(); MoveGen.MoveList nextMoves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, nextMoves); if (nextMoves.size == 0) { ret += ('#'); } else { ret += ('+'); } pos.unMakeMove(move, ui); } return ret; }
private int kingSafetyKPPart(Position pos) { ulong key = pos.pawnZobristHash() ^ pos.kingZobristHash(); KingSafetyHashData ksh = kingSafetyHash[(int)key & (kingSafetyHash.Length - 1)]; if (ksh.key != key) { int score = 0; ulong wPawns = pos.pieceTypeBB[Piece.WPAWN]; ulong bPawns = pos.pieceTypeBB[Piece.BPAWN]; { int safety = 0; int halfOpenFiles = 0; if (Position.getY(pos.wKingSq) < 2) { ulong shelter = 1UL << Position.getX(pos.wKingSq); shelter |= ((shelter & BitBoard.maskBToHFiles) >> 1) | ((shelter & BitBoard.maskAToGFiles) << 1); shelter <<= 8; safety += 3 * BITS.bitCount(wPawns & shelter); safety -= 2 * BITS.bitCount(bPawns & (shelter | (shelter << 8))); shelter <<= 8; safety += 2 * BITS.bitCount(wPawns & shelter); shelter <<= 8; safety -= BITS.bitCount(bPawns & shelter); ulong wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff; if (wOpen != 0) { halfOpenFiles += 25 * BITS.bitCount(wOpen & 0xe7); halfOpenFiles += 10 * BITS.bitCount(wOpen & 0x18); } ulong bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff; if (bOpen != 0) { halfOpenFiles += 25 * BITS.bitCount(bOpen & 0xe7); halfOpenFiles += 10 * BITS.bitCount(bOpen & 0x18); } safety = Math.Min(safety, 8); } int kSafety = (safety - 9) * 15 - halfOpenFiles; score += kSafety; } { int safety = 0; int halfOpenFiles = 0; if (Position.getY(pos.bKingSq) >= 6) { ulong shelter = 1UL << (56 + Position.getX(pos.bKingSq)); shelter |= ((shelter & BitBoard.maskBToHFiles) >> 1) | ((shelter & BitBoard.maskAToGFiles) << 1); shelter >>= 8; safety += 3 * BITS.bitCount(bPawns & shelter); safety -= 2 * BITS.bitCount(wPawns & (shelter | (shelter >> 8))); shelter >>= 8; safety += 2 * BITS.bitCount(bPawns & shelter); shelter >>= 8; safety -= BITS.bitCount(wPawns & shelter); ulong wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff; if (wOpen != 0) { halfOpenFiles += 25 * BITS.bitCount(wOpen & 0xe7); halfOpenFiles += 10 * BITS.bitCount(wOpen & 0x18); } ulong bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff; if (bOpen != 0) { halfOpenFiles += 25 * BITS.bitCount(bOpen & 0xe7); halfOpenFiles += 10 * BITS.bitCount(bOpen & 0x18); } safety = Math.Min(safety, 8); } int kSafety = (safety - 9) * 15 - halfOpenFiles; score -= kSafety; } ksh.key = key; ksh.score = score; } return ksh.score; }
/** * Static evaluation of a position. * @param pos The position to evaluate. * @return The evaluation score, measured in centipawns. * Positive values are good for the side to make the next move. */ public int evalPos(Position pos) { int score = pos.wMtrl - pos.bMtrl; wKingAttacks = bKingAttacks = 0; wKingZone = BitBoard.kingAttacks[pos.getKingSq(true)]; wKingZone |= wKingZone << 8; bKingZone = BitBoard.kingAttacks[pos.getKingSq(false)]; bKingZone |= bKingZone >> 8; wAttacksBB = bAttacksBB = 0L; score += pieceSquareEval(pos); score += pawnBonus(pos); score += tradeBonus(pos); score += castleBonus(pos); score += rookBonus(pos); score += bishopEval(pos, score); score += threatBonus(pos); score += kingSafety(pos); score = endGameEval(pos, score); if (!pos.whiteMove) score = -score; return score; // FIXME! Test penalty if side to move has >1 hanging piece // FIXME! Test "tempo value" }
private int pawnBonus(Position pos) { ulong key = pos.pawnZobristHash(); PawnHashData phd = pawnHash[(int)key & (pawnHash.Length - 1)]; if (phd.key != key) computePawnHashData(pos, phd); int score = phd.score; int hiMtrl = qV + rV; score += interpolate(pos.bMtrl - pos.bMtrlPawns, 0, 2 * phd.passedBonusW, hiMtrl, phd.passedBonusW); score -= interpolate(pos.wMtrl - pos.wMtrlPawns, 0, 2 * phd.passedBonusB, hiMtrl, phd.passedBonusB); // Passed pawns are more dangerous if enemy king is far away int mtrlNoPawns; int highMtrl = qV + rV; ulong m = phd.passedPawnsW; if (m != 0) { mtrlNoPawns = pos.bMtrl - pos.bMtrlPawns; if (mtrlNoPawns < highMtrl) { int kingPos = pos.getKingSq(false); int kingX = Position.getX(kingPos); int kingY = Position.getY(kingPos); while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); int x = Position.getX(sq); int y = Position.getY(sq); int pawnDist = Math.Min(5, 7 - y); int kingDistX = Math.Abs(kingX - x); int kingDistY = Math.Abs(kingY - 7); int kingDist = Math.Max(kingDistX, kingDistY); int kScore = kingDist * 4; if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist); score += interpolate(mtrlNoPawns, 0, kScore, highMtrl, 0); if (!pos.whiteMove) kingDist--; if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) score += 500; // King can't stop pawn m &= m-1; } } } m = phd.passedPawnsB; if (m != 0) { mtrlNoPawns = pos.wMtrl - pos.wMtrlPawns; if (mtrlNoPawns < highMtrl) { int kingPos = pos.getKingSq(true); int kingX = Position.getX(kingPos); int kingY = Position.getY(kingPos); while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); int x = Position.getX(sq); int y = Position.getY(sq); int pawnDist = Math.Min(5, y); int kingDistX = Math.Abs(kingX - x); int kingDistY = Math.Abs(kingY - 0); int kingDist = Math.Max(kingDistX, kingDistY); int kScore = kingDist * 4; if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist); score -= interpolate(mtrlNoPawns, 0, kScore, highMtrl, 0); if (pos.whiteMove) kingDist--; if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) score -= 500; // King can't stop pawn m &= m-1; } } } return score; }
/** Compute white_material - black_material. */ static int material(Position pos) { return pos.wMtrl - pos.bMtrl; }
/** * Convert a chess move string to a Move object. * Any prefix of the string representation of a valid move counts as a legal move string, * as ulong as the string only matches one valid move. */ public static Move stringToMove(Position pos, string strMove) { strMove = strMove.Replace("=", ""); Move move = null; if (strMove.Length == 0) return move; MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); { char lastChar = strMove[strMove.Length - 1]; if ((lastChar == '#') || (lastChar == '+')) { MoveGen.MoveList subMoves = new MoveGen.MoveList(); int len = 0; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; string str1 = TextIO.moveTostring(pos, m, true, moves); if (str1[str1.Length - 1] == lastChar) { subMoves.m[len++] = m; } } subMoves.size = len; moves = subMoves; strMove = normalizeMovestring(strMove); } } for (int i = 0; i < 2; i++) { // Search for full match for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; string str1 = normalizeMovestring(TextIO.moveTostring(pos, m, true, moves)); string str2 = normalizeMovestring(TextIO.moveTostring(pos, m, false, moves)); if (i == 0) { if ((str1 == strMove) || (str2 == strMove)) { return m; } } else { if ((strMove.ToLower() == str1.ToLower()) || (strMove.ToLower() == str2.ToLower())) { return m; } } } } for (int i = 0; i < 2; i++) { // Search for unique substring match for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; string str1 = normalizeMovestring(TextIO.moveTostring(pos, m, true)); string str2 = normalizeMovestring(TextIO.moveTostring(pos, m, false)); bool match; if (i == 0) { match = (str1.StartsWith(strMove) || str2.StartsWith(strMove)); } else { match = (str1.ToLower().StartsWith(strMove.ToLower()) || str2.ToLower().StartsWith(strMove.ToLower())); } if (match) { if (move != null) { return null; // More than one match, not ok } else { move = m; } } } if (move != null) return move; } return move; }
/** Compute bishop evaluation. */ private int bishopEval(Position pos, int oldScore) { int score = 0; ulong occupied = pos.whiteBB | pos.blackBB; ulong wBishops = pos.pieceTypeBB[Piece.WBISHOP]; ulong bBishops = pos.pieceTypeBB[Piece.BBISHOP]; if ((wBishops | bBishops) == 0) return 0; ulong m = wBishops; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); ulong atk = BitBoard.bishopAttacks(sq, occupied); wAttacksBB |= atk; score += bishMobScore[BITS.bitCount(atk & ~pos.whiteBB)]; if ((atk & bKingZone) != 0) bKingAttacks += BITS.bitCount(atk & bKingZone); m &= m-1; } m = bBishops; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); ulong atk = BitBoard.bishopAttacks(sq, occupied); bAttacksBB |= atk; score -= bishMobScore[BITS.bitCount(atk & ~pos.blackBB)]; if ((atk & wKingZone) != 0) wKingAttacks += BITS.bitCount(atk & wKingZone); m &= m-1; } bool whiteDark = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq ) != 0; bool whiteLight = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) != 0; bool blackDark = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq ) != 0; bool blackLight = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) != 0; int numWhite = (whiteDark ? 1 : 0) + (whiteLight ? 1 : 0); int numBlack = (blackDark ? 1 : 0) + (blackLight ? 1 : 0); // Bishop pair bonus if (numWhite == 2) { int numPawns = pos.wMtrlPawns / pV; score += 20 + (8 - numPawns) * 3; } if (numBlack == 2) { int numPawns = pos.bMtrlPawns / pV; score -= 20 + (8 - numPawns) * 3; } // FIXME!!! Bad bishop if ((numWhite == 1) && (numBlack == 1) && (whiteDark != blackDark) && (pos.wMtrl - pos.wMtrlPawns == pos.bMtrl - pos.bMtrlPawns)) { int penalty = (oldScore + score) / 2; int loMtrl = 2 * bV; int hiMtrl = 2 * (qV + rV + bV); int mtrl = pos.wMtrl + pos.bMtrl - pos.wMtrlPawns - pos.bMtrlPawns; score -= interpolate(mtrl, loMtrl, penalty, hiMtrl, 0); } // Penalty for bishop trapped behind pawn at a2/h2/a7/h7 if (((wBishops | bBishops) & 0x0081000000008100L) != 0) { if ((pos.squares[48] == Piece.WBISHOP) && // a7 (pos.squares[41] == Piece.BPAWN) && (pos.squares[50] == Piece.BPAWN)) score -= pV * 3 / 2; if ((pos.squares[55] == Piece.WBISHOP) && // h7 (pos.squares[46] == Piece.BPAWN) && (pos.squares[53] == Piece.BPAWN)) score -= (pos.pieceTypeBB[Piece.WQUEEN] != 0) ? pV : pV * 3 / 2; if ((pos.squares[8] == Piece.BBISHOP) && // a2 (pos.squares[17] == Piece.WPAWN) && (pos.squares[10] == Piece.WPAWN)) score += pV * 3 / 2; if ((pos.squares[15] == Piece.BBISHOP) && // h2 (pos.squares[22] == Piece.WPAWN) && (pos.squares[13] == Piece.WPAWN)) score += (pos.pieceTypeBB[Piece.BQUEEN] != 0) ? pV : pV * 3 / 2; } return score; }
private int threatBonus(Position pos) { int score = 0; // Sum values for all black pieces under attack ulong m = pos.pieceTypeBB[Piece.WKNIGHT]; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); wAttacksBB |= BitBoard.knightAttacks[sq]; m &= m-1; } wAttacksBB &= (pos.pieceTypeBB[Piece.BKNIGHT] | pos.pieceTypeBB[Piece.BBISHOP] | pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BQUEEN]); ulong pawns = pos.pieceTypeBB[Piece.WPAWN]; wAttacksBB |= (pawns & BitBoard.maskBToHFiles) << 7; wAttacksBB |= (pawns & BitBoard.maskAToGFiles) << 9; m = wAttacksBB & pos.blackBB & ~pos.pieceTypeBB[Piece.BKING]; int tmp = 0; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); tmp += pieceValue[pos.squares[sq]]; m &= m-1; } score += tmp + tmp * tmp / qV; // Sum values for all white pieces under attack m = pos.pieceTypeBB[Piece.BKNIGHT]; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); bAttacksBB |= BitBoard.knightAttacks[sq]; m &= m-1; } bAttacksBB &= (pos.pieceTypeBB[Piece.WKNIGHT] | pos.pieceTypeBB[Piece.WBISHOP] | pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WQUEEN]); pawns = pos.pieceTypeBB[Piece.BPAWN]; bAttacksBB |= (pawns & BitBoard.maskBToHFiles) >> 9; bAttacksBB |= (pawns & BitBoard.maskAToGFiles) >> 7; m = bAttacksBB & pos.whiteBB & ~pos.pieceTypeBB[Piece.WKING]; tmp = 0; while (m != 0) { int sq = BitBoard.numberOfTrailingZeros(m); tmp += pieceValue[pos.squares[sq]]; m &= m-1; } score -= tmp + tmp * tmp / qV; return score / 64; }
/** Return a FEN string corresponding to a chess Position object. */ public static string toFEN(Position pos) { string ret = ""; // Piece placement for (int r = 7; r >=0; r--) { int numEmpty = 0; for (int c = 0; c < 8; c++) { int p = pos.getPiece(Position.getSquare(c, r)); if (p == Piece.EMPTY) { numEmpty++; } else { if (numEmpty > 0) { ret+=numEmpty.ToString(); numEmpty = 0; } switch (p) { case Piece.WKING: ret+=('K'); break; case Piece.WQUEEN: ret+=('Q'); break; case Piece.WROOK: ret+=('R'); break; case Piece.WBISHOP: ret+=('B'); break; case Piece.WKNIGHT: ret+=('N'); break; case Piece.WPAWN: ret+=('P'); break; case Piece.BKING: ret+=('k'); break; case Piece.BQUEEN: ret+=('q'); break; case Piece.BROOK: ret+=('r'); break; case Piece.BBISHOP: ret+=('b'); break; case Piece.BKNIGHT: ret+=('n'); break; case Piece.BPAWN: ret+=('p'); break; default: throw new RuntimeException(); } } } if (numEmpty > 0) { ret += numEmpty.ToString(); } if (r > 0) { ret += ('/'); } } ret += (pos.whiteMove ? " w " : " b "); // Castling rights bool anyCastle = false; if (pos.h1Castle()) { ret += ('K'); anyCastle = true; } if (pos.a1Castle()) { ret += ('Q'); anyCastle = true; } if (pos.h8Castle()) { ret += ('k'); anyCastle = true; } if (pos.a8Castle()) { ret += ('q'); anyCastle = true; } if (!anyCastle) { ret += ('-'); } // En passant target square { ret += (' '); if (pos.getEpSquare() >= 0) { int x = Position.getX(pos.getEpSquare()); int y = Position.getY(pos.getEpSquare()); ret += ((char)(x + 'a')); ret += ((char)(y + '1')); } else { ret += ('-'); } } // Move counters ret += " " + pos.halfMoveClock; ret += " " + pos.fullMoveCounter; return ret; }