public HashSet <ulong> findTiedBoard(Board board, Stack <Move> history) { Dictionary <ulong, int> occurredPositions = new Dictionary <ulong, int>(); HashSet <ulong> tiedPositions = new HashSet <ulong>(); // take copies so we do not modify the original collection var boardCopy = board.CreateCopy(); Stack <Move> historyCopy = new Stack <Move>(history); var hash = HashBoard.hash(board); while (historyCopy.Count != 0) { if (!occurredPositions.ContainsKey(hash)) { occurredPositions.Add(hash, 0); } // count how many times the position has occured occurredPositions[hash]++; var move = historyCopy.Pop(); boardCopy.UndoMove(move); hash = HashBoard.ApplyMove(board, move, hash); } foreach (var position in occurredPositions) { if (position.Value >= 2) { // when a position occurs 3 times tiedPositions.Add(position.Key); } } return(tiedPositions); }
private Board incrementalUpdate(string fen, int fromPosition, int toPosition, Piece promotion = Piece.EMPTY) { var board = BoardFactory.LoadBoardFromFen(fen); var boardHash = HashBoard.hash(board); var move = board.FindMove(fromPosition, toPosition, promotion); var nextBoardHash = HashBoard.ApplyMove(board, move, boardHash); board.Move(move); var expectedHash = HashBoard.hash(board); Assert.AreEqual(expectedHash, nextBoardHash); return(board); }
public void whiteBigPawnMoveBecomesSamePosition() { /* * Starting position (White to play) +---------------+ |r n b q k b n r| 8 |p p p p _ p p p| 7 |_ _ _ _ _ _ _ _| 6 |_ _ _ _ p _ _ _| 5 |_ _ B _ P _ _ _| 4 |_ _ _ _ _ _ _ _| 3 |P P P P _ P P P| 2 |R N B Q K _ N R| 1 +---------------+ * A B C D E F G H * F2 -> F4 +---------------+ |r n b q k b n r| 8 |p p p p _ p p p| 7 |_ _ _ _ _ _ _ _| 6 |_ _ _ _ p _ _ _| 5 |_ _ B _ P P _ _| 4 |_ _ _ _ _ _ _ _| 3 |P P P P _ _ P P| 2 |R N B Q K _ N R| 1 +---------------+ * A B C D E F G H */ var board = BoardFactory.LoadBoardFromFen("rnbqkbnr/pppp1ppp/8/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR w KQkq - 2 2"); var moves = board.GetMoves(); var boardHash = HashBoard.hash(board); var move = moves.FindTargetPosition(BoardStateOffset.F5); var nextBoardHash = HashBoard.ApplyMove(board, move, boardHash); board.Move(move); var expectedHash = HashBoard.hash(board); Assert.AreEqual(expectedHash, nextBoardHash); }
public void canPromote() { /* * Starting position (White to play) +---------------+ |r _ _ _ k _ _ r| 8 |_ _ _ _ _ _ _ _| 7 |_ _ _ _ _ _ _ _| 6 |_ _ p P _ _ _ _| 5 |_ _ _ _ _ _ _ _| 4 |_ _ _ _ _ _ _ _| 3 |_ _ _ _ _ _ _ _| 2 |R _ _ _ K _ _ R| 1 +---------------+ * A B C D E F G H * D5 -> C6 +---------------+ |r _ _ _ k _ _ r| 8 |_ _ _ _ _ _ _ _| 7 |_ _ P _ _ _ _ _| 6 |_ _ _ _ _ _ _ _| 5 |_ _ _ _ _ _ _ _| 4 |_ _ _ _ _ _ _ _| 3 |_ _ _ _ _ _ _ _| 2 |R _ _ _ K _ _ R| 1 +---------------+ * A B C D E F G H */ var board = BoardFactory.LoadBoardFromFen("r3k2r/8/8/2pP4/8/8/8/R3K2R w KQkq c6 0 2"); var moves = board.GetMoves(); var boardHash = HashBoard.hash(board); var move = moves.FindTargetPosition(BoardStateOffset.D5, BoardStateOffset.C6); var nextBoardHash = HashBoard.ApplyMove(board, move, boardHash); board.Move(move); board.UndoMove(move); var previousHash = HashBoard.ApplyMove(board, move, nextBoardHash); Assert.AreEqual(boardHash, previousHash); }
// should only be called by MinMaxWorker, since it initializes a bunch of stuff private SolvedMove MinMaxMove(AITask aiTask, Move move, SolvedMove lastSolvedMove) { if (lastSolvedMove != null) { lastSolvedMove.startSolvingMove = move; aiTask.onMoveComplete(lastSolvedMove); } Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); boardHash = HashBoard.ApplyMove(aiTask.board, move, boardHash); ulong currentBoardHash = boardHash; lock (stateLock) { if (alreadySolved.Contains(move)) { // if the hash has board has already been analyzed then skip boardHash = HashBoard.ApplyMove(aiTask.board, move, boardHash); return(null); } if (min > bestScore) { bestScore = min; } } aiTask.board.Move(move); var startedFromMin = min; var detectWinnerMoves = aiTask.board.GetMoves(); // check for winner var winner = aiTask.board.detectWinner(detectWinnerMoves); if (winner != Winner.NONE) { float score = 0; if ((winner == Winner.WINNER_WHITE || winner == Winner.WINNER_BLACK)) { // if a checkmate is found then no deeper moves matter since we are going to play that move score = float.MaxValue - aiTask.board.VirtualLevel; } else if (winner == Winner.DRAW) { score = 0; } lock (stateLock) { alreadySolved.Add(move); min = Math.Max(score, min); } stopWatch.Stop(); return(new SolvedMove() { startFromMin = min, min = min, max = max, durationMS = stopWatch.ElapsedMilliseconds, move = new EvaluatedMove() { move = move, score = score }, taskId = currentTask.taskId, }); } if (aiTask.tiedPositions.Contains(currentBoardHash)) { float score = 0; lock (stateLock) { alreadySolved.Add(move); min = Math.Max(score, min); } return(new SolvedMove() { startFromMin = min, min = min, max = max, durationMS = stopWatch.ElapsedMilliseconds, taskId = currentTask.taskId, move = new EvaluatedMove() { move = move, score = score, }, }); } aiTask.board.VirtualLevel++; var moveScore = minmax.MinMax(aiTask.board, aiTask.depth, aiTask.tiedPositions, false, min, max); aiTask.board.VirtualLevel--; aiTask.board.UndoMove(move); boardHash = HashBoard.ApplyMove(aiTask.board, move, boardHash); lock (stateLock) { if (alreadySolved.Contains(move)) { //Console.WriteLine($"collision! refound found move! {MoveHelper.ReadableMove(move)}"); // if the hash has board has already been analyzed before this thread managed to do it, then skip return(null); } // optimize for player if (moveScore > bestScore) { bestScore = moveScore; } min = Math.Max(moveScore, min); alreadySolved.Add(move); } // outside of stateLock because otherwise it will trigger will trigger the lock in moveSolved Thread t = Thread.CurrentThread; stopWatch.Stop(); return(new SolvedMove() { startFromMin = startedFromMin, min = min, max = max, durationMS = stopWatch.ElapsedMilliseconds, move = new EvaluatedMove() { move = move, score = moveScore }, taskId = currentTask.taskId }); }
public List <EvaluatedMove> MinMaxList(Board board, int depth, HashSet <ulong> tiedPositions = null, bool maximizing = true, float min = float.MinValue, float max = float.MaxValue) { boardHash = HashBoard.hash(board); if (tiedPositions == null) { tiedPositions = new HashSet <ulong>(); } List <EvaluatedMove> movePoints = new List <EvaluatedMove>(); //var bestMove = MinMaxInternal(board, depth, maximizing, min, max); var bestMove = maximizing ? float.MinValue : float.MaxValue; var moveList = layeredLists[board.VirtualLevel]; moveList.Clear(); var moves = board.GetMoves(moveList); var winner = board.detectWinner(moves); foreach (var move in moves) { byte myTurn = board.IsWhiteTurn; boardHash = HashBoard.ApplyMove(board, move, boardHash); board.Move(move); board.VirtualLevel++; var attacked = board.Attacked(board.GetKingPosition(myTurn), myTurn); if (attacked) { // if the king is under attack after making the move then it is not a valid move, in which case ignore the move board.VirtualLevel--; board.UndoMove(move); boardHash = HashBoard.ApplyMove(board, move, boardHash); continue; } var moveScore = MinMax(board, depth, tiedPositions, !maximizing, min, max); movePoints.Add(new EvaluatedMove() { move = move, score = moveScore, }); board.VirtualLevel--; board.UndoMove(move); boardHash = HashBoard.ApplyMove(board, move, boardHash); if (maximizing) { // optimize for player if (moveScore > bestMove) { bestMove = moveScore; } min = Math.Max(moveScore, min); } else { if (moveScore < bestMove) { bestMove = moveScore; } max = Math.Min(moveScore, max); } } // sort the moves in descending order //movePoints.Sort((a, b) => (a.score < b.score) ? 1 : -1); movePoints = movePoints.OrderBy(move => move.score).Reverse().ToList(); return(movePoints); }
protected float MinMaxInteral(Board board, int depth, HashSet <ulong> tiedPositions, bool maximizing = true, float min = float.MinValue, float max = float.MaxValue) { var optimizeForColor = maximizing ? 1 : 0; var minimizeForColor = optimizeForColor ^ 1; float bestMove = maximizing ? float.MinValue : float.MaxValue; if (tiedPositions.Contains(boardHash)) { // if the position is tied due to repetition then return 0; return(0); } var moveList = layeredLists[board.VirtualLevel]; moveList.Clear(); //var moves = Board.GetMoves(board, moveList); var moves = board.GetMoves(moveList); if (board.VirtualLevel >= depth) { // if we have reached max depth assign a score. var winner = board.detectWinner(moves); if ((winner == Winner.WINNER_WHITE || winner == Winner.WINNER_BLACK)) { if (maximizing) { // if a checkmate is found then no deeper moves matter since we are going to play that move return(float.MinValue + board.VirtualLevel); } else { return(float.MaxValue - board.VirtualLevel); } } else if (winner == Winner.DRAW) { return(0); } float score = EvalBoard.evalBoard(board, moves); int isWhite = ((board.IsWhiteTurn ^ depth) & 1); if (isWhite != 1) { // if black started the query then optimize for black score *= -1; } return(score); } // because detectWinner requires checking for valid moves, which is slow only do it for end nodes // for all other cases reimplement reimplement the logic locally if (board.hasInsufficientMaterialOrTimeLimit()) { return(0); } // hasValidMove is used to track if the player has a valid move they can play, // if not this is used to declare a winner bool foundValidMove = false; foreach (MoveAndEvaluatedPosition moveAndEvluated in moves .Select(GetMoveAndEvaluatedPositionFromMove) .OrderByDescending(OrderMoveAndEvaluatedPositionFromMove)) { var move = moveAndEvluated.move; //foreach (var move in moves) { byte myTurn = board.IsWhiteTurn; //var calculatedBeforeHash = HashBoard.hash(board); //var beforeHash = boardHash; boardHash = HashBoard.ApplyMove(board, move, boardHash); board.Move(move); //var expectedHash = HashBoard.hash(board); //if (expectedHash != boardHash) { // Console.WriteLine("ARG"); // Console.WriteLine(calculatedBeforeHash); // Console.WriteLine(beforeHash); //} board.VirtualLevel++; var attacked = board.Attacked(board.GetKingPosition(myTurn), myTurn); if (attacked) { // if the king is under attack after making the move then it is not a valid move, in which case ignore the move board.VirtualLevel--; board.UndoMove(move); boardHash = HashBoard.ApplyMove(board, move, boardHash); continue; } foundValidMove = true; EvaluatedPosition existingScore = moveAndEvluated.evaluatedPosition; bool useCache = false; float moveScore = 0; int movesFromMaxDepth = depth - board.VirtualLevel; if (moveAndEvluated.hasEvaluatedMove) { // subtract the difference between the 2 depths to give previous best moves a disadvantage // because a previous move that was explored at less depth has less knowlegde than the current best if (existingScore.distanceToEndSearch - depth + existingScore.depth >= movesFromMaxDepth) { useCache = true; moveScore = existingScore.score; } } if (!useCache) { moveScore = MinMax(board, depth, tiedPositions, !maximizing, min, max); moveScores[boardHash] = new EvaluatedPosition() { distanceToEndSearch = (byte)movesFromMaxDepth, score = moveScore, depth = (byte)depth, //fen = board.simplifiedFEN, }; } board.VirtualLevel--; board.UndoMove(move); // undo previous hash boardHash = HashBoard.ApplyMove(board, move, boardHash); if (maximizing) { // optimize for player if (moveScore > bestMove) { bestMove = moveScore; } min = Math.Max(moveScore, min); if (min > max) { return(bestMove); } } else { if (moveScore < bestMove) { bestMove = moveScore; } max = Math.Min(moveScore, max); if (min > max) { return(bestMove); } } } if (!foundValidMove) { if (maximizing) { return(float.MinValue + board.VirtualLevel); // if a checkmate is found then no deeper moves matter since we are going to play that move } else { return(float.MaxValue - board.VirtualLevel); } } return(bestMove); }