Exemple #1
0
        /// <summary>
        /// Should only be called through the public Search method.
        /// </summary>
        /// <param name="state">The game state to consider.</param>
        /// <param name="isLightPlayer">The player to move.</param>
        /// <param name="alpha">The alpha pruning value.</param>
        /// <param name="beta">The beta pruning value.</param>
        /// <param name="depth">The current search depth.</param>
        /// <returns>A MinimaxMove that represents the best move found.</returns>
        /// <remarks>
        /// The initial alpha value should be Int32.MinValue, the initial beta value
        /// should be Int32.MaxValue, and the initial depth value should be 0.
        ///
        /// The search will terminate ASAP if the m_ct cancellation token is signaled.
        ///
        /// This method is thread-safe.
        /// </remarks>
        private MinimaxMove InternalSearch(MinimaxSpot[,] state, bool isLightPlayer, int alpha, int beta, int depth)
        {
            // Stop the search if...
            if (TerminalTest(state) || depth >= m_maxDepth || m_ct.IsCancellationRequested)
            {
                m_movesConsidered++;
                return(new MinimaxMove(EvaluateHeuristic(state)));
            }

            // Initialize the best move for this recursive call.
            MinimaxMove bestMove = new MinimaxMove(isLightPlayer ? Int32.MinValue : Int32.MaxValue);

            // Get the valid moves for this recursive call.
            IEnumerable <MinimaxMove> validMoves = GetValidMoves(state, isLightPlayer);

            // If there are valid moves, recurse on each.
            bool consideredLocalMoves = false;

            foreach (MinimaxMove move in validMoves)
            {
                consideredLocalMoves = true;

                MinimaxMove curMove = move;
                curMove.Value = InternalSearch(GetInsight(state, curMove, isLightPlayer), !isLightPlayer, alpha, beta, depth + 1).Value;
                if (isLightPlayer)
                {
                    if (curMove.Value > bestMove.Value)
                    {
                        bestMove = curMove;
                    }
                    if (bestMove.Value >= beta)
                    {
                        break;
                    }
                    alpha = Math.Max(alpha, bestMove.Value.Value);
                }
                else
                {
                    if (curMove.Value < bestMove.Value)
                    {
                        bestMove = curMove;
                    }
                    if (bestMove.Value <= alpha)
                    {
                        break;
                    }
                    beta = Math.Min(beta, bestMove.Value.Value);
                }
            }

            // If there were no valid moves, still calculate the value.
            if (!consideredLocalMoves)
            {
                bestMove.Value = InternalSearch(state, !isLightPlayer, alpha, beta, depth + 1).Value;
            }

            return(bestMove);
        }
 protected override MinimaxSpot[,] GetInsight(MinimaxSpot[,] state, MinimaxMove move, bool isLightPlayer)
 {
     MinimaxSpot[,] insightState = new MinimaxSpot[m_numRows, m_numCols];
     for (int i = 0; i < m_numRows; i++)
     {
         for (int j = 0; j < m_numCols; j++)
         {
             insightState[i, j] = state[i, j];
         }
     }
     insightState[move.Row, move.Col] = isLightPlayer ? MinimaxSpot.Light : MinimaxSpot.Dark;
     return(insightState);
 }
Exemple #3
0
        /// <summary>
        /// Returns the best move resulting from a Minimax, alpha-beta pruning search on the given state.
        /// </summary>
        /// <param name="state">The state to consider.</param>
        /// <param name="isLightPlayer">The player to move.</param>
        /// <param name="inParallel">A boolean indicating whether to use the parallel algorithm.</param>
        /// <returns>A MinimaxMove that represents the best move found.</returns>
        /// <remarks>
        /// This method will only return a MinimaxMove(-1...) if there are no valid moves.
        /// </remarks>
        public MinimaxMove Search(MinimaxSpot[,] state, bool isLightPlayer, bool inParallel)
        {
            // Initialize a bunch of state.
            m_maxDepth         = MaxDepth == -1 ? Int32.MaxValue : MaxDepth;
            m_degOfParallelism = DegreeOfParallelism;
            m_timeLimit        = TimeLimit;
            m_taskCount        = 0;
            m_movesConsidered  = 0;
            var curCts = m_cts = new CancellationTokenSource();

            m_ct = m_cts.Token;

            MinimaxMove aiMove = new MinimaxMove(-1, -1, null);

            // Start the timeout timer.  Done using a dedicated thread to minimize delay
            // in cancellation due to lack of threads in the pool to run the callback.
            var timeoutTask = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(m_timeLimit);
                curCts.Cancel();
            }, TaskCreationOptions.LongRunning);

            // Do the search
            aiMove = inParallel ?
                     InternalSearchTPL(state, isLightPlayer, Int32.MinValue, Int32.MaxValue, 0, CancellationToken.None) :
                     InternalSearch(state, isLightPlayer, Int32.MinValue, Int32.MaxValue, 0);

            // Make sure that MinimaxMove(-1...) is only returned if there are no valid moves, because
            // InternalSearch* may return MinimaxMove(-1...) if none of the valid moves beats Int32.Min/Max.
            if (aiMove.Row == -1)
            {
                foreach (var move in GetValidMoves(state, isLightPlayer))
                {
                    aiMove       = move;
                    aiMove.Value = isLightPlayer ? Int32.MinValue : Int32.MaxValue;
                    break;
                }
            }

            return(aiMove);
        }
Exemple #4
0
 /// <summary>
 /// Returns the game state that results when the given player plays the given move on the given
 /// state.  If the move is invalid, the new state should be the same as the old state.
 /// Must be thread-safe.
 /// </summary>
 /// <param name="state">The state to play a move on.</param>
 /// <param name="move">The move to play.</param>
 /// <param name="isLightPlayer">The player to play the move.</param>
 /// <returns>A MinimaxSpot matrix that represents the insight state.</returns>
 protected abstract MinimaxSpot[,] GetInsight(MinimaxSpot[,] state, MinimaxMove move, bool isLightPlayer);
Exemple #5
0
        /// <summary>
        /// Should only be called through the public Search method.
        /// </summary>
        /// <param name="state">The game state to consider.</param>
        /// <param name="isLightPlayer">The player to move.</param>
        /// <param name="alpha">The alpha pruning value.</param>
        /// <param name="beta">The beta pruning value.</param>
        /// <param name="depth">The current search depth.</param>
        /// <param name="token">The pruning token.</param>
        /// <returns>A MinimaxMove that represents the best move found.</returns>
        /// <remarks>
        /// The initial alpha value should be Int32.MinValue, the initial beta value
        /// should be Int32.MaxValue, the initial depth value should be 0, and the
        /// initial token should be a non-settable token.
        ///
        /// The search will terminate ASAP if the m_ct cancellation token is signaled.
        ///
        /// This method is thread-safe.
        /// </remarks>
        private MinimaxMove InternalSearchTPL(MinimaxSpot[,] state, bool isLightPlayer, int alpha, int beta, int depth, CancellationToken token)
        {
            // Stop the search if...
            if (TerminalTest(state) || depth >= m_maxDepth || m_ct.IsCancellationRequested)
            {
                m_movesConsidered++; // NOTE: this is racy and may be lower than the actual count, but it only needs to be an appx
                return(new MinimaxMove(EvaluateHeuristic(state)));
            }

            // Initialize the best move for this recursive call.
            MinimaxMove bestMove = new MinimaxMove(isLightPlayer ? Int32.MinValue : Int32.MaxValue);

            // Get the valid moves for this recursive call.
            IEnumerable <MinimaxMove> validMoves = GetValidMoves(state, isLightPlayer);

            bool                    consideredLocalMoves = false;
            Queue <Task>            workers = new Queue <Task>();
            object                  bigLock = new object();
            CancellationTokenSource cts     = new CancellationTokenSource();

            foreach (MinimaxMove move in validMoves)
            {
                // SHARED STATE
                //     The local variables (bestMove, alpha, beta) are protected by a lock.
                //     The non-local variables (m_taskCount) are modified using Interlocked
                consideredLocalMoves = true;

                // If the pruning token is signaled, stop this loop.
                if (token.IsCancellationRequested)
                {
                    cts.Cancel();
                    break;
                }

                MinimaxMove curMove = move;
                if (m_taskCount < m_degOfParallelism && depth <= m_maxDepth - 1)
                {
                    Interlocked.Increment(ref m_taskCount);
                    workers.Enqueue(Task.Factory.StartNew(() =>
                    {
                        curMove.Value = InternalSearchTPL(GetInsight(state, curMove, isLightPlayer), !isLightPlayer, alpha, beta, depth + 1, cts.Token).Value;
                        lock (bigLock)
                        {
                            if (isLightPlayer)
                            {
                                if (curMove.Value > bestMove.Value)
                                {
                                    bestMove = curMove;
                                }
                                if (bestMove.Value >= beta)
                                {
                                    cts.Cancel();
                                }
                                alpha = Math.Max(alpha, bestMove.Value.Value);
                            }
                            else
                            {
                                if (curMove.Value < bestMove.Value)
                                {
                                    bestMove = curMove;
                                }
                                if (bestMove.Value <= alpha)
                                {
                                    cts.Cancel();
                                }
                                beta = Math.Min(beta, bestMove.Value.Value);
                            }
                        }
                        Interlocked.Decrement(ref m_taskCount);
                    }));
                }
                else
                {
                    bool isPruning = false;
                    curMove.Value = InternalSearchTPL(GetInsight(state, curMove, isLightPlayer), !isLightPlayer, alpha, beta, depth + 1, cts.Token).Value;

                    // If there are no tasks, no need to lock.
                    bool lockTaken = false;
                    try
                    {
                        if (workers.Count > 0)
                        {
                            Monitor.Enter(bigLock, ref lockTaken);
                        }
                        if (isLightPlayer)
                        {
                            if (curMove.Value > bestMove.Value)
                            {
                                bestMove = curMove;
                            }
                            if (bestMove.Value >= beta)
                            {
                                isPruning = true;
                            }
                            alpha = Math.Max(alpha, bestMove.Value.Value);
                        }
                        else
                        {
                            if (curMove.Value < bestMove.Value)
                            {
                                bestMove = curMove;
                            }
                            if (bestMove.Value <= alpha)
                            {
                                isPruning = true;
                            }
                            beta = Math.Min(beta, bestMove.Value.Value);
                        }
                    }
                    finally { if (lockTaken)
                              {
                                  Monitor.Exit(bigLock);
                              }
                    }

                    if (isPruning)
                    {
                        cts.Cancel();
                        break;
                    }
                }
            }

            Task.WaitAll(workers.ToArray());

            // If there were no valid moves, still calculate the value.
            if (!consideredLocalMoves)
            {
                bestMove.Value = InternalSearchTPL(state, !isLightPlayer, alpha, beta, depth + 1, token).Value;
            }

            return(bestMove);
        }
Exemple #6
0
        /// <summary>
        /// Should only be called through the public Search method.
        /// </summary>
        /// <param name="state">The game state to consider.</param>
        /// <param name="isLightPlayer">The player to move.</param>
        /// <param name="alpha">The alpha pruning value.</param>
        /// <param name="beta">The beta pruning value.</param>
        /// <param name="depth">The current search depth.</param>
        /// <returns>A MinimaxMove that represents the best move found.</returns>
        /// <remarks>
        /// The initial alpha value should be Int32.MinValue, the initial beta value 
        /// should be Int32.MaxValue, and the initial depth value should be 0.
        /// 
        /// The search will terminate ASAP if the m_ct cancellation token is signaled.
        /// 
        /// This method is thread-safe.
        /// </remarks>
        private MinimaxMove InternalSearch(MinimaxSpot[,] state, bool isLightPlayer, int alpha, int beta, int depth)
        {
            // Stop the search if...
            if (TerminalTest(state) || depth >= m_maxDepth || m_ct.IsCancellationRequested)
            {
                m_movesConsidered++;
                return new MinimaxMove(EvaluateHeuristic(state));
            }

            // Initialize the best move for this recursive call.
            MinimaxMove bestMove = new MinimaxMove(isLightPlayer ? Int32.MinValue : Int32.MaxValue);

            // Get the valid moves for this recursive call.
            IEnumerable<MinimaxMove> validMoves = GetValidMoves(state, isLightPlayer);

            // If there are valid moves, recurse on each.
            bool consideredLocalMoves = false;
            foreach (MinimaxMove move in validMoves)
            {
                consideredLocalMoves = true;

                MinimaxMove curMove = move;
                curMove.Value = InternalSearch(GetInsight(state, curMove, isLightPlayer), !isLightPlayer, alpha, beta, depth + 1).Value;
                if (isLightPlayer)
                {
                    if (curMove.Value > bestMove.Value) bestMove = curMove;
                    if (bestMove.Value >= beta) break;
                    alpha = Math.Max(alpha, bestMove.Value.Value);
                }
                else
                {
                    if (curMove.Value < bestMove.Value) bestMove = curMove;
                    if (bestMove.Value <= alpha) break;
                    beta = Math.Min(beta, bestMove.Value.Value);
                }
            }

            // If there were no valid moves, still calculate the value.
            if (!consideredLocalMoves)
            {
                bestMove.Value = InternalSearch(state, !isLightPlayer, alpha, beta, depth + 1).Value;
            }

            return bestMove;
        }
Exemple #7
0
 /// <summary>
 /// Returns the game state that results when the given player plays the given move on the given
 /// state.  If the move is invalid, the new state should be the same as the old state.
 /// Must be thread-safe.
 /// </summary>
 /// <param name="state">The state to play a move on.</param>
 /// <param name="move">The move to play.</param>
 /// <param name="isLightPlayer">The player to play the move.</param>
 /// <returns>A MinimaxSpot matrix that represents the insight state.</returns>
 protected abstract MinimaxSpot[,] GetInsight(MinimaxSpot[,] state, MinimaxMove move, bool isLightPlayer);
Exemple #8
0
        /// <summary>
        /// Returns the best move resulting from a Minimax, alpha-beta pruning search on the given state.
        /// </summary>
        /// <param name="state">The state to consider.</param>
        /// <param name="isLightPlayer">The player to move.</param>
        /// <param name="inParallel">A boolean indicating whether to use the parallel algorithm.</param>
        /// <returns>A MinimaxMove that represents the best move found.</returns>
        /// <remarks>
        /// This method will only return a MinimaxMove(-1...) if there are no valid moves.
        /// </remarks>
        public MinimaxMove Search(MinimaxSpot[,] state, bool isLightPlayer, bool inParallel)
        {
            // Initialize a bunch of state.
            m_maxDepth = MaxDepth == -1 ? Int32.MaxValue : MaxDepth;
            m_degOfParallelism = DegreeOfParallelism;
            m_timeLimit = TimeLimit;
            m_taskCount = 0;
            m_movesConsidered = 0;
            var curCts = m_cts = new CancellationTokenSource();
            m_ct = m_cts.Token;

            MinimaxMove aiMove = new MinimaxMove(-1, -1, null);

            // Start the timeout timer.  Done using a dedicated thread to minimize delay 
            // in cancellation due to lack of threads in the pool to run the callback.
            var timeoutTask = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(m_timeLimit);
                curCts.Cancel();
            }, TaskCreationOptions.LongRunning);

            // Do the search
            aiMove = inParallel ?
                InternalSearchTPL(state, isLightPlayer, Int32.MinValue, Int32.MaxValue, 0, CancellationToken.None) :
                InternalSearch(state, isLightPlayer, Int32.MinValue, Int32.MaxValue, 0);

            // Make sure that MinimaxMove(-1...) is only returned if there are no valid moves, because
            // InternalSearch* may return MinimaxMove(-1...) if none of the valid moves beats Int32.Min/Max.
            if (aiMove.Row == -1)
            {
                foreach (var move in GetValidMoves(state, isLightPlayer))
                {
                    aiMove = move;
                    aiMove.Value = isLightPlayer ? Int32.MinValue : Int32.MaxValue;
                    break;
                }
            }

            return aiMove;
        }
Exemple #9
0
        /// <summary>
        /// Should only be called through the public Search method.
        /// </summary>
        /// <param name="state">The game state to consider.</param>
        /// <param name="isLightPlayer">The player to move.</param>
        /// <param name="alpha">The alpha pruning value.</param>
        /// <param name="beta">The beta pruning value.</param>
        /// <param name="depth">The current search depth.</param>
        /// <param name="token">The pruning token.</param>
        /// <returns>A MinimaxMove that represents the best move found.</returns>
        /// <remarks>
        /// The initial alpha value should be Int32.MinValue, the initial beta value 
        /// should be Int32.MaxValue, the initial depth value should be 0, and the 
        /// initial token should be a non-settable token.
        /// 
        /// The search will terminate ASAP if the m_ct cancellation token is signaled.
        /// 
        /// This method is thread-safe.
        /// </remarks>
        private MinimaxMove InternalSearchTPL(MinimaxSpot[,] state, bool isLightPlayer, int alpha, int beta, int depth, CancellationToken token)
        {
            // Stop the search if...
            if (TerminalTest(state) || depth >= m_maxDepth || m_ct.IsCancellationRequested)
            {
                m_movesConsidered++; // NOTE: this is racy and may be lower than the actual count, but it only needs to be an appx
                return new MinimaxMove(EvaluateHeuristic(state));
            }

            // Initialize the best move for this recursive call.
            MinimaxMove bestMove = new MinimaxMove(isLightPlayer ? Int32.MinValue : Int32.MaxValue);

            // Get the valid moves for this recursive call.
            IEnumerable<MinimaxMove> validMoves = GetValidMoves(state, isLightPlayer);

            bool consideredLocalMoves = false;
            Queue<Task> workers = new Queue<Task>();
            object bigLock = new object();
            CancellationTokenSource cts = new CancellationTokenSource();
            foreach (MinimaxMove move in validMoves)
            {
                // SHARED STATE
                //     The local variables (bestMove, alpha, beta) are protected by a lock.
                //     The non-local variables (m_taskCount) are modified using Interlocked
                consideredLocalMoves = true;

                // If the pruning token is signaled, stop this loop.
                if (token.IsCancellationRequested)
                {
                    cts.Cancel();
                    break;
                }

                MinimaxMove curMove = move;
                if (m_taskCount < m_degOfParallelism && depth <= m_maxDepth - 1)
                {
                    Interlocked.Increment(ref m_taskCount);
                    workers.Enqueue(Task.Factory.StartNew(() =>
                    {
                        curMove.Value = InternalSearchTPL(GetInsight(state, curMove, isLightPlayer), !isLightPlayer, alpha, beta, depth + 1, cts.Token).Value;
                        lock (bigLock)
                        {
                            if (isLightPlayer)
                            {
                                if (curMove.Value > bestMove.Value) bestMove = curMove;
                                if (bestMove.Value >= beta) cts.Cancel();
                                alpha = Math.Max(alpha, bestMove.Value.Value);
                            }
                            else
                            {
                                if (curMove.Value < bestMove.Value) bestMove = curMove;
                                if (bestMove.Value <= alpha) cts.Cancel();
                                beta = Math.Min(beta, bestMove.Value.Value);
                            }
                        }
                        Interlocked.Decrement(ref m_taskCount);
                    }));
                }
                else
                {
                    bool isPruning = false;
                    curMove.Value = InternalSearchTPL(GetInsight(state, curMove, isLightPlayer), !isLightPlayer, alpha, beta, depth + 1, cts.Token).Value;

                    // If there are no tasks, no need to lock.
                    bool lockTaken = false;
                    try
                    {
                        if (workers.Count > 0) Monitor.Enter(bigLock, ref lockTaken);
                        if (isLightPlayer)
                        {
                            if (curMove.Value > bestMove.Value) bestMove = curMove;
                            if (bestMove.Value >= beta) isPruning = true;
                            alpha = Math.Max(alpha, bestMove.Value.Value);
                        }
                        else
                        {
                            if (curMove.Value < bestMove.Value) bestMove = curMove;
                            if (bestMove.Value <= alpha) isPruning = true;
                            beta = Math.Min(beta, bestMove.Value.Value);
                        }
                    }
                    finally { if (lockTaken) Monitor.Exit(bigLock); }

                    if (isPruning)
                    {
                        cts.Cancel();
                        break;
                    }
                }
            }

            Task.WaitAll(workers.ToArray());

            // If there were no valid moves, still calculate the value.
            if (!consideredLocalMoves)
            {
                bestMove.Value = InternalSearchTPL(state, !isLightPlayer, alpha, beta, depth + 1, token).Value;
            }

            return bestMove;
        }
Exemple #10
0
        private void GoAI()
        {
            if (m_isGameOver)
            {
                return;
            }

            m_isAIMoving = true;

            m_progBarAnim = new DoubleAnimation(0.0, 100.0, m_progBarDuration);
            if (m_isAIParallel)
            {
                ui_parProgBar.Visibility = Visibility.Visible;
                ui_parProgBar.BeginAnimation(ProgressBar.ValueProperty, m_progBarAnim, HandoffBehavior.SnapshotAndReplace);
            }
            else
            {
                ui_seqProgBar.Visibility = Visibility.Visible;
                ui_seqProgBar.BeginAnimation(ProgressBar.ValueProperty, m_progBarAnim, HandoffBehavior.SnapshotAndReplace);
            }

            m_aiUICts  = new CancellationTokenSource();
            m_aiUITask = Task.Factory.StartNew(() =>
            {
                return(m_game.GetAIMove(m_isAIParallel));
            }, m_aiUICts.Token, TaskCreationOptions.None, TaskScheduler.Default)
                         .ContinueWith(completedTask =>
            {
                MinimaxMove aiMove = completedTask.Result;
                if (aiMove.Row != -1)
                {
                    m_game.MakeMove(aiMove.Row, aiMove.Col);
                }
                else
                {
                    m_game.PassMove();
                }

                string s;
                if (m_isAIParallel)
                {
                    s = String.Format("{0:N}", m_game.MovesConsidered);
                    s = s.Substring(0, s.Length - 3);
                    ui_parLabel.Content      = s;
                    ui_parProgBar.Visibility = Visibility.Hidden;
                }
                else
                {
                    s = String.Format("{0:N}", m_game.MovesConsidered);
                    s = s.Substring(0, s.Length - 3);
                    ui_seqLabel.Content      = s;
                    ui_seqProgBar.Visibility = Visibility.Hidden;
                }

                UpdateBoard();
                m_isAIMoving = false;

                if (m_isAuto)
                {
                    m_isAIParallel = !m_isAIParallel;
                    GoAI();
                }
            }, m_aiUICts.Token, TaskContinuationOptions.None, m_UIScheduler);
        }