алгоритм мин-макс

Я написал однопоточный алгоритм min-max для шахматной игры, который отлично работает. Теперь я пытаюсь переписать его, чтобы использовать все доступные процессорные ядра, но не могу заставить его работать правильно.

Моя идея состоит в том, чтобы создать столько потоков, сколько ядер в системе (в моем случае 4), и позволить потокам добавлять и удалять рабочие элементы из очереди. Каждый из этих рабочих элементов представляет собой «CalculateState», который содержит информацию о возможной шахматной доске после x ходов на доске.

Когда рабочий элемент создается в maxDepth, он оценивает шахматную доску и «возвращает» ее значение. Возврат выполняется путем распространения его значения вверх по дереву проверенных ходов (для имитации рекурсии).

Запуск алгоритма:

private readonly ConcurrentPriorityQueue<int, CalculateState> _calculateStates = new ConcurrentPriorityQueue<int, CalculateState>(); 
private Thread[] _threads = new Thread[Environment.ProcessorCount];
private const int MaxDepth = 3;
private PlayerColor _maxPlayer;

public Move CalculateMoveMultithreaded(ChessBoard board)
    {
        _maxPlayer = board.TurnToMove;
        var parentState = new CalculateState(null, null, 0, null, int.MaxValue, int.MinValue, board.TurnToMove);

        foreach (var move in board.GetPossibleMoves())
        {
            move.MakeMove(board);
            var newState = ChessStateTransforms.TransformChessBoardToState(board);
            move.UnMakeMove(board);

            _calculateStates.Enqueue(MaxDepth, new CalculateState(move, newState, 1, parentState, int.MaxValue, int.MinValue, Player.OppositeColor(board.TurnToMove)));
        }

        for (var i = 0; i < _threads.Length; i++)
        {
            var calculationThread = new Thread(DoWork);
            _threads[i] = calculationThread;
            calculationThread.Start();
        }

        foreach (var thread in _threads)
        {
            thread.Join();
        }

        return parentState.MoveToMake;
    }

Выполнение потока:

private void DoWork()
    {
        while (true)
        {
            KeyValuePair<int, CalculateState> queueItem;
            if (!_calculateStates.TryDequeue(out queueItem))
                break;

            var calculateState = queueItem.Value;
            var board = ChessStateTransforms.TransformChessStateIntoChessBoard(calculateState.ChessState);

            if (calculateState.Depth == MaxDepth)
            {
                var boardValue = board.ValueOfBoard(_maxPlayer);
                calculateState.PropergateValue(boardValue);
                continue;
            }

            foreach (var move in board.GetPossibleMoves())
            {
                move.MakeMove(board);
                var newState = ChessStateTransforms.TransformChessBoardToState(board);
                move.UnMakeMove(board);

                _calculateStates.Enqueue(MaxDepth - calculateState.Depth, new CalculateState(calculateState.MoveToMake, newState, calculateState.Depth + 1, calculateState, calculateState.MinValue, calculateState.MaxValue, Player.OppositeColor(board.TurnToMove)));
            }

        }
    }

Контекст рабочего элемента.

 private class CalculateState
    {
        public readonly PlayerColor Turn;
        public int MaxValue;
        public int MinValue;

        public readonly int Depth;
        public readonly ChessState ChessState;
        public Move MoveToMake;

        private readonly CalculateState _parentState;        

        public CalculateState(Move moveToMake, ChessState chessState, int depth, CalculateState parentState, int minValue, int maxValue, PlayerColor turn)
        {
            Depth = depth;
            _parentState = parentState;
            MoveToMake = moveToMake;
            ChessState = chessState;
            MaxValue = maxValue;
            Turn = turn;
            MinValue = minValue;
        }

        public void PropergateValue(int value, Move firstMove = null)
        {
            lock (this)
            {
                if (Turn == _maxPlayer)
                {
                    if (value > MaxValue)
                    {
                        MaxValue = value;

                        if (Depth == 0)
                        {
                            MoveToMake = firstMove;
                            return;
                        }

                        _parentState.PropergateValue(MaxValue, MoveToMake);
                    }
                }
                else
                {
                    if (value < MinValue)
                    {
                        MinValue = value;

                        if (Depth == 0)
                        {
                            MoveToMake = firstMove;
                            return;
                        }

                        _parentState.PropergateValue(MinValue, MoveToMake);
                    }


                }
            }
        }
}

Так как алгоритм будет возвращать ходы, которые забирают фигуры противника, но совершенно не защищают свои. Я уверен, что код в chessboard, move, valueofboard и т. д. правильный. Проблема должна быть в коде многопоточности/распространения значения. Я рвал свои волосы из-за этого больше недели и был бы очень признателен за любую помощь.

Спасибо


person user1565031    schedule 31.07.2012    source источник
comment
В чем проблема? Что не работает как надо?   -  person dreamlax    schedule 31.07.2012
comment
Мин-макс не работает. Например, он заберет мой Камень, даже если при этом потеряет свою Королеву.   -  person user1565031    schedule 31.07.2012


Ответы (1)


Извините, что не дал точного ответа на то, что вы спросили (на самом деле ваша проблема не совсем ясна, и исследовать ее на основе того, что вы дали, очень сложно), но я рекомендую лучше реализовать сокращение альфа-бета< /strong> в вашем мин-макс. Это может помочь вам гораздо больше, чем сотни процессорных ядер. Если вы хотите прочитать об этом, см. http://www.cs.utah.edu/~hal/courses/2009S_AI/Walkthrough/AlphaBeta/ и http://cs.ucla.edu/~rosen/161/notes/alphabeta.html

PS: что касается вашего вопроса, было бы сложно реализовать многопоточную рекурсию (эффективно используя все потоки и не разбивая дерево перемещения только на верхнем уровне). Я почти уверен, что вы сделали ошибку там. Я бы рекомендовал вам использовать дополнительную очередь состояний, необходимых для расчета (расширения). Каждый поток должен получать элемент из очереди и вычислять его, добавляя узлы clild в ваше дерево. Таким образом, ваш алгоритм больше не будет DFS, а превратится в BFS (поиск в ширину), который намного эффективнее в таких задачах вычисления ходов.

person Sasha    schedule 31.07.2012
comment
Спасибо за ответ. Я использовал альфа-бета-обрезку в своей однопоточной версии и планировал реализовать нечто подобное и для многопоточной, но мне нужно, чтобы это работало в первую очередь. Я понимаю, что этот вопрос нелегко исследовать. Я был бы очень рад увидеть код/псевдокод рабочей версии вместо собственного исправления. - person user1565031; 31.07.2012