/// <summary> /// Tells the opening book what move did the oponent played , so the opening book could update itself. /// </summary> /// <param name="move">The move the opponent played</param> static internal void MoveApplied(Move move) { //if not in book, break away. if (!IsInBook) { return; } //look for such a move OpeningMove applied = CurrentMove.Responses.FirstOrDefault(x => x.Move.EqualMove(move)); //if not found, we are leaving the book, otherwise we apply move and check if we will then be leaving the book. if (applied == null) { IsInBook = false; } else { if (applied.Responses.Count == 0) { IsInBook = false; } CurrentMove = applied; } }
/// <summary> /// Resets to the starting position /// </summary> static internal void ResetBook() { if (m_Root != null) { IsInBook = true; } //root is always the starting position CurrentMove = m_Root; }
/// <summary> /// Provides a random move out of the valid opening moves availible at the current node. /// First try excellent moves, then good, and so forth. /// </summary> /// <returns>The returned move, must be played.</returns> internal static Move GetRandomMove() { //we always make sure we are still inside the book if (IsInBook == false) { return(null); } Random rand = new Random(); //look for excellent moves var GreatResponses = CurrentMove.Responses.Where(x => x.MoveType == OpeningMoveType.Excellent); //if no such exists, look for good move. if (GreatResponses.Count() == 0) { GreatResponses = CurrentMove.Responses.Where(x => x.MoveType == OpeningMoveType.Good); } //if no such exists, look for any move (I don't load bad moves at all) if (GreatResponses.Count() == 0) { GreatResponses = CurrentMove.Responses; } //pick one randomly , perheps as a more balanced move, the random distribution should corraspond to the probability of node. int movePosition = rand.Next(0, GreatResponses.Sum(x => x.Count) - 1); //now we pick one according to the probability int counter = 0; foreach (OpeningMove openingMove in GreatResponses) { counter += openingMove.Count; if (movePosition < counter) { CurrentMove = openingMove; break; } } //if the found move would draw us out of the book, make sure the user won't ask for another move in the future. if (CurrentMove.Responses.Count == 0) { IsInBook = false; } return(CurrentMove.Move); }
static OpeningBook() { try { //CreateOpeningBooks(); Generic <OpeningMove> .LoadFromDisk(out m_Root, "ChessEngineBook.xml"); CurrentMove = m_Root; IsInBook = false; } catch (Exception ex) { IsInBook = false; Global.WriteToLog(ex.Message + ex.StackTrace); } }
/// <summary> /// Parses PGN files and saves them to the 'ChessEngineBook.xml' file. /// NOTE: we should also zip the files to conserve space. /// </summary> private static void CreateOpeningBooks() { List <string> m_OpeningBookLocations = new List <string> { //"RJF60.pgn", "strong.PGN", "CMX.PGN", }; OpeningMove startingPoint = new OpeningMove(); foreach (string openingBookLocation in m_OpeningBookLocations) { TextReader tr = null; tr = new StreamReader(openingBookLocation); string book = tr.ReadToEnd(); tr.Close(); AnalyzeBook(book, startingPoint); } Generic <OpeningMove> .SaveToDisk(startingPoint, "ChessEngineBook.xml"); }
/// <summary> /// Parses one single move. /// </summary> /// <param name="_board">The current board , current opening book's game board</param> /// <param name="_pgnMove">the move's PGN notation</param> /// <param name="moves">A list of all availible moves</param> /// <returns>An opening move represents the parsed response.</returns> private static OpeningMove PGNMoveParser(Board _board, string _pgnMove, List <Move> moves) { bool isCapture = _pgnMove.Contains('x'); bool test = false; if (test) { moves = MovesGenerator.GetLegalMoves(_board); } OpeningMove move = new OpeningMove(); try { //first check if it is castle if (_pgnMove.StartsWith("O-O-O")) { move.Move = moves.First(x => x.MoveType == MoveTypes.CastleQueenSide); } else if (_pgnMove.StartsWith("O-O")) { move.Move = moves.First(x => x.MoveType == MoveTypes.CastleKingSide); } else { int piece = PGNletterToPieceParser(_pgnMove[0], _board.CurrentPlayer); var pieceMoves = moves.Where(x => x.MovingPiece == piece); if (pieceMoves.Count() == 1) { move.Move = pieceMoves.ElementAt(0); } else { if (piece == Piece.BlackPawn || piece == Piece.WhitePawn) { var sourceMoves = pieceMoves.Where(x => x.SourceFile == _pgnMove[0]); if (sourceMoves.Count() == 1) { move.Move = sourceMoves.ElementAt(0); } else { if (isCapture) { string dest = _pgnMove.Substring(_pgnMove.IndexOf('x') + 1, 2); move.Move = sourceMoves.First(x => x.DestinationSquareString == dest); } else { string dest = _pgnMove.Substring(0, 2); move.Move = sourceMoves.First(x => x.DestinationSquareString.StartsWith(dest)); } } } else { IEnumerable <Move> destMoves; if (isCapture) { string dest = _pgnMove.Substring(_pgnMove.IndexOf('x') + 1, 2); destMoves = pieceMoves.Where(x => x.DestinationSquareString == dest); } else { int indexDestRank = _pgnMove.LastIndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' }); string dest = _pgnMove.Substring(indexDestRank - 1, 2); destMoves = pieceMoves.Where(x => x.DestinationSquareString == dest); } switch (destMoves.Count()) { case 1: move.Move = destMoves.ElementAt(0); break; case 2: //either rank or file ambiguity if (_pgnMove[1] >= '1' && _pgnMove[1] <= '9') { //rank ambiguity move.Move = destMoves.First(x => x.SourceRank == _pgnMove[1]); } else { move.Move = destMoves.First(x => x.SourceFile == _pgnMove[1]); } break; case 3: move.Move = destMoves.First(x => x.SourceSquareString == _pgnMove.Substring(1, 2)); break; } } } //now get move type if (_pgnMove.Contains("!!")) { move.MoveType = OpeningMoveType.Excellent; } else if (_pgnMove.Contains("!?")) { move.MoveType = OpeningMoveType.Interesting; } else if (_pgnMove.Contains("?!")) { move.MoveType = OpeningMoveType.Dubious; } else if (_pgnMove.Contains("!")) { move.MoveType = OpeningMoveType.Good; } else if (_pgnMove.Contains("??")) { move.MoveType = OpeningMoveType.Blunder; } else if (_pgnMove.Contains("?")) { move.MoveType = OpeningMoveType.Bad; } } if (move.Move == null) { throw new System.Exception("not found"); } return(move); } catch (Exception ex) { throw ex; } }
/// <summary> /// Parses the book string in the standard PGN format, and inserts it into the starting position node /// </summary> /// <param name="book">A standard PGN formatted string</param> /// <param name="startingPoint">The starting node to add responses to</param> private static void AnalyzeBook(string book, OpeningMove startingPoint) { try { string[] tags = book.Replace("\r\n", " ").Split(new string[] { "[" }, StringSplitOptions.RemoveEmptyEntries); for (int i = 6; i < tags.Length; i += 7) { string movesString = tags[i].Substring(tags[i].IndexOf(']') + 1); string[] movesArray = movesString.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); Board board = new Board(); board.StartingPosition(); //parentMove will slide, containing the parent to the move. OpeningMove parentMove = startingPoint; bool abortMatch = false; for (int j = 0; j < movesArray.Length && !abortMatch; j++) { if (j % 3 == 0) { continue; } if (parentMove.LegalMoves == null) { parentMove.LegalMoves = MovesGenerator.GetLegalMoves(board); } OpeningMove move = PGNMoveParser(board, movesArray[j], parentMove.LegalMoves); if (move.MoveType != OpeningMoveType.Excellent && move.MoveType != OpeningMoveType.Good) { abortMatch = true; break; } //to avoid duplicate entries problem, we make sure to use same instance of a move. OpeningMove storedSameMove = parentMove.Responses.FirstOrDefault(x => x.Move.EqualMove(move.Move)); if (storedSameMove == null) { move.Count = 1; parentMove.Responses.Add(move); } else { parentMove.Count++; move = storedSameMove; } parentMove = move; board.ApplyMove(move.Move); } } } catch (Exception ex) { throw ex; } }