public void spawnWorkers(int?number = null)
        {
            if (number == null)
            {
                number = Environment.ProcessorCount;
            }
            for (int i = 0; i < number; i++)
            {
                var worker = new AIWorker();

                Thread thread = new Thread(() => {
                    EvalBoard.initThreadStaticVariables();
                    worker.WaitForTask();
                });
                thread.Start();
                lock (stateLock) {
                    workers.Add(worker);
                }
            }
        }
Beispiel #2
0
        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 async Task <List <EvaluatedMove> > analyzeBoard(Board board, int depth, Stack <Move> history = null, Action <AIProgress> onProgress = null)
        {
            EvalBoard.initThreadStaticVariables();

            HashSet <ulong> tiedBoards;

            if (history != null)
            {
                tiedBoards = this.findTiedBoard(board, history);
            }
            else
            {
                tiedBoards = new HashSet <ulong>();
            }

            var minmax = new MinMaxAI();
            // get shallow minMax to figure out a initial ordering, because at low depth the thread overhead is going to cost more than it gains
            var moves = minmax.MinMaxList(board, 2);

            solvedMoves = moves.Select(move => new SolvedMove()
            {
                move = move,
            }).ToList();

            var combinedMoves = moves.ToList();
            var oldMoves      = new List <EvaluatedMove>();

            totalFound = 0;
            totalMoves = (depth - 2) * moves.Count;


            for (int currentDepth = 3; currentDepth <= depth; currentDepth++)
            {
                moves = await delegateToWorkers(board, combinedMoves, currentDepth, tiedBoards, onProgress);


                combinedMoves.Clear();

                for (int i = 0; i < moves.Count; i++)
                {
                    // mix the last 2 move orders to try and find the best more as early as possible
                    EvaluatedMove nextMove;
                    if (oldMoves.Count > i)
                    {
                        nextMove = oldMoves[i];
                        if (combinedMoves.Find(existingMove => existingMove.move.Equals(nextMove.move)) == null)
                        {
                            combinedMoves.Add(nextMove);
                        }
                    }

                    nextMove = moves[i];
                    if (combinedMoves.Find(existingMove => existingMove.move.Equals(nextMove.move)) == null)
                    {
                        combinedMoves.Add(nextMove);
                    }
                }
                oldMoves = moves.ToList();
            }

            return(moves);
        }
        private async Task <List <EvaluatedMove> > delegateToWorkers(Board board, List <EvaluatedMove> moves, int depth, HashSet <ulong> tiedBoards, Action <AIProgress> onProgress = null)
        {
            EvalBoard.initThreadStaticVariables();
            int workerWorkId = random.Next();

            lock (stateLock) {
                workId = workerWorkId;
            }
            solvedMoves.Clear();
            //var minmax = new MinMaxAI();
            // get shallow minMax to figure out a initial ordering, which will be used to share out
            //var moves = minmax.MinMaxList(board, 2);
            //var moves = Board.GetMoves(board)
            //    .Where(move => Board.IsLegalMove(board, move))
            //    .Select(move => new BestMove() { move = move, score = 10 })
            //    .ToList();
            // create a list of moves foreach worker
            List <List <Move> > workerMoves = workers.Select((worker) => new List <Move>()).ToList();

            // the idea here is that every worker gets the complete list of moves to try but they start at different points
            // the goal is to find the best move as soon as possible as possible and then share it to all other threads
            // therefor the moves are sorted first and then given out so the first worker
            // worker 1: 1,4,7,10,
            // worker 2: 2,5,8,11,
            // worker 3: 3,6,9,12,
            for (int i = 0; i < moves.Count; i++)
            {
                var move = moves[i];
                workerMoves[i % workerMoves.Count].Add(move.move);
            }

            CancellationTokenSource cancelationSource = new CancellationTokenSource();
            var cancelationtoken = cancelationSource.Token;

            int moveCount = moves.Count;


            object alreadyCompletedLock = new object();

            try {
                var task = new Task(() => {
                    for (int i = 0; i < workers.Count; i++)
                    {
                        var worker = workers[i];
                        var aiTask = new AITask()
                        {
                            taskId         = i,
                            board          = board.CreateCopy(),
                            moves          = workerMoves,
                            depth          = depth,
                            tiedPositions  = tiedBoards,
                            onMoveComplete = (solvedMove) => {
                                int count  = 0;
                                bool isNew = false;
                                lock (stateLock) {
                                    if (workId != workerWorkId)
                                    {
                                        // if the worker returned to late then refuse the task
                                        return;
                                    }
                                    var exists = solvedMoves.Count(move => move.move.move.Equals(solvedMove.move.move));
                                    if (exists == 0)
                                    {
                                        isNew = true;
                                        solvedMoves.Add(solvedMove);
                                        foreach (var otherWorker in workers)
                                        {
                                            if (worker != otherWorker)
                                            {
                                                otherWorker.moveSolved(solvedMove);
                                            }
                                        }
                                        count = solvedMoves.Count;
                                        if (solvedMoves.Count >= moveCount)
                                        {
                                            cancelationSource.Cancel();
                                        }
                                    }
                                }
                                if (onProgress != null && isNew)
                                {
                                    totalFound++;
                                    onProgress(new AIProgress()
                                    {
                                        foundScore = solvedMove.move.score,
                                        total      = totalMoves,
                                        progress   = totalFound,
                                        move       = solvedMove,
                                        depth      = depth
                                    });
                                }
                            }
                        };
                        worker.tasks.Enqueue(aiTask);
                    }
                    // max wait for 3 minutes
                    Task.Delay(1000 * 60 * 3).Wait();
                    cancelationSource.Cancel();
                });

                task.Start();
                //while (!alreadyComplete) {
                //    Task.Delay(10).GetAwaiter().GetResult();
                //}
                //lock(alreadyCompleteLock) {
                //    wasComplated = alreadyComplete;
                //}
                //if(!wasComplated) {

                task.Wait(cancelationtoken);
                //}
            } catch (OperationCanceledException) {
                // intentional cancel
            }


            return(solvedMoves.Select(move => move.move).OrderBy(move => move.score).Reverse().ToList());
        }