/// <summary> /// Returns a string that describes the given principal variation. /// </summary> /// <param name="position">The position the principal variation is to be played on.</param> /// <param name="depth">The depth of the search that yielded the principal variation.</param> /// <param name="value">The value of the search that yielded the principal variation.</param> /// <returns>A string that describes the given principal variation.</returns> private String CreatePVString(Position position, Int32 depth, Int32 value) { List <Int32> pv = GetCurrentPV(); Boolean isMate = Math.Abs(value) > NearCheckmateValue; Int32 movesToMate = (CheckmateValue - Math.Abs(value) + 1) / 2; switch (Restrictions.Output) { // Return standard output. case OutputType.GUI: String depthString = depth.ToString(); String valueString = isMate ? (value > 0 ? "+Mate " : "-Mate ") + movesToMate : (value / 100.0).ToString("+0.00;-0.00"); String movesString = Stringify.MovesAlgebraically(position, pv); return(String.Format(PVFormat, depthString, valueString, movesString)); // Return UCI output. case OutputType.UCI: String score = isMate ? "mate " + (value < 0 ? "-" : "") + movesToMate : "cp " + value; Double elapsed = _stopwatch.Elapsed.TotalMilliseconds; Int64 nps = (Int64)(1000 * _totalNodes / elapsed); return(String.Format("info depth {0} score {1} time {2} nodes {3} nps {4} pv {5}", depth, score, (Int32)elapsed, _totalNodes, nps, Stringify.Moves(pv))); } return(null); }
/// <summary> /// Returns a move to be played on the given position that has the given /// representation in coordinate notation. /// </summary> /// <param name="position">The position the move is to be played on.</param> /// <param name="name">The representation of the move in coordinate notation.</param> /// <returns>A move that has the given representation in coordinate notation.</returns> public static Int32 Create(Position position, String name) { foreach (Int32 move in position.LegalMoves()) { if (name == Stringify.Move(move)) { return(move); } } return(Invalid); }
/// <summary> /// Returns a text drawing of the position with the given comments displayed. /// </summary> /// <param name="comments">The comments to display.</param> /// <returns>A text drawing of the position with the given comments displayed</returns> public String ToString(params String[] comments) { StringBuilder sb = new StringBuilder(" +------------------------+ ", 400); Int32 index = 0; if (index < comments.Length) { sb.Append(comments[index++]); } for (Int32 rank = 0; rank < 8; rank++) { sb.Append(Environment.NewLine); sb.Append(' '); sb.Append(8 - rank); sb.Append(" |"); for (Int32 file = 0; file < 8; file++) { Int32 piece = Square[file + rank * 8]; if (piece != Piece.Empty) { sb.Append((piece & Colour.Mask) == Colour.White ? '<' : '['); sb.Append(Stringify.PieceInitial(piece)); sb.Append((piece & Colour.Mask) == Colour.White ? '>' : ']'); } else { sb.Append((file + rank) % 2 == 1 ? ":::" : " "); } } sb.Append("| "); if (index < comments.Length) { sb.Append(comments[index++]); } } sb.Append(Environment.NewLine); sb.Append(" +------------------------+ "); if (index < comments.Length) { sb.Append(comments[index++]); } sb.Append(Environment.NewLine); sb.Append(" a b c d e f g h "); if (index < comments.Length) { sb.Append(comments[index++]); } return(sb.ToString()); }
/// <summary> /// Returns the PGN string of the game. /// </summary> /// <returns>The PGN string of the game.</returns> public String GetPGN() { StringBuilder sb = new StringBuilder(); sb.Append("[Date \"" + _date + "\"]"); sb.Append(Environment.NewLine); sb.Append("[White \"" + White.Name + "\"]"); sb.Append(Environment.NewLine); sb.Append("[Black \"" + Black.Name + "\"]"); sb.Append(Environment.NewLine); String result = "*"; switch (_state) { case GameState.WhiteWon: result = "1-0"; break; case GameState.BlackWon: result = "0-1"; break; case GameState.Draw: result = "1/2-1/2"; break; } sb.Append("[Result \"" + result + "\"]"); sb.Append(Environment.NewLine); String initialFEN = _initialPosition.GetFEN(); if (initialFEN != Position.StartingFEN) { sb.Append("[SetUp \"1\"]"); sb.Append(Environment.NewLine); sb.Append("[FEN \"" + initialFEN + "\"]"); sb.Append(Environment.NewLine); } sb.Append(Environment.NewLine); sb.Append(Stringify.MovesAlgebraically(_initialPosition, _moves, StringifyOptions.Proper)); if (result != "*") { sb.Append(" " + result); } return(sb.ToString()); }
/// <summary> /// Returns the text representation of the given move in coordinate notation. /// </summary> /// <param name="move">The move to identify.</param> /// <returns>The text representation of the given move in coordinate notation.</returns> public static String Move(Int32 move) { String coordinates = Stringify.Square(MoveClass.From(move)) + Stringify.Square(MoveClass.To(move)); Int32 special = MoveClass.Special(move); switch (special & PieceClass.Mask) { default: return(coordinates); case PieceClass.Queen: case PieceClass.Rook: case PieceClass.Bishop: case PieceClass.Knight: return(coordinates + PieceInitial(special).ToLowerInvariant()); } }
/// <summary> /// Performs divide on the given position with the given depth. This /// essentially performs perft on each of the positions arising from the /// legal moves for the given position. This method writes the results to /// the terminal. /// </summary> /// <param name="position">The position to perform divide on.</param> /// <param name="depth">The depth to perform divide with.</param> public static void Divide(Position position, Int32 depth) { const Int32 MoveWidth = 8; String format = "{0,-" + MoveWidth + "}{1}"; Terminal.WriteLine(format, "Move", "Nodes"); Terminal.WriteLine("-----------------------------------------------------------------------"); List <Int32> moves = position.LegalMoves(); Int64 totalNodes = 0; foreach (Int32 move in moves) { position.Make(move); Int64 nodes = Nodes(position, depth - 1); position.Unmake(move); totalNodes += nodes; Terminal.WriteLine(format, Stringify.Move(move), nodes); } Terminal.WriteLine("-----------------------------------------------------------------------"); Terminal.WriteLine("Moves: " + moves.Count); Terminal.WriteLine("Nodes: " + totalNodes); }
/// <summary> /// Begins the test with the given positions. /// </summary> /// <param name="epd">A list of positions in EPD format.</param> public static void Run(List <String> epd) { // Perform testing on a background thread. new Thread(new ThreadStart(() => { IEngine engine = new Zero(); Restrictions.Output = OutputType.None; Int32 totalPositions = 0; Int32 totalSolved = 0; Int64 totalNodes = 0; Double totalTime = 0; Terminal.WriteLine(ResultFormat, "Position", "Result", "Time", "Nodes"); Terminal.WriteLine("-----------------------------------------------------------------------"); foreach (String line in epd) { List <String> terms = new List <String>(line.Replace(";", " ;").Split(' ')); // Strip everything to get the FEN. Int32 bmIndex = line.IndexOf("bm "); bmIndex = bmIndex < 0 ? Int32.MaxValue : bmIndex; Int32 amIndex = line.IndexOf("am "); amIndex = amIndex < 0 ? Int32.MaxValue : amIndex; String fen = line.Remove(Math.Min(bmIndex, amIndex)); // Get the best moves. List <String> solutions = new List <String>(); for (Int32 i = terms.IndexOf("bm") + 1; i >= 0 && i < terms.Count && terms[i] != ";"; i++) { solutions.Add(terms[i]); } // Get the ID of the position. Int32 idIndex = line.IndexOf("id ") + 3; String id = line.Substring(idIndex, line.IndexOf(';', idIndex) - idIndex).Replace(@"\", ""); if (id.Length > IDWidthLimit) { id = id.Remove(IDWidthLimit) + ".."; } // Set the position and invoke a search on it. Position position = new Position(fen); VisualPosition.Set(position); engine.Reset(); Stopwatch stopwatch = Stopwatch.StartNew(); Int32 move = engine.GetMove(position); stopwatch.Stop(); Double elapsed = stopwatch.Elapsed.TotalMilliseconds; totalPositions++; totalTime += elapsed; totalNodes += engine.Nodes; // Determine whether the engine found a solution. String result = "fail"; if (solutions.Contains(Stringify.MoveAlgebraically(position, move))) { result = "pass"; totalSolved++; } // Print the result for the search on the position. Terminal.WriteLine(ResultFormat, id, result, String.Format("{0:0} ms", elapsed), engine.Nodes); } // Print final results after all positions have been searched. Terminal.WriteLine("-----------------------------------------------------------------------"); Terminal.WriteLine("Result {0} / {1}", totalSolved, totalPositions); Terminal.WriteLine("Time {0:0} ms", totalTime); Terminal.WriteLine("Average nodes {0:0}", (Double)totalNodes / totalPositions); })) { IsBackground = true }.Start(); // Open the GUI window to draw positions. Application.Run(new Window()); }
/// <summary> /// Returns the FEN string that describes the position. /// </summary> /// <returns>The FEN string that describes the position.</returns> public String GetFEN() { StringBuilder sb = new StringBuilder(); for (Int32 rank = 0; rank < 8; rank++) { Int32 spaces = 0; for (Int32 file = 0; file < 8; file++) { Int32 square = file + rank * 8; if (Square[square] == Piece.Empty) { spaces++; } else { if (spaces > 0) { sb.Append(spaces); spaces = 0; } String piece = Stringify.PieceInitial(Square[square]); if ((Square[square] & Colour.Mask) == Colour.Black) { piece = piece.ToLowerInvariant(); } sb.Append(piece); } } if (spaces > 0) { sb.Append(spaces); } if (rank < 7) { sb.Append('/'); } } sb.Append(' '); sb.Append(SideToMove == Colour.White ? 'w' : 'b'); sb.Append(' '); if (CastleKingside[Colour.White] > 0) { sb.Append('K'); } if (CastleQueenside[Colour.White] > 0) { sb.Append('Q'); } if (CastleKingside[Colour.Black] > 0) { sb.Append('k'); } if (CastleQueenside[Colour.Black] > 0) { sb.Append('q'); } if (sb[sb.Length - 1] == ' ') { sb.Append('-'); } sb.Append(' '); if (EnPassantSquare != InvalidSquare) { sb.Append(Stringify.Square(EnPassantSquare)); } else { sb.Append('-'); } sb.Append(' '); sb.Append(FiftyMovesClock); sb.Append(' '); sb.Append(HalfMoves / 2 + 1); return(sb.ToString()); }
/// <summary> /// Starts play between the two players on the current position for the game. /// This method is non-blocking and does not modify the given position. /// </summary> /// <param name="p">The position to start playing from.</param> private void Play(Position p) { Position position = p.DeepClone(); VisualPosition.Set(position); _state = GameState.Ingame; _waitForStop.Reset(); new Thread(new ThreadStart(() => { while (true) { IPlayer player = (position.SideToMove == Colour.White) ? White : Black; List <Int32> legalMoves = position.LegalMoves(); // Adjudicate checkmate and stalemate. if (legalMoves.Count == 0) { if (position.InCheck(position.SideToMove)) { _message = "Checkmate. " + Stringify.Colour(1 - position.SideToMove) + " wins!"; _state = player.Equals(White) ? GameState.BlackWon : GameState.WhiteWon; } else { _message = "Stalemate. It's a draw!"; _state = GameState.Draw; } } // Adjudicate draw. if (position.InsufficientMaterial()) { _message = "Draw by insufficient material!"; _state = GameState.Draw; } if (player is IEngine && player.AcceptsDraw) { if (position.FiftyMovesClock >= 100) { _message = "Draw by fifty-move rule!"; _state = GameState.Draw; } if (position.HasRepeated(3)) { _message = "Draw by threefold repetition!"; _state = GameState.Draw; } } // Consider game end. if (_state != GameState.Ingame) { _waitForStop.Set(); return; } // Get move from player. Position copy = position.DeepClone(); Int32 move = player.GetMove(copy); if (!position.Equals(copy)) { Terminal.WriteLine("Board modified!"); } // Consider game stop. if (_state != GameState.Ingame) { _waitForStop.Set(); return; } // Make the move. position.Make(move); VisualPosition.Make(move); _moves.Add(move); _types.Add(player.GetType()); } })) { IsBackground = true }.Start(); }
/// <summary> /// Executes the parsing. /// </summary> public static void Run() { Restrictions.Output = OutputType.Universal; IEngine engine = new Zero(); Position position = new Position(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 " + Zero.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 = new Position(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; } } }