public void hashDifferentBoards() { var first = BoardFactory.LoadBoardFromFen("rnbqkbnr/pppp1ppp/8/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR b KQkq - 2 2"); var second = BoardFactory.LoadBoardFromFen("rnbqkbnr/pppp1ppp/8/4p3/2B1PP2/8/PPPP2PP/RNBQK1NR b KQkq - 2 2"); Assert.AreNotEqual(HashBoard.hash(first), HashBoard.hash(second)); }
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); }
public void blackAndWhiteDontHashToTheSameValue() { var whiteBoard = BoardFactory.LoadBoardFromFen("r3k2r/8/8/2pP4/8/8/8/R3K2R w KQkq c6 0 2"); var blackBoard = BoardFactory.LoadBoardFromFen("r3k2r/8/8/2pP4/8/8/8/R3K2R b KQkq c6 0 2"); Assert.AreNotEqual(HashBoard.hash(whiteBoard), HashBoard.hash(blackBoard)); }
public void enpassantTargetMakesADifference() { var noEnpassant = BoardFactory.LoadBoardFromFen("rnb1kbnr/1ppp1ppp/p3p3/8/1P1PP2q/5N2/P1P2PPP/RNBQKB1R b KQkq - 1 0"); var enpassant = BoardFactory.LoadBoardFromFen("rnb1kbnr/1ppp1ppp/p3p3/8/1P1PP2q/5N2/P1P2PPP/RNBQKB1R b KQkq b3 1 0"); var enpassantHash = HashBoard.hash(enpassant); Assert.AreNotEqual(HashBoard.hash(noEnpassant), enpassantHash); }
// navnet skifter til noUnionFunction i anden test public void hashFunktion() { ulong combined = 0; var board = ChessGame.StartGame().board; for (int i = 0; i < 1000000; i++) { combined += HashBoard.hash(board); } }
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); }
// start by solving your own threads problems // afterwards start solving moves that belong to other that have not been started yet // if there are only started moves left then start working a move that is already beeing worked on (they might have a worse starting minimum score) internal void MinMaxWorker(AITask aiTask) { currentTask = aiTask; Board board = aiTask.board; List <Move> moves = aiTask.moves[aiTask.taskId]; int depth = aiTask.depth; Action <SolvedMove> onMoveComplete = aiTask.onMoveComplete; lock (stateLock) { max = float.MaxValue; min = float.MinValue; alreadySolved.Clear(); begunMoves.Clear(); } bestScore = float.MinValue; boardHash = HashBoard.hash(board); SolvedMove lastSolvedMove = null; // ------------ start working on this threads moves ------------------ foreach (var move in moves) { // check that the other threads have not begun the next move yet if (!alreadySolved.Contains(move) && !begunMoves.Contains(move)) { lastSolvedMove = MinMaxMove(aiTask, move, lastSolvedMove); } } // ------------ start searching for other threads unfinished work -------------- IEnumerable <Move> availableMoves = new List <Move>(); Random random = new Random(); do { availableMoves = aiTask.moves.SelectMany((threadsMoves) => { return(threadsMoves.Where(move => !alreadySolved.Contains(move) && !begunMoves.Contains(move))); }).ToList(); // use random to avoid conflicts var nextMove = availableMoves.RandomElementUsing(random); if (MoveHelper.isValidMove(nextMove)) { lastSolvedMove = MinMaxMove(aiTask, nextMove, lastSolvedMove); } } while (availableMoves.Count() > 0); // ------------ start searching for other threads begun but unfinished work -------------- do { availableMoves = aiTask.moves.SelectMany((threadsMoves) => { return(threadsMoves.Where(move => !alreadySolved.Contains(move))); }).ToList(); // use random to avoid conflicts var nextMove = availableMoves.RandomElementUsing(random); if (MoveHelper.isValidMove(nextMove)) { lastSolvedMove = MinMaxMove(aiTask, nextMove, lastSolvedMove); } } while (availableMoves.Count() > 0); if (lastSolvedMove != null) { aiTask.onMoveComplete(lastSolvedMove); } }
// 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); }
public float MinMax(Board board, int depth, HashSet <ulong> tiedPositions, bool maximizing = true, float min = float.MinValue, float max = float.MaxValue) { // this function boardHash = HashBoard.hash(board); return(MinMaxInteral(board, depth, tiedPositions, maximizing, min, max)); }
public void hasDifferentBitStrings() { Assert.AreNotEqual(HashBoard.pieceHash(0, Piece.EMPTY), HashBoard.pieceHash(1, Piece.EMPTY)); }