/// <summary> /// Executes the parsing. /// </summary> public static void Run() { Restrictions.Output = OutputType.UCI; Engine engine = new Engine(); Position position = Position.Create(Position.StartingFEN); String command; while ((command = Console.ReadLine()) != null) { List <String> terms = new List <String>(command.Split(' ')); switch (terms[0]) { default: Terminal.WriteLine("Unknown command: {0}", terms[0]); Terminal.WriteLine("Enter \"help\" for assistance."); break; case "uci": Terminal.WriteLine("id name " + engine.Name); Terminal.WriteLine("id author Zong Zheng Li"); Terminal.WriteLine("option name Hash type spin default " + Engine.DefaultHashAllocation + " min 1 max 2047"); Terminal.WriteLine("uciok"); break; case "ucinewgame": engine.Reset(); break; case "setoption": if (terms.Contains("Hash")) { engine.HashAllocation = Int32.Parse(terms[terms.IndexOf("value") + 1]); } break; case "position": String fen = Position.StartingFEN; if (terms[1] != "startpos") { fen = command.Substring(command.IndexOf("fen") + 4); } position = Position.Create(fen); Int32 movesIndex = terms.IndexOf("moves"); if (movesIndex >= 0) { for (Int32 i = movesIndex + 1; i < terms.Count; i++) { position.Make(Move.Create(position, terms[i])); } } break; case "go": Restrictions.Reset(); for (Int32 i = 1; i < terms.Count; i++) { switch (terms[i]) { default: case "infinite": break; case "depth": Restrictions.Depth = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = false; break; case "movetime": Restrictions.MoveTime = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = false; break; case "wtime": Restrictions.TimeControl[Colour.White] = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = true; break; case "btime": Restrictions.TimeControl[Colour.Black] = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = true; break; case "winc": Restrictions.TimeIncrement[Colour.White] = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = true; break; case "binc": Restrictions.TimeIncrement[Colour.Black] = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = true; break; case "nodes": Restrictions.Nodes = Int32.Parse(terms[i + 1]); Restrictions.UseTimeControls = false; break; case "ponder": // TODO: implement command. break; case "mate": // TODO: implement command. break; case "movestogo": // TODO: implement command. break; } } new Thread(new ThreadStart(() => { Int32 bestMove = engine.GetMove(position); Terminal.WriteLine("bestmove " + Stringify.Move(bestMove)); })) { IsBackground = true }.Start(); break; case "stop": engine.Stop(); break; case "isready": Terminal.WriteLine("readyok"); break; case "quit": return; case "perft": Perft.Iterate(position, Int32.Parse(terms[1])); break; case "divide": Perft.Divide(position, Int32.Parse(terms[1])); break; case "draw": Terminal.WriteLine(position); break; case "fen": Terminal.WriteLine(position.GetFEN()); break; case "ponderhit": // TODO: implement command. break; case "register": // TODO: implement command. break; case "help": Terminal.WriteLine("Command Function"); Terminal.WriteLine("-----------------------------------------------------------------------"); Terminal.WriteLine("position [fen] Sets the current position to the position denoted"); Terminal.WriteLine(" by the given FEN. \"startpos\" is accepted for the"); Terminal.WriteLine(" starting position"); Terminal.WriteLine("go [type] [number] Searches the current position. Search types include"); Terminal.WriteLine(" \"movetime\", \"depth\", \"nodes\", \"wtime\", \"btime\","); Terminal.WriteLine(" \"winc\", and \"binc\""); Terminal.WriteLine("perft [number] Runs perft() on the current position to the given"); Terminal.WriteLine(" depth"); Terminal.WriteLine("divide [number] Runs divide() on the current position for the given"); Terminal.WriteLine(" depth"); Terminal.WriteLine("fen Prints the FEN of the current position."); Terminal.WriteLine("draw Draws the current position"); Terminal.WriteLine("stop Stops an ongoing search"); Terminal.WriteLine("quit Exits the application"); Terminal.WriteLine("-----------------------------------------------------------------------"); break; } } }
/// <summary> /// Returns the dynamic value of the position as determined by a recursive /// search that terminates upon reaching a quiescent position. /// </summary> /// <param name="position">The position to search on.</param> /// <param name="ply">The number of plies from the root position.</param> /// <param name="alpha">The lower bound on the value of the best move.</param> /// <param name="beta">The upper bound on the value of the best move.</param> /// <returns>The value of the termination position given optimal play.</returns> private Int32 Quiescence(Position position, Int32 ply, Int32 alpha, Int32 beta) { _totalNodes++; _quiescenceNodes++; // Evaluate the position statically. Check for upper bound cutoff and lower // bound improvement. Int32 value = Evaluate(position); if (value >= beta) { return(value); } if (value > alpha) { alpha = value; } // Initialize variables and generate the pseudo-legal moves to be // considered. Perform basic move ordering and sort the moves. Int32 colour = position.SideToMove; Int32[] moves = _generatedMoves[ply]; Int32 movesCount = position.PseudoQuiescenceMoves(moves); for (Int32 i = 0; i < movesCount; i++) { _moveValues[i] = MoveOrderingValue(moves[i]); } Sort(moves, _moveValues, movesCount); // Go through the move list. for (Int32 i = 0; i < movesCount; i++) { _movesSearched++; Int32 move = moves[i]; // Consider the move only if it doesn't immediately lose material. This // improves efficiency. if ((Move.Piece(move) & Piece.Mask) <= (Move.Capture(move) & Piece.Mask) || EvaluateStaticExchange(position, move) >= 0) { // Make the move. position.Make(move); // Search the move if it is legal. This is equivalent to not leaving the // king in check. if (!position.InCheck(colour)) { value = -Quiescence(position, ply + 1, -beta, -alpha); // Check for upper bound cutoff and lower bound improvement. if (value >= beta) { position.Unmake(move); return(value); } if (value > alpha) { alpha = value; } } // Unmake the move. position.Unmake(move); } } return(alpha); }
/// <summary> /// Returns whether the given move is a dangerous pawn advance. A dangerous /// pawn advance is a pawn move that results in the pawn being in a position /// in which no enemy pawns can threaten or block it. /// </summary> /// <param name="move">The move to consider.</param> /// <param name="passedPawnPreventionBitboard">A bitboard giving the long term attack possibilities of the enemy pawns.</param> /// <returns>Whether the given move is a dangerous pawn advance.</returns> private Boolean IsDangerousPawnAdvance(Int32 move, UInt64 passedPawnPreventionBitboard) { return(Move.IsPawnAdvance(move) && ((1UL << Move.To(move)) & passedPawnPreventionBitboard) == 0); }
/// <summary> /// Returns the dynamic value of the position as determined by a recursive /// search to the given depth. This implements the main search algorithm. /// </summary> /// <param name="position">The position to search on.</param> /// <param name="depth">The depth to search to.</param> /// <param name="ply">The number of plies from the root position.</param> /// <param name="alpha">The lower bound on the value of the best move.</param> /// <param name="beta">The upper bound on the value of the best move.</param> /// <param name="inCheck">Whether the side to play is in check.</param> /// <param name="allowNull">Whether a null move is permitted.</param> /// <returns>The value of the termination position given optimal play.</returns> private Int32 Search(Position position, Int32 depth, Int32 ply, Int32 alpha, Int32 beta, Boolean inCheck, Boolean allowNull = true) { // Check whether to enter quiescence search and initialize pv length. _pvLength[ply] = 0; if (depth <= 0 && !inCheck) { return(Quiescence(position, ply, alpha, beta)); } // Check for time extension and search termination. This is done once for // every given number of nodes for efficency. if (++_totalNodes > _referenceNodes) { _referenceNodes += NodeResolution; // Apply loss time extension. The value of the best move for the current // root position is compared with the value of the previous root position. // If there is a large loss, a time extension is given. Int32 loss = _finalAlpha - _rootAlpha; if (loss >= TimeControlsLossResolution) { Int32 index = Math.Min(loss / TimeControlsLossResolution, TimeControlsLossExtension.Length - 1); TryTimeExtension(TimeControlsLossThreshold, TimeControlsLossExtension[index]); } if (_stopwatch.ElapsedMilliseconds >= _timeLimit + _timeExtension || _totalNodes >= Restrictions.Nodes) { _abortSearch = true; } } if (_abortSearch) { return(Infinity); } // Perform draw detection. Int32 drawValue = ((ply & 1) == 0) ? DrawValue : -DrawValue; Int32 drawRepetitions = (ply > 2) ? 2 : 3; if (position.FiftyMovesClock >= 100 || position.InsufficientMaterial() || position.HasRepeated(drawRepetitions)) { return(drawValue); } // Perform mate distance pruning. Int32 mateAlpha = Math.Max(alpha, -(CheckmateValue - ply)); Int32 mateBeta = Math.Min(beta, CheckmateValue - (ply + 1)); if (mateAlpha >= mateBeta) { return(mateAlpha); } // Perform hash probe. _hashProbes++; Int32 hashMove = Move.Invalid; HashEntry hashEntry; if (_table.TryProbe(position.ZobristKey, out hashEntry)) { hashMove = hashEntry.Move; if (hashEntry.Depth >= depth) { Int32 hashType = hashEntry.Type; Int32 hashValue = hashEntry.GetValue(ply); if ((hashType == HashEntry.Beta && hashValue >= beta) || (hashType == HashEntry.Alpha && hashValue <= alpha)) { _hashCutoffs++; return(hashValue); } } } Int32 colour = position.SideToMove; // Apply null move heuristic. if (allowNull && !inCheck && position.Bitboard[colour] != (position.Bitboard[colour | Piece.King] | position.Bitboard[colour | Piece.Pawn])) { position.MakeNull(); Int32 reduction = NullMoveReduction + (depth >= NullMoveAggressiveDepth ? depth / NullMoveAggressiveDivisor : 0); Int32 value = -Search(position, depth - 1 - reduction, ply + 1, -beta, -beta + 1, false, false); position.UnmakeNull(); if (value >= beta) { return(value); } } // Generate legal moves and perform basic move ordering. Int32[] moves = _generatedMoves[ply]; Int32 movesCount = position.LegalMoves(moves); if (movesCount == 0) { return(inCheck ? -(CheckmateValue - ply) : drawValue); } for (Int32 i = 0; i < movesCount; i++) { _moveValues[i] = MoveOrderingValue(moves[i]); } // Apply single reply and check extensions. if (movesCount == 1 || inCheck) { depth++; } // Perform killer move ordering. _killerMoveChecks++; bool killerMoveFound = false; for (Int32 slot = 0; slot < KillerMovesAllocation; slot++) { Int32 killerMove = _killerMoves[ply][slot]; for (Int32 i = 0; i < movesCount; i++) { if (moves[i] == killerMove) { _moveValues[i] = KillerMoveValue + slot * KillerMoveSlotValue; if (!killerMoveFound) { _killerMoveMatches++; } killerMoveFound = true; break; } } } // Perform hash move ordering. _hashMoveChecks++; if (hashMove != Move.Invalid) { for (Int32 i = 0; i < movesCount; i++) { if (moves[i] == hashMove) { _moveValues[i] = HashMoveValue; _hashMoveMatches++; break; } } } // Check for futility pruning activation. Boolean futileNode = false; Int32 futilityValue = 0; if (depth < FutilityMargin.Length && !inCheck) { futilityValue = Evaluate(position) + FutilityMargin[depth]; futileNode = futilityValue <= alpha; } // Sort the moves based on their ordering values and initialize variables. Sort(moves, _moveValues, movesCount); Int32 irreducibleMoves = 1; while (irreducibleMoves < movesCount && _moveValues[irreducibleMoves] > 0) { irreducibleMoves++; } UInt64 preventionBitboard = PassedPawnPreventionBitboard(position); Int32 bestType = HashEntry.Alpha; Int32 bestMove = moves[0]; // Go through the move list. for (Int32 i = 0; i < movesCount; i++) { _movesSearched++; Int32 move = moves[i]; Boolean causesCheck = position.CausesCheck(move); Boolean dangerous = inCheck || causesCheck || alpha < -NearCheckmateValue || IsDangerousPawnAdvance(move, preventionBitboard); Boolean reducible = i + 1 > irreducibleMoves; // Perform futility pruning. if (futileNode && !dangerous && futilityValue + PieceValue[Move.Capture(move)] <= alpha) { _futileMoves++; continue; } // Make the move and initialize its value. position.Make(move); Int32 value = alpha + 1; // Perform late move reductions. if (reducible && !dangerous) { value = -Search(position, depth - 1 - LateMoveReduction, ply + 1, -alpha - 1, -alpha, causesCheck); } // Perform principal variation search. else if (i > 0) { value = -Search(position, depth - 1, ply + 1, -alpha - 1, -alpha, causesCheck); } // Perform a full search. if (value > alpha) { value = -Search(position, depth - 1, ply + 1, -beta, -alpha, causesCheck); } // Unmake the move and check for search termination. position.Unmake(move); if (_abortSearch) { return(Infinity); } // Check for upper bound cutoff. if (value >= beta) { _table.Store(new HashEntry(position, depth, ply, move, value, HashEntry.Beta)); if (reducible) { for (Int32 j = _killerMoves[ply].Length - 2; j >= 0; j--) { _killerMoves[ply][j + 1] = _killerMoves[ply][j]; } _killerMoves[ply][0] = move; } return(value); } // Check for lower bound improvement. if (value > alpha) { alpha = value; bestMove = move; bestType = HashEntry.Exact; // Collect the principal variation. _pvMoves[ply][0] = move; for (Int32 j = 0; j < _pvLength[ply + 1]; j++) { _pvMoves[ply][j + 1] = _pvMoves[ply + 1][j]; } _pvLength[ply] = _pvLength[ply + 1] + 1; } } // Store the results in the hash table and return the lower bound of the // value of the position. _table.Store(new HashEntry(position, depth, ply, bestMove, alpha, bestType)); return(alpha); }