public static Move NoMove() { // This returns a Move, which indicates no move has been made. Move result = new Move(); result.moveType = Const.NoMoveID; return result; }
private void AddMove(int moveType, int fromPos, int toPos) { if (nrGeneratedMoves == maxNrMoves) { // optionally increase the moveList int newMaxNrMoves = maxNrMoves * 2; Move[] moveList2 = new Move[newMaxNrMoves]; for (int i = 0; i < maxNrMoves; i++) moveList2[i] = moveList[i]; maxNrMoves = newMaxNrMoves; moveList = moveList2; } // moveList[nrGeneratedMoves].moveType = moveType; moveList[nrGeneratedMoves].fromPosition = fromPos; moveList[nrGeneratedMoves].toPosition = toPos; // don't know yet how to handle promotions in seeScore moveList[nrGeneratedMoves].seeScore = 0; // will be filled later // store the possible captured piece : if (moveType < Const.SpecialMoveID) { // this can be EmptyID or some PieceType int capturedPieceType = board.SquareContents[toPos].pieceType; // if (capturedPieceType == Const.EmptyID) { moveList[nrGeneratedMoves].captureInfo = Const.NoCaptureID; } else { // yes, it is a capture. // Store the captured piece type in bits 0..2, and the capturing piece type in bits 3..5 moveList[nrGeneratedMoves].captureInfo = capturedPieceType + (board.SquareContents[fromPos].pieceType << Const.NrPieceTypeBits); // also store the SEE score moveList[nrGeneratedMoves].seeScore = attack.SEE(moveType, fromPos, toPos); } } else { switch (moveType) { case Const.EnPassantCaptureID: moveList[nrGeneratedMoves].captureInfo = Const.PawnID + (Const.PawnID << Const.NrPieceTypeBits); moveList[nrGeneratedMoves].seeScore = attack.SEE(moveType, fromPos, toPos); break; case Const.PawnPromoteQueenID: case Const.PawnPromoteRookID: case Const.PawnPromoteBishopID: case Const.PawnPromoteKnightID: // this can be EmptyID or some PieceType // this can be EmptyID or some PieceType int capturedPieceType = board.SquareContents[toPos].pieceType; // if (capturedPieceType == Const.EmptyID) moveList[nrGeneratedMoves].captureInfo = Const.NoCaptureID; else { // yes, it is a capture. // Store the captured piece type in bits 0..2, and the capturing piece type in bits 3..5 moveList[nrGeneratedMoves].captureInfo = capturedPieceType + (board.SquareContents[fromPos].pieceType << Const.NrPieceTypeBits); } break; default: moveList[nrGeneratedMoves].captureInfo = Const.NoCaptureID; break; } } nrGeneratedMoves++; }
public Move[] GenerateQuiescenceMoves(Move[] initialMoveStorage) { // This generates only captures and pawn promotions moveList = initialMoveStorage; if (moveList == null || moveList.Length < 50) moveList = new Move[50]; maxNrMoves = moveList.Length; nrGeneratedMoves = 0; // // Now generate pseudo legal moves. No check for getting into Check ! nrGeneratedMoves = 0; GenerateCapturingKingMoves(); GenerateCapturingQueenMoves(); GenerateCapturingRookMoves(); GenerateCapturingBishopMoves(); GenerateCapturingKnightMoves(); GenerateCapturingPawnMoves(); GeneratePromotingPawnMoves(); // Returns a pointer to the moveList. // This may be a different one then the initial, if the initial was to short. return moveList; }
public Move[] GenerateMoves(Move[] initialMoveStorage) { moveList = initialMoveStorage; if (moveList == null || moveList.Length < 50) moveList = new Move[50]; maxNrMoves = moveList.Length; nrGeneratedMoves = 0; // // Now generate pseudo legal moves. No check for getting into Check ! nrGeneratedMoves = 0; GenerateKingMoves(); GenerateCastleMoves(); GenerateQueenMoves(); GenerateRookMoves(); GenerateBishopMoves(); GenerateKnightMoves(); GeneratePawnMoves(); // Returns a pointer to the moveList. // This may be a different one then the initial, if the initial was to short. return moveList; }
private void ScoreMoves(int plyNr) { const int transpostionTableScore = 1 << 30; const int followPVScore = 1 << 29; const int pawnPromotionOffset = 1 << 27; const int winningCaptureScoreOffset = 1 << 25; const int equalCaptureScoreOffset = 1 << 24; const int killerMove1Score = 1 << 21; const int killerMove2Score = killerMove1Score + 100; // this seems to speed it up by a very little const int losingCaptureScoreOffset = 1 << 20; const int historyMoveScore = 1 << 14; // 16384 // int colorToMove = board.colorToMove; int historyTableNr; if (use2HistoryTables) historyTableNr = colorToMove; else historyTableNr = Const.White; // use the same table for both colors bool haveFoundPVMove = false; // assign some static score to each move and order them if (moveOrdering_SearchPV) { if (plyNr >= PrincipalVariation.Length) isFollowingThePV = false; // Searching beyond the PrincipalVariation length. } // bool searchForTTMove = false; Move ttMove = Move.NoMove(); if (moveOrdering_SearchTT) { int index = transpositionTable.GetIndex(board.HashValue, 0); if (index >= 0) { int compressedMove = transpositionTable.slots[index].compressedMove; ttMove = new Move(compressedMove); if (ttMove.moveType != Const.NoMoveID) searchForTTMove = true; } } // int nrMoves = nrGeneratedMovesInPly[plyNr]; if (scoreMatrix[plyNr] == null || scoreMatrix[plyNr].Length < nrMoves) scoreMatrix[plyNr] = new int[nrMoves + 1]; Move[] moves = moveMatrix[plyNr]; // just a pointer int[] scores = scoreMatrix[plyNr]; // just a pointer // // History : find the min and max for this set of moves int maxHistoryValue = 0; if (useMoveOrdering_History) { // find the highest history value for (int i = 0; i < nrMoves; i++) { int from = moves[i].fromPosition; int to = moves[i].toPosition; if (History[historyTableNr][from, to] > maxHistoryValue) maxHistoryValue = History[historyTableNr][from, to]; } } // for (int i = 0; i < nrMoves; i++) { // first set the score to 0 scores[i] = 0; // see if anyting can be found in the transposition table if (searchForTTMove) { if (moves[i].fromPosition == ttMove.fromPosition && moves[i].toPosition == ttMove.toPosition && moves[i].moveType == ttMove.moveType) { searchForTTMove = false; scores[i] += transpostionTableScore; } } // check for the PV if (moveOrdering_SearchPV) { // is this move in the correct place in the principal variation ? if (isFollowingThePV && !haveFoundPVMove) { if (moves[i].fromPosition == PrincipalVariation[plyNr].fromPosition && moves[i].toPosition == PrincipalVariation[plyNr].toPosition && moves[i].moveType == PrincipalVariation[plyNr].moveType) { haveFoundPVMove = true; scores[i] += followPVScore; } } } // is this a capture ? if (moves[i].captureInfo != Const.NoCaptureID) { if (moveOrdering_UseSEE) { int seeScore = moves[i].seeScore; // multiply by (historyMoveScore+1), to not let HistoryMoveScore interfere with SeeScore's if (seeScore > 0) scores[i] += winningCaptureScoreOffset + seeScore * (historyMoveScore + 1); else if (seeScore < 0) scores[i] += losingCaptureScoreOffset + seeScore * (historyMoveScore + 1); // seeScore is negative else scores[i] += equalCaptureScoreOffset; } else { // NB : pawn = 5, queen = 0 int capturedPieceType = moves[i].captureInfo & Const.PieceTypeBitMask; int capturingPieceType = (moves[i].captureInfo >> Const.NrPieceTypeBits) & Const.PieceTypeBitMask; // positive=winning , negative=losing int captureScore = capturingPieceType - capturedPieceType; if (captureScore > 0) scores[i] += winningCaptureScoreOffset + captureScore * (historyMoveScore + 1); else if (captureScore == 0) scores[i] += equalCaptureScoreOffset; else scores[i] += losingCaptureScoreOffset + captureScore * (historyMoveScore + 1); } } else { // Not a capture : is it a Killer move ? if (useKillerMoves) { if (moves[i] == KillerMoves1[plyNr]) scores[i] += killerMove1Score; else if (moves[i] == KillerMoves2[plyNr]) scores[i] += killerMove2Score; } } // is it a pawn promotion ? Only score the Queen promotion. Let the minor promotions get score = 0; if (moves[i].moveType == Const.PawnPromoteQueenID) scores[i] += pawnPromotionOffset; /* if (moves[i].moveType >= Const.SpecialMoveID) { switch (moves[i].moveType) { case Const.PawnPromoteQueenID: scores[i] += pawnPromotionOffset + 4 * (historyMoveScore + 1); break; // don't care about the minor promotions case Const.PawnPromoteRookID: scores[i] -= pawnPromotionOffset + 3 * (historyMoveScore + 1); break; case Const.PawnPromoteBishopID: scores[i] -= pawnPromotionOffset + 2 * (historyMoveScore + 1); break; case Const.PawnPromoteKnightID: scores[i] -= pawnPromotionOffset + 1 * (historyMoveScore + 1); break; } } */ if (useMoveOrdering_History && maxHistoryValue != 0) { // if maxHistoryValue == 0, History is empty. Dividing by it yields Int.MinValue !! int moveFromPos = moves[i].fromPosition; int moveToPos = moves[i].toPosition; // history now scores from historyMoveScore to 2*historyMoveScore scores[i] += historyMoveScore + (int)(historyMoveScore * 1.0 * History[historyTableNr][moveFromPos, moveToPos] / maxHistoryValue); #if CheckIfMoveOrderingHasMoves if (scores[i] == int.MinValue) MessageBox.Show("oo"); #endif } if (moveOrdering_StaticPositionValue) { // trick from Rebel : sort moves by their static position evaluation. // This might help a little for otherwise unsorted moves. // this probably only works for a very simple evaluation !!! // A better approach might be internal deepening int moveFromPos = moves[i].fromPosition; int moveToPos = moves[i].toPosition; int moveType = moves[i].moveType; switch (moves[i].moveType) { case Const.KingID: case Const.QueenID: case Const.RookID: case Const.BishopID: case Const.KnightID: scores[i] += evaluator.PieceSquareValues[colorToMove][moveType][moveToPos] - evaluator.PieceSquareValues[colorToMove][moveType][moveFromPos]; break; case Const.PawnID: case Const.Pawn2StepID: case Const.EnPassantCaptureID: scores[i] += evaluator.PieceSquareValues[colorToMove][Const.PawnID][moveToPos] - evaluator.PieceSquareValues[colorToMove][Const.PawnID][moveFromPos]; break; case Const.CastleKSID: case Const.CastleQSID: // the from/to pos is that of the king scores[i] += evaluator.PieceSquareValues[colorToMove][Const.KingID][moveToPos] - evaluator.PieceSquareValues[colorToMove][Const.KingID][moveFromPos]; break; } } } // if (moveOrdering_SearchPV && !haveFoundPVMove) isFollowingThePV = false; // lost the PV track #if CheckIfMoveOrderingHasMoves for (int i = 0; i < nrGeneratedMovesInPly[plyNr]; i++) { if (scores[i] == int.MinValue) MessageBox.Show("OhOh"); } #endif }
private void CreateStorage() { // Creates the initial storage. It will be extended if needed. nrGeneratedMovesInPly = new int[maxNrPlies]; moveMatrix = new Move[maxNrPlies][]; scoreMatrix = new int[maxNrPlies][]; PV_Matrix = new Move[maxNrPlies][]; nrMovesInPVLine = new int[maxNrPlies]; for (int i = 0; i < maxNrPlies; i++) PV_Matrix[i] = new Move[maxNrExpectedPVMoves]; KillerMoves1 = new Move[maxNrPlies]; KillerMoves2 = new Move[maxNrPlies]; // create history for both colors History = new int[2][,]; History[0] = new int[Const.NrSquares, Const.NrSquares]; History[1] = new int[Const.NrSquares, Const.NrSquares]; }
private void CheckEnoughNrPliesStorage(int plyNr) { // optionally create extra storage when the new PlyNr exceeds the maxnrPlies. if (plyNr < maxNrPlies) return; // int newMaxNrPlies = maxNrPlies + 10; Move[][] newMoveMatrix = new Move[newMaxNrPlies][]; int[][] newScoreMatrix = new int[newMaxNrPlies][]; int[] newNrGeneratedMovesInPly = new int[newMaxNrPlies]; Move[][] newPV_Matrix = new Move[newMaxNrPlies][]; int[] newNrMovesInPVLine = new int[newMaxNrPlies]; Move[] newKillerMoves1 = new Move[newMaxNrPlies]; Move[] newKillerMoves2 = new Move[newMaxNrPlies]; for (int i = 0; i < maxNrPlies; i++) { newMoveMatrix[i] = moveMatrix[i]; newScoreMatrix[i] = scoreMatrix[i]; newNrGeneratedMovesInPly[i] = nrGeneratedMovesInPly[i]; newPV_Matrix[i] = PV_Matrix[i]; newNrMovesInPVLine[i] = nrMovesInPVLine[i]; newKillerMoves1[i] = KillerMoves1[i]; newKillerMoves2[i] = KillerMoves2[i]; } // initialize the appended storage for (int i = maxNrPlies; i < newMaxNrPlies; i++) { // moveMatrix[] will be handled (created) by MoveGenerator.GenerateMoves newPV_Matrix[i] = new Move[maxNrExpectedPVMoves]; } moveMatrix = newMoveMatrix; scoreMatrix = newScoreMatrix; nrGeneratedMovesInPly = newNrGeneratedMovesInPly; PV_Matrix = newPV_Matrix; nrMovesInPVLine = newNrMovesInPVLine; KillerMoves1 = newKillerMoves1; KillerMoves2 = newKillerMoves2; maxNrPlies = newMaxNrPlies; }
public bool MakeMove(Move move) { bool moveIsLegal = true; // this will be checked if a castle is done and by the IsInCheck test // increment the HalfMoveNr halfMoveNr++; // increment the fiftyMoveNr. It will be reset later in this method if it was a capture or a pawn-move. fiftyMoveNr++; // capturedPieceType = Const.InvalidID; capturedPiecePosition = Const.InvalidID; #if HashDebug if (HashValue != CalcHashValue()) throw new ApplicationException("hash differs"); #endif // maybe remove the en-passant from the hash if (enPassantPosition != Const.InvalidID) HashValue ^= transpositionTable.EPSquareValue[enPassantPosition]; // if (move.moveType < Const.SpecialMoveID) { // a normal move of a piece enPassantPosition = Const.InvalidID; // normal move : always reset the enpassant position // Handle castling info if (canCastleQueenSide[colorToMove] || canCastleKingSide[colorToMove]) { // check for a king move if (move.moveType == Const.KingID) { ResetCanCastleQS(colorToMove); ResetCanCastleKS(colorToMove); } // check for a castle move if (move.moveType == Const.RookID) { int offset = colorToMove * 56; // check if the QS rook will move. Offset points to the queen-side rooks. if (move.fromPosition == offset) ResetCanCastleQS(colorToMove); // check if the KS rook will move. Offset+7 points to the king-side rooks. if (move.fromPosition == offset + 7) ResetCanCastleKS(colorToMove); } } // update the fiftyMoveNr if (move.moveType == Const.PawnID) { fiftyMoveNr = 0; // pawn move : reset the fiftyMoveNr // if a pawn is moved, the previous position can never be seen again : so repeatedPosition_SearchOffset = nrHashValuesInHistory; //repeatedPosition_SearchOffset = halfMoveNr - 1; } // is it a capture ? Yes : remove the captured piece if (SquareContents[move.toPosition].pieceType != Const.EmptyID) { // save state capturedPiecePosition = move.toPosition; capturedPieceType = SquareContents[move.toPosition].pieceType; // RemovePieceFromBoard(move.toPosition); fiftyMoveNr = 0; // capture : reset the fiftyMoveNr } // now make the move MovePieceOnBoard(move.fromPosition, move.toPosition); } else { // Specials // it is a Castle, enpassant capture, 2-stap pawn move or pawn promotion int castleRankOffset = colorToMove * 56; switch (move.moveType) { case Const.CastleQSID: // Maybe the castle was illegal. Do it anyway. // Illegal castle will immediately be undone in SearchMove. if (moveIsLegal) moveIsLegal = moveGenerator.CastleIsLegal(Const.CastleQSID); MovePieceOnBoard(castleRankOffset + 4, castleRankOffset + 2); // King MovePieceOnBoard(castleRankOffset + 0, castleRankOffset + 3); // Rook hasCastled[colorToMove] = true; ResetCanCastleQS(colorToMove); ResetCanCastleKS(colorToMove); break; case Const.CastleKSID: // Maybe the castle was illegal. Do it anyway. // Illegal castle will immediately be undone in SearchMove. if (moveIsLegal) moveIsLegal = moveGenerator.CastleIsLegal(Const.CastleKSID); MovePieceOnBoard(castleRankOffset + 4, castleRankOffset + 6); // King MovePieceOnBoard(castleRankOffset + 7, castleRankOffset + 5); // Rook hasCastled[colorToMove] = true; ResetCanCastleQS(colorToMove); ResetCanCastleKS(colorToMove); break; case Const.EnPassantCaptureID: capturedPieceType = Const.PawnID; if (colorToMove == Const.White) capturedPiecePosition = enPassantPosition - 8; // captured pawn is black else capturedPiecePosition = enPassantPosition + 8; // captured pawn is white RemovePieceFromBoard(capturedPiecePosition); MovePieceOnBoard(move.fromPosition, move.toPosition); break; case Const.PawnPromoteQueenID: case Const.PawnPromoteRookID: case Const.PawnPromoteBishopID: case Const.PawnPromoteKnightID: if (SquareContents[move.toPosition].pieceType != Const.EmptyID) { // save state capturedPiecePosition = move.toPosition; capturedPieceType = SquareContents[move.toPosition].pieceType; RemovePieceFromBoard(move.toPosition); // it was a capture } RemovePieceFromBoard(move.fromPosition); AddNewPieceToBoard(colorToMove, Const.QueenID + move.moveType - Const.PawnPromoteQueenID, move.toPosition); fiftyMoveNr = 0; // pawn move : reset the fiftyMoveNr break; case Const.Pawn2StepID: MovePieceOnBoard(move.fromPosition, move.toPosition); // point enPassantPosition to the jumped over square enPassantPosition = (move.fromPosition + move.toPosition) / 2; fiftyMoveNr = 0; // pawn move : reset the fiftyMoveNr // update hashValue HashValue ^= transpositionTable.EPSquareValue[enPassantPosition]; break; case Const.NullMoveID: // just do nothing break; default: throw new Exception("Invalid special move nr"); } // always reset the enPassant pasition, unless it set by the Pawn 2Step move if (move.moveType != Const.Pawn2StepID) enPassantPosition = Const.InvalidID; } // Finally check if this move was legal. If not, it will immediately be undone in SearchMove. // Do it with an IF statement, since also castling could have set moveIsLegal to false. if (IsInCheck()) moveIsLegal = false; // ToggleMoveColor(); // Store the state exactly as it was AFTER this move was made. Also stores HashValue. StoreBoardState(); // // Remember the hash-value of this board nrHashValuesInHistory++; if (nrHashValuesInHistory == HashValueHistory.Length) { // increase size ulong[] newArray = new ulong[HashValueHistory.Length + 100]; Array.Copy(HashValueHistory, newArray, HashValueHistory.Length); HashValueHistory = newArray; } HashValueHistory[nrHashValuesInHistory] = HashValue; // return moveIsLegal; }
private void StoreKillerAndHistory(Move currentMove, int currentDepth) { if (useKillerMoves && currentMove.captureInfo == Const.NoCaptureID) { // It gives a cut-off. Remember if for move-ordening // Don't store capturing moves (they already get high move-order priority) // And make sure KillerMove2 does not becomes equal to KillerMove1 if (KillerMoves1[plyNr] != currentMove) { KillerMoves2[plyNr] = KillerMoves1[plyNr]; KillerMoves1[plyNr] = currentMove; } else { // KillerMove1 is already set to CurrentMove. Try KillerMove2 if (KillerMoves2[plyNr] != currentMove) KillerMoves2[plyNr] = currentMove; } } if (useMoveOrdering_History) { int color; if (use2HistoryTables) color = board.colorToMove; else color = Const.White; // use the same table for both colors int[,] history = History[color]; history[currentMove.fromPosition, currentMove.toPosition] += 2 << currentDepth; if (history[currentMove.fromPosition, currentMove.toPosition] > maxHistoryValue[color]) { maxHistoryValue[color] = history[currentMove.fromPosition, currentMove.toPosition]; if (maxHistoryValue[color] > 1 << 30) ScaleDownHistory(color, 2); } } }
private void StoreCurrentThinking(int score) { // stores the best found mobes, so far and the score currentScore = score; int nrPVMoves = PrincipalVariation.Length; if (nrPVMoves > maxNrThinkMoves) nrPVMoves = maxNrThinkMoves; bool TTStillMatchesPV = true; // this signals if the PV matches the TT int nrThinkMoves = 0; Move[] thinkMoves = new Move[maxNrThinkMoves]; ulong[] thinkMoveHashValues = new ulong[maxNrThinkMoves]; // First store everything in a clone, since making captures reorders the indices of pieces in PiecePos. // This reorders future moves. Somehow, this gives problems Board clone = new Board(false); clone.LoadFrom(board); // for (int i = 0; i < nrPVMoves; i++) { if (TTStillMatchesPV) { int ttIndex = transpositionTable.GetIndex(board.HashValue, 0); if (ttIndex >= 0) { Move ttMove = new Move(transpositionTable.slots[ttIndex].compressedMove); if (ttMove != PrincipalVariation[i]) TTStillMatchesPV = false; } else TTStillMatchesPV = false; } thinkMoves[i] = PrincipalVariation[i]; thinkMoveHashValues[i] = board.HashValue; board.MakeMove(PrincipalVariation[i]); nrThinkMoves++; } // finished the PrincipalVariation. Now follow the TT nrCurrentMovesFromPV = nrThinkMoves; int ttIndex2; while ((ttIndex2 = transpositionTable.GetIndex(board.HashValue, 0)) != -1) { Move move = new Move(transpositionTable.slots[ttIndex2].compressedMove); if (move.moveType == Const.NoMoveID) break; if (nrThinkMoves >= maxNrThinkMoves - 1) break; thinkMoves[nrThinkMoves] = move; thinkMoveHashValues[nrThinkMoves] = board.HashValue; board.MakeMove(move); nrThinkMoves++; // Check if this move has occured before. Otherwise an endless loop would occurr. // Allow for 3 entries, since this is possible for the 3-move rule (?) int nrSameEntries = 0; for (int i = 0; i < nrThinkMoves; i++) if (thinkMoveHashValues[i] == board.HashValue) nrSameEntries++; if (nrSameEntries >= 3) break; } // now rewind the board by undoing the moves made for (int i = nrThinkMoves - 1; i >= 0; i--) board.UnMakeMove(thinkMoves[i]); // switch back to the original board board.LoadFrom(clone); // // So found the moves, now store them currentMoves = new Move[nrThinkMoves]; for (int i = 0; i < nrThinkMoves; i++) currentMoves[i] = thinkMoves[i]; }
private void ScoreQMoves(int plyNr) { const int transpostionTableScore = 1 << 30; const int pawnPromotionOffset = 1 << 27; const int winningCaptureScoreOffset = 1 << 25; const int equalCaptureScoreOffset = 1 << 24; const int losingCaptureScoreOffset = 1 << 20; // // assign some static score to each move // bool searchForTTMove = false; Move ttMove = Move.NoMove(); if (moveOrdering_SearchTT) { int index = transpositionTable.GetIndex(board.HashValue, 0); if (index >= 0) { int compressedMove = transpositionTable.slots[index].compressedMove; ttMove = new Move(compressedMove); if (ttMove.moveType != Const.NoMoveID) searchForTTMove = true; } } int nrMoves = nrGeneratedMovesInPly[plyNr]; if (scoreMatrix[plyNr] == null || scoreMatrix[plyNr].Length < nrMoves) scoreMatrix[plyNr] = new int[nrMoves + 1]; Move[] moves = moveMatrix[plyNr]; // just a pointer int[] scores = scoreMatrix[plyNr]; // just a pointer for (int i = 0; i < nrMoves; i++) { // first set the score to 0 scores[i] = 0; if (searchForTTMove) { // see if anyting can be found in the transposition table if (moves[i].fromPosition == ttMove.fromPosition && moves[i].toPosition == ttMove.toPosition && moves[i].moveType == ttMove.moveType) { searchForTTMove = false; scores[i] += transpostionTableScore; } } // is this a capture ? if (moves[i].captureInfo != Const.NoCaptureID) { if (moveOrdering_UseSEE) { int seeScore = moves[i].seeScore; if (seeScore > 0) scores[i] += winningCaptureScoreOffset + seeScore; else if (seeScore < 0) scores[i] += losingCaptureScoreOffset + seeScore; // seeScore is negative else scores[i] += equalCaptureScoreOffset; } else { // NB : pawn = 5, queen = 0 int capturedPieceType = moves[i].captureInfo & Const.PieceTypeBitMask; int capturingPieceType = (moves[i].captureInfo >> Const.NrPieceTypeBits) & Const.PieceTypeBitMask; // positive=winning , negative=losing int captureScore = capturingPieceType - capturedPieceType; if (captureScore > 0) scores[i] += winningCaptureScoreOffset + captureScore; else if (captureScore == 0) scores[i] += equalCaptureScoreOffset; else scores[i] += losingCaptureScoreOffset + captureScore; } } // is it a pawn promotion ? Only score the Queen promotion. Let the minor promotions get score = 0; if (moves[i].moveType == Const.PawnPromoteQueenID) scores[i] = pawnPromotionOffset; /* if (moves[i].moveType >= Const.SpecialMoveID) { switch (moves[i].moveType) { case Const.PawnPromoteQueenID: scores[i] += pawnPromotionOffset + 4; break; // don't care about these case Const.PawnPromoteRookID: scores[i] -= pawnPromotionOffset + 3; break; case Const.PawnPromoteBishopID: scores[i] -= pawnPromotionOffset + 2; break; case Const.PawnPromoteKnightID: scores[i] -= pawnPromotionOffset + 1; break; } } */ } }
public void StoreMoveInHistory(Move move) { if (nrMovesInHistory == MoveHistory.Length) { // increase storage space Move[] newArray = new Move[MoveHistory.Length + 100]; Array.Copy(MoveHistory, newArray, MoveHistory.Length); MoveHistory = newArray; } MoveHistory[nrMovesInHistory] = move; nrMovesInHistory++; }
public void MakeMove(Move move) { // this must later go, in favour of MakeMove(string) if (move.moveType != Const.NoMoveID) board.MakeMove(move); else throw new Exception("illegal move"); // store the move in MoveHistory StoreMoveInHistory(move); }
public string MoveToSANString(Move move) { int fromPosition = move.fromPosition; int toPosition = move.toPosition; // first generate all (pseudo-legal) moves : Move[] moves = moveGenerator.GenerateMoves(null); int nrMoves = moveGenerator.nrGeneratedMoves; // Check the list to see if the move is present int moveNr = -1; for (int i=0; i<nrMoves; i++) if (moves[i] == move) { moveNr = i; break; } if (moveNr == -1) return "???"; // maybe it's a special move if (move.moveType >= Const.SpecialMoveID) { // these are never ambiguous switch (move.moveType) { case Const.CastleQSID: return "O-O-O"; case Const.CastleKSID: return "O-O"; case Const.EnPassantCaptureID: return EPD.PositionToFileString(fromPosition) + "x" +EPD.PositionToString(toPosition); case Const.PawnPromoteQueenID: case Const.PawnPromoteRookID: case Const.PawnPromoteBishopID: case Const.PawnPromoteKnightID: string s = EPD.PositionToFileString(fromPosition); if (move.captureInfo == Const.NoCaptureID) s += EPD.PositionToRankString(toPosition); else { s += "x" + EPD.PositionToString(toPosition); } return s + "=" + EPD.PromotionPieceToString(move.moveType); case Const.Pawn2StepID: return EPD.PositionToString(toPosition); default: throw new ArgumentException("Invalid Special MoveType"); } } // maybe it's a pawn move : if (move.moveType == Const.PawnID) { // is never ambiguous if (move.captureInfo == Const.NoCaptureID) return EPD.PositionToString(toPosition); else return EPD.PositionToFileString(fromPosition) + "x" + EPD.PositionToString(toPosition); } // It's a major piece move. string ss = EPD.PieceTypeToString(move.moveType); // check whetter multiple moves of the same type end on the same square int nrAmbiguousMoves = 0; Move ambiguousMove = Move.NoMove(); for (int i = 0; i < nrMoves; i++) { if (i == moveNr) continue; if (move.moveType == moves[i].moveType && toPosition == moves[i].toPosition) { nrAmbiguousMoves++; if (nrAmbiguousMoves > 1) break; // more then 1, always specify both file and rank // only need to store 1 ambiguousMove = moves[i]; } } if (nrAmbiguousMoves > 0) { // add either a FileChar, a RankChar or both if (nrAmbiguousMoves == 1) { // different files ? if (ambiguousMove.fromPosition %8 != move.fromPosition % 8) ss += EPD.PositionToFileString(move.fromPosition); else // no, so differnent ranks ss += EPD.PositionToRankString(move.fromPosition); } else // more then 1 ambiguous move. Use both file and rank ss += EPD.PositionToString(move.fromPosition); } if (move.captureInfo != Const.NoCaptureID) ss += "x"; return ss + EPD.PositionToString(toPosition); }
public string MoveToLANString(Move move) { // e.g. e2e4 e1g1 (KS castle), e7e8q (promotion) string s = EPD.PositionToFileString(move.fromPosition) + EPD.PositionToFileString(move.toPosition); if (move.moveType >= Const.SpecialMoveID) switch (move.moveType) { case Const.PawnPromoteQueenID: case Const.PawnPromoteRookID: case Const.PawnPromoteBishopID: case Const.PawnPromoteKnightID: s += EPD.PromotionPieceToString(move.moveType).ToLower(); break; } return s; }
private void AddQuiescenceMove(int moveType, int fromPos, int toPos) { if (nrGeneratedMoves == maxNrMoves) { // optionally increase the moveList int newMaxNrMoves = maxNrMoves * 2; Move[] moveList2 = new Move[newMaxNrMoves]; for (int i = 0; i < maxNrMoves; i++) moveList2[i] = moveList[i]; maxNrMoves = newMaxNrMoves; moveList = moveList2; } // moveList[nrGeneratedMoves].moveType = moveType; moveList[nrGeneratedMoves].fromPosition = fromPos; moveList[nrGeneratedMoves].toPosition = toPos; moveList[nrGeneratedMoves].seeScore = 0; // filled in later // // store the possible captured piece : if (moveType < Const.SpecialMoveID) { // this can be EmptyID or some PieceType int capturedPieceType = board.SquareContents[toPos].pieceType; // if (capturedPieceType == Const.EmptyID) moveList[nrGeneratedMoves].captureInfo = Const.NoCaptureID; else { // yes, it is a capture. // Store the captured piece type in bits 0..2, and the capturing piece type in bits 3..5 moveList[nrGeneratedMoves].captureInfo = capturedPieceType + (board.SquareContents[fromPos].pieceType << Const.NrPieceTypeBits); // also store the SEE-score moveList[nrGeneratedMoves].seeScore = attack.SEE(moveType, fromPos, toPos); } } else { switch (moveType) { case Const.EnPassantCaptureID: moveList[nrGeneratedMoves].captureInfo = Const.PawnID + (Const.PawnID << Const.NrPieceTypeBits); moveList[nrGeneratedMoves].seeScore = attack.SEE(moveType, fromPos, toPos); break; case Const.PawnPromoteQueenID: case Const.PawnPromoteRookID: case Const.PawnPromoteBishopID: case Const.PawnPromoteKnightID: // this can be EmptyID or some PieceType // this can be EmptyID or some PieceType int capturedPieceType = board.SquareContents[toPos].pieceType; // if (capturedPieceType == Const.EmptyID) moveList[nrGeneratedMoves].captureInfo = Const.NoCaptureID; else { // yes, it is a capture. // Store the captured piece type in bits 0..2, and the capturing piece type in bits 3..5 moveList[nrGeneratedMoves].captureInfo = capturedPieceType + (board.SquareContents[fromPos].pieceType << Const.NrPieceTypeBits); } break; default: moveList[nrGeneratedMoves].captureInfo = Const.NoCaptureID; break; } } // maybe abort here, if captures with negative SEE is excluded if (noNegativeSEECaptures && !(moveType >= Const.SpecialMoveID)) { // moveType>=Const.SpecialMoveID : always do special stuff. // This also means that en-passant captures are never filtered out ! // Also : SEE has problems with promotions or so if (moveList[nrGeneratedMoves].seeScore < 0) return; } nrGeneratedMoves++; }
private int AlphaBeta(int depth, int alpha, int beta, bool canDoNullMove, bool canDoLMR) { // Alpha = the current best score that can be forced by some means. // Beta = the worst-case scenario for the opponent. // = upper bound of what the opponent can achieve // Score >= Beta, the opponent won't let you get into this position : he has a better previous move. // nrNodesSearchedAfterLastTimeCheck++; // Check the time. if (nrNodesSearchedAfterLastTimeCheck >= maxNrNodeSearchesBeforeTimeCheck) { nrNodesSearchedAfterLastTimeCheck = 0; abortSearch = IsSearchTimeFinished(); // check the time if ((DateTime.Now - lastTimeReportWasSent).TotalSeconds > 0.5) { // send a report, once a second if (SearchMoveHasNewResults != null) { currentNrNodes = nodeCount; currentSearchTime = (int)((DateTime.Now - startedThinkingTime).TotalMilliseconds) / 1000.0; currentTTFullPerMill = transpositionTable.GetTTFullPerMill(); SearchMoveHasNewResults(false); } lastTimeReportWasSent = DateTime.Now; } #if !SILVERLIGHT Application.DoEvents(); #endif } // // If we are out of time, quit. Don't care about the score, since this entire depth will be discarded. if (abortSearch) return 0; // nodeCount++; int initialAlpha = alpha; // check for enough storage CheckEnoughNrPliesStorage(plyNr); // nrMovesInPVLine[plyNr] = 0; // if (plyNr > 0 && board.IsPracticallyDrawn()) return 0; // check for 50-move rule ,3x repetition & not enough material // First look in the Transposition Table // But not for PlyNr = 0, otherwise no move is made on finding an exact score ! // Not for depth <=0, since this should go to QSearch below if (useTTable && plyNr > 0 && depth > 0) { int index = transpositionTable.GetIndex(board.HashValue, depth); if (index >= 0) { int hashScore = transpositionTable.slots[index].score; /* // correct the mate-score ?? This doesn't work, since the stored hashScore is incorrect. if (Math.Abs(hashScore) > Evaluator.FutureMate && Math.Abs(hashScore) <= Evaluator.MateValue) { if (hashScore > Evaluator.FutureMate) hashScore -= plyNr; else hashScore += plyNr; } */ int bound = transpositionTable.slots[index].flags; // http://www.cs.ualberta.ca/~jonathan/Courses/657/Notes/4.DAGs.pdf // Also see Beowulf, comp.c switch (bound) { case TranspositionTable.exactBound: // This score is precisely known, so return it. return hashScore; case TranspositionTable.upperBound: // Upperbound : true score is hashScore or less. if (hashScore <= alpha) // This was an upper bound, but still isn't greater than alpha, so return a fail-low return hashScore; // was alpha if (hashScore < beta) // This was an upper bound, but was greater than alpha, so adjust beta if necessary beta = hashScore; break; case TranspositionTable.lowerBound: // Lowerbound (caused by beta cutoff) : true score is hashScore or larger. if (hashScore >= beta) // This was a lower bound, but was still greater than beta, so return a fail-high return hashScore; if (hashScore > alpha) // This was a lower bound, but was not greater than beta, so adjust alpha if necessary alpha = hashScore; break; } } } bool isInCheck = board.IsInCheck(); // maybe extend the remaining depth in certain circumstances bool haveExtended = false; if (UseExtensions) { if (isInCheck) { depth++; haveExtended = true; } } // due to the extensions, we do not enter QSearch while in check. if (depth <= 0) return QSearch(maxQuiescenceDepth, alpha, beta); if (useNullMove) { // If making no move at all would produce a beta cut-off, it is reasonable to assume // that _do_ making a move would _definitely_ produce a cut-off. // (assuming that making a move always improves the position). // Test it (= make no move) quickly, with a reduced depth (-2). // alpha == beta - 1 : only try null moves when not in the PV if (canDoNullMove && alpha == beta - 1 && depth >= 2 && !isInCheck && !board.HasZugZwang()) { Move nullMove = Move.NullMove(); board.MakeMove(nullMove); // This might screw up the PV !! nullMoveCounter++; plyNr++; int nullMoveScore; // adaptive null-move pruning if (depth > 6) nullMoveScore = -AlphaBeta(depth - 1 - 3, -beta, -beta + 1, false, canDoLMR); // 3-depth reduction else nullMoveScore = -AlphaBeta(depth - 1 - 2, -beta, -beta + 1, false, canDoLMR); // 2-depth reduction plyNr--; nullMoveCounter--; board.UnMakeMove(nullMove); if (nullMoveScore >= beta) return nullMoveScore; // was beta } } // extended futility pruning // not sure is this is done in the right way, but it seems to work very well. bool doPruneFutilities = false; if (useFutilityPruning) { if ( (depth==2 || depth == 3) && Math.Abs(alpha) < Evaluator.FutureMate && !isInCheck && !haveExtended) { //int fastEval = evaluator.GetFastEvaluation(); // do full evaluation (?) int fastEval = evaluator.GetEvaluation(alpha, beta); // nb : depth=0 is unused, since it already went to QSearch // nb : depth=1 makes just 1 move and then goes to QSearch. Pruning this does not work very well. int[] margin = { 0, 0, 125, 300 }; if (fastEval + margin[depth] < alpha) doPruneFutilities = true; } } // generate moves moveMatrix[plyNr] = moveGenerator.GenerateMoves(moveMatrix[plyNr]); nrGeneratedMovesInPly[plyNr] = moveGenerator.nrGeneratedMoves; // statically order the moves, so the (hopefully) best are tried first : Good for AlphaBeta ScoreMoves(plyNr); // singular move extension // if (nrGeneratedMovesInPly[plyNr] == 1) // depth++; // loop over all generated moves bool legalMoveIsMade = false; Move bestMove = Move.NoMove(); int bestScore = -10 * Evaluator.MateValue; for (int i = 0; i < nrGeneratedMovesInPly[plyNr]; i++) { // If we are out of time, quit. Don't care about the score, since this entire depth is not used. if (abortSearch) return 0; Move currentMove = moveMatrix[plyNr][FindBestMoveNr(plyNr)]; // check if this move is legal if (!board.MakeMove(currentMove)) { // the move is illegal, e.g. by illegal castle or leaving king in check. board.UnMakeMove(currentMove); continue; } bool moveGivesCheck = board.IsInCheck(); if (doPruneFutilities) { // don't futility prune : captures, 'special'-moves && putting king in check, no legal move was made if (legalMoveIsMade && !moveGivesCheck && currentMove.seeScore <= 0 && currentMove.moveType < Const.EnPassantCaptureID) { board.UnMakeMove(currentMove); continue; } } legalMoveIsMade = true; // if (plyNr == 0) currentRootMoveNr = i; // keep track of which root-move we are trying // int score; plyNr++; if (UsePVSearch) { if (i == 0) // assume the first move is the best (&legal). Search it with the normal window. score = -AlphaBeta(depth - 1, -beta, -alpha, true, canDoLMR); else { // The next moves are considered to be worse. // Check this with a 'very' narrow window // try reduction on the 'not' so important moves // But : not for captures, pawn-moves, special moves, checks, root-moves if (useLateMoveReduction && canDoLMR && i >= 4 && depth >= 3 // depth 3 & 5 gave identical results after 43 games && !haveExtended && currentMove.captureInfo == Const.NoCaptureID && currentMove.moveType < Const.PawnID // dont reduces pawn- & special-moves // && currentMove.moveType < Const.CastleQSID // dont reduces pawn- & special-moves && plyNr > 1 // 1=root : don't reduce the root && !isInCheck && !moveGivesCheck) { // a reduced PV search bool canDoMoreRecuctions = useOnly1LateMoveReduction ? false : true; score = -AlphaBeta(depth - 2, -alpha - 1, -alpha, true, canDoMoreRecuctions); // If it was not worse but better, research it with a normal & unreduced window. if (score > alpha) score = -AlphaBeta(depth - 1, -beta, -alpha, true, canDoLMR); } else { // a normal PV search score = -AlphaBeta(depth - 1, -alpha - 1, -alpha, true, canDoLMR); // If it was not worse but better, research it with a normal window. // If it's >= beta, don't worry, it will be cut-off. if (score > alpha && score < beta) score = -AlphaBeta(depth - 1, -beta, -score, true, canDoLMR); } } } else score = -AlphaBeta(depth - 1, -beta, -alpha, true, canDoLMR); plyNr--; board.UnMakeMove(currentMove); // If we are out of time, quit. Don't care about the score, since this entire depth will be discarded. if (abortSearch) return 0; if (score > bestScore) { // The score is better then was attained before. Store it and compare it to alpha and beta. bestScore = score; bestMove = currentMove; if (bestScore > alpha) { if (bestScore >= beta) { // To good to be true. The opponent will never let us get in this position. // Store the move which caused the cut-off, and quit. StoreKillerAndHistory(currentMove, depth); if (bestScore != 0) // don't store draw-values ?? transpositionTable.Put(board.HashValue, bestScore, TranspositionTable.lowerBound , depth, board.halfMoveNr, bestMove.Compress()); return bestScore; } alpha = bestScore; // update the PV_Matrix; if (!dontStoreNullMoveInPV || nullMoveCounter == 0) { int nrPVMovesToCopy = nrMovesInPVLine[plyNr + 1]; if (PV_Matrix[plyNr].Length < nrPVMovesToCopy + 1) PV_Matrix[plyNr] = new Move[nrPVMovesToCopy + 10]; // store the current move, since it was better PV_Matrix[plyNr][0] = currentMove; // Append the current moves of the searched tree, since the current move is better. Array.Copy(PV_Matrix[plyNr + 1], 0, PV_Matrix[plyNr], 1, nrPVMovesToCopy); nrMovesInPVLine[plyNr] = nrPVMovesToCopy + 1; } } } } if (legalMoveIsMade) { // update transposition table. if (useTTable) { /* // correct the mate-score ?? if (Math.Abs(ttScore) > Evaluator.FutureMate && Math.Abs(ttScore) <= Evaluator.MateValue) { if (ttScore > Evaluator.FutureMate) ttScore += plyNr; else ttScore -= plyNr; } */ if (bestScore != 0) // don't store draw-values ?? { if (bestScore > initialAlpha) transpositionTable.Put(board.HashValue, bestScore, TranspositionTable.exactBound , depth, board.halfMoveNr, bestMove.Compress()); else if (StoreUpperBoundsInTT) transpositionTable.Put(board.HashValue, bestScore, TranspositionTable.upperBound , depth, board.halfMoveNr, bestMove.Compress()); } } return bestScore; // the current best possible score. } else { // no legal move could be made : either CheckMate or StaleMate if (board.IsInCheck()) return -Evaluator.MateValue + plyNr ; // CheckMate. +PlyNr : promote fastest checkmates // return -Evaluator.MateValue + plyNr - 1; // CheckMate. +PlyNr : promote fastest checkmates else return 0; // StaleMate : this must be done better !! } }
public void UnMakeMove(Move move) { int prevColorToMove = colorToMove; // This is the color which made the move : ToggleMoveColor(); // decrement the HalfMoveNr halfMoveNr--; // if (move.moveType < Const.SpecialMoveID) { // un-make the move MovePieceOnBoard(move.toPosition, move.fromPosition); } else { // Unmake specials // it is a Castle, enpassant capture, 2-stap pawn move or pawn promotion int castleRankOffset = colorToMove * 56; switch (move.moveType) { case Const.CastleQSID: MovePieceOnBoard(castleRankOffset + 2, castleRankOffset + 4); // King MovePieceOnBoard(castleRankOffset + 3, castleRankOffset + 0); // Rook break; case Const.CastleKSID: MovePieceOnBoard(castleRankOffset + 6, castleRankOffset + 4); // King MovePieceOnBoard(castleRankOffset + 5, castleRankOffset + 7); // Rook break; case Const.EnPassantCaptureID: MovePieceOnBoard(move.toPosition, move.fromPosition); break; case Const.PawnPromoteQueenID: case Const.PawnPromoteRookID: case Const.PawnPromoteBishopID: case Const.PawnPromoteKnightID: RemovePieceFromBoard(move.toPosition); // the promoted piece AddNewPieceToBoard(colorToMove, Const.PawnID, move.fromPosition); break; case Const.Pawn2StepID: MovePieceOnBoard(move.toPosition, move.fromPosition); int oldEPPos = (move.toPosition + move.fromPosition)/2; break; case Const.NullMoveID: break; default: throw new Exception("Invalid special move nr"); } } // was it a capture ? Yes : restore the captured piece if (capturedPieceType != Const.InvalidID) AddNewPieceToBoard(prevColorToMove, capturedPieceType, capturedPiecePosition); // Last restore the BoardState as it was just after the move. This also restores the HashValue. RestoreBoardState(); #if HashDebug if (HashValue != CalcHashValue()) throw new ApplicationException("hash differs"); #endif nrHashValuesInHistory--; }