Ejemplo n.º 1
0
        public Board(int gridWidth, int gridHeight, IBoardDrawer drawer)
        {
            this.drawer = drawer;

            const int AIPlayer = 0;
            const int humanPlayer = 1 - AIPlayer;

            // Test seeds:
            seed = 1092552428;
            //seed = 2053617222;
            //seed = new Random().Next();

            players[AIPlayer] = new AIPlayer(AIPlayer, this);
            players[humanPlayer] = new HumanPlayer(humanPlayer, this);

            grid = new Grid(gridWidth, gridHeight, seed);

            grid.Move(3, 0);
            grid.Move(3, 1);
            grid.Move(3, 0);
            grid.Move(3, 1);
            grid.Move(3, 0);

            grid.Move(2, 1);
        }
Ejemplo n.º 2
0
 public override void BeginMove(Grid grid)
 {
     board.Move(logReader.GetNextMove(), player);
 }
Ejemplo n.º 3
0
 public virtual void BeginMove(Grid grid)
 {
 }
Ejemplo n.º 4
0
        private void PrintMoveStatistics(double runtime, Grid grid, SearchResult result,
            int[] scores, int[] scoreTypes)
        {
            Debug.Assert(scores.Length == scoreTypes.Length);

            log.WriteLine("Done");

            // Print the grid before the AI's move to the log file.
            log.WriteLineToLog();
            log.WriteLineToLog("Grid before AI move:");
            log.WriteLineToLog(grid.ToString());
            log.WriteLineToLog();

            // Print node statistics.
            double cutoffsOnFirstChild = betaCutoffsOnFirstChild * 100D / betaCutoffs;
            double cutoffsOnOrderedChildren = betaCutoffsOnOrderedChildren * 100D / betaCutoffs;
            log.Write("Analysed ");
            WriteWithColor("{0:N0}", ConsoleColor.White, totalNodesSearched);
            log.WriteLine(" states, including {0:N0} end states.", endNodesSearched);
            log.WriteLine("   Searched {0:N0} pv-nodes, {1:N0} cut-nodes and {2:N0} all-nodes.",
                pvNodes, cutNodes, allNodes);
            log.WriteLine("   {0:N0} cutoffs from lookups.", alphaBetaCutoffs);
            log.WriteLine("   {0:N0} beta cutoffs ({1:N2}% on first child, {2:N2}% on "
                + "ordered children).", betaCutoffs, cutoffsOnFirstChild,
                cutoffsOnOrderedChildren);

            // Print runtime statistics.
            double nodesPerMillisecond = Math.Round(totalNodesSearched / runtime, 4);
            string minutes = (runtime > 60000)
                ? String.Format(" ({0:N2} minutes)", runtime / 60000.0) : "";
            log.Write("Runtime {0:N} ms{1} (", runtime, minutes);
            WriteWithColor("{0:N}", ConsoleColor.White, nodesPerMillisecond);
            log.WriteLine(" states / ms).");
            log.WriteLine("   Total runtime {0:N} ms.", totalRuntime);

            // Print the scores of each valid move.
            log.Write("The move scores are");
            for (int i = 0; i < scores.Length; i++)
            {
                if (grid.IsValidMove(i))
                {
                    log.Write("  {0}:", i);

                    if (scoreTypes[i] == -1)
                    {
                        log.Write("-");
                    }

                    else
                    {
                        if (scores[i] == infinity)
                        {
                            log.WriteToLog("+\u221E");
                            Console.ForegroundColor = ConsoleColor.Green;
                            Console.Write("+Inf");
                            Console.ResetColor();
                        }

                        else if (scores[i] == -infinity)
                        {
                            log.WriteToLog("-\u221E");
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.Write("-Inf");
                            Console.ResetColor();
                        }

                        else
                        {
                            log.Write("{0}", scores[i]);
                        }

                        switch (scoreTypes[i])
                        {
                            case NodeTypeExact:
                                log.Write("E");
                                break;
                            case NodeTypeLower:
                                log.Write("L");
                                break;
                            case NodeTypeUpper:
                                log.Write("U");
                                break;
                        }
                    }
                }
            }

            log.WriteLine();

            // Print the score of the chosen move.
            if (result.score > 0)
            {
                WriteLineWithColor("   AI will win latest on move {0}.",
                    ConsoleColor.Green, result.GetDepth());
            }
            else if (result.score < 0)
            {
                WriteLineWithColor("   AI will lose on move {0} (assuming perfect play).",
                    ConsoleColor.Red, result.GetDepth());
            }
            else
            {
                log.WriteLine("   Move score is {0}.", result.score);
            }

            log.WriteLine("   Move is {0:N0}.", finalMove);
            log.WriteLine("Correct/Incorrect guesses: {0:N0}/{1:N0}", correctGuesses, incorrectGuesses);

            // Print transposition table statistics.
            log.WriteLine();
            log.WriteLine("Transposition table:");
            log.WriteLine("   Size:                     {0:N0}", TranspositionTable.TableSize);
            log.WriteLine("   Memory Space:             {0:N0} MB",
                TranspositionTable.MemorySpaceBytes / 1024 / 1024);
            log.WriteLine("   Shallow Lookups:          {0:N0}", shallowTableLookups);
            log.WriteLine("   Lookups:                  {0:N0}", tableLookups);
            log.WriteLine("   Requests:                 {0:N0}",
                transpositionTable.Requests);
            log.WriteLine("   Insertions:               {0:N0}",
                transpositionTable.Insertions);
            log.WriteLine("   Collisions:               {0:N0}",
                transpositionTable.Collisions);
            log.WriteLine("   Items:                    {0:N0} ({1:N4}% full)",
                transpositionTable.Size, 100.0 * transpositionTable.Size
                / TranspositionTable.TableSize);
            transpositionTable.ResetStatistics();

            log.WriteLine();
            log.WriteLine();
        }
Ejemplo n.º 5
0
        public override void BeginMove(Grid grid)
        {
            // Run CalculateNextMove in a worker thread, then call MakeMove in the
            // main thread.
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(CalculateNextMove);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(MakeMove);

            worker.RunWorkerAsync(grid);
        }
Ejemplo n.º 6
0
        private void PerftHelper(Grid grid, int depth, int maxDepth, int[] results,
            HashSet<ulong> visitedStates)
        {
            int player = depth & 1;

            if (depth >= maxDepth || grid.IsGameOver(player)
                || visitedStates.Contains(grid.Hash))
            {
                return;
            }

            results[depth]++;
            visitedStates.Add(grid.Hash);

            for (int i = 0; i < grid.Width; i++)
            {
                if (grid.IsValidMove(i))
                {
                    grid.Move(i, player);
                    PerftHelper(grid, depth + 1, maxDepth, results, visitedStates);
                    grid.UndoMove(i, player);
                }
            }
        }
Ejemplo n.º 7
0
        private SearchResult Negamax(int currentDepth, Grid state, int alpha, int beta,
            bool allowNullWindow = true)
        {
            int currentPlayer = currentDepth & 1;
            int originalAlpha = alpha;

            totalNodesSearched++;
            
            ulong validMovesMask = state.GetValidMovesMask();
            ulong playerThreats = state.GetThreats(currentPlayer);

            // If the player can win on this move then return immediately,
            // no more evaluation is needed.
            ulong currentPlayerThreats = validMovesMask & playerThreats;
            if (currentPlayerThreats != 0)
            {
                endNodesSearched++;

                if (currentDepth == moveNumber)
                {
                    finalMove = GetFirstColumnOfThreatBoard(currentPlayerThreats);
                }

                return new SearchResult(infinity, currentDepth);
            }

            ulong opponentThreats = state.GetThreats(1 - currentPlayer);

            // If the opponent could win in one move on this position, then the
            // current player must play that move instead.
            ulong currentOpponentThreats = validMovesMask & opponentThreats;
            if (currentOpponentThreats != 0)
            {
                int forcedMove = GetFirstColumnOfThreatBoard(currentOpponentThreats);

                if (currentDepth == moveNumber)
                {
                    finalMove = forcedMove;
                }

                // Remove the forced move from the threat board.
                currentOpponentThreats &= ~(0x3FUL << (forcedMove * (state.Height + 1)));

                // If there is another threat return the loss immediately, otherwise
                // play the forced move.
                if (currentOpponentThreats != 0)
                {
                    endNodesSearched++;

                    return new SearchResult(-infinity, currentDepth + 1);
                }

                // Take the single forced move.
                state.Move(forcedMove, currentPlayer);
                SearchResult childResult
                    = -Negamax(currentDepth + 1, state, -beta, -alpha, allowNullWindow);
                state.UndoMove(forcedMove, currentPlayer);
                
                return childResult;
            }

            // This must be a draw if there are no forced moves and only two
            // moves left in the game.
            if (currentDepth >= maxMoves - 1)
            {
                endNodesSearched++;

                return new SearchResult(0, currentDepth);
            }

            // Will be set to the best move of the lookup.
            int entryBestMove = -1;

            // Check if the position or the flipped position should be used for the lookup.
            ulong hash = Math.Min(state.Hash, state.FlippedHash);
            bool usingFlippedPosition = hash != state.Hash;

            // Check if this state has already been visited.
            ulong entry;
            if (transpositionTable.Lookup(hash, out entry))
            {
                // If the state has been visited, then return immediately or
                // improve the alpha and beta values depending on the node type.
                tableLookups++;

                // The score is stored in bits 8 to 15 of the entry.
                int encoded = (int)(((entry >> 8) & 0xFF) - 128);
                SearchResult lookupResult = new SearchResult { score = encoded };

                // The type is stored in bits 6 to 7 of the entry.
                int entryType = (int)((entry >> 6) & 0x3);
                
                // Flip the best move if necessary.
                entryBestMove = (int)((entry >> 16) & 0x7);
                if (usingFlippedPosition)
                {
                    entryBestMove = 6 - entryBestMove;
                }

                switch (entryType)
                {
                    // If the score is exact, there is no need to check anything
                    // else, so return the score of the entry.
                    case NodeTypeExact:
                        if (currentDepth == moveNumber)
                        {
                            finalMove = entryBestMove;
                        }
                        return lookupResult;

                    // If the entry score is an upper bound on the actual score,
                    // see if the current upper bound can be reduced.
                    case NodeTypeUpper:
                        beta = Math.Min(beta, lookupResult.GetValue());
                        break;

                    // If the entry score is a lower bound on the actual score,
                    // see if the current lower bound can be increased.
                    case NodeTypeLower:
                        alpha = Math.Max(alpha, lookupResult.GetValue());
                        break;
                }

                // At this point alpha or beta may have been improved, so check if
                // this is a cuttoff.
                if (alpha >= beta)
                {
                    alphaBetaCutoffs++;

                    if (currentDepth == moveNumber)
                    {
                        finalMove = entryBestMove;
                    }

                    return lookupResult;
                }
            }

            // A bitmap where a 1 means the corresponding move has already been
            // checked. Initialised with a 1 at all invalid moves.
            ulong checkedMoves = state.GetInvalidMovesMask();

            SearchResult result = new SearchResult { score = int.MinValue };

            int bestMove = -1;
            int index = -1;
            bool isFirstChild = true;

            // If no parent of this position has tried a null-window search yet,
            // try guessing the score of this position.
            if (allowNullWindow)
            {
                int guess = (currentPlayer == 0)
                    ? GuessScore(0, playerThreats, opponentThreats)
                    : GuessScore(1, opponentThreats, playerThreats);

                // Check if there is a guess for this position.
                if (guess != 0)
                {
                    int nullAlpha;
                    int nullBeta;
                    int expectedScore;

                    // If this is likely to be a win, search this position with a
                    // null window centered at +infinity.
                    if (guess == 1)
                    {
                        nullAlpha = infinity;
                        nullBeta = infinity + 1;
                        expectedScore = infinity;
                    }

                    // If this is likely to be a loss.
                    else
                    {
                        nullAlpha = -infinity - 1;
                        nullBeta = -infinity;
                        expectedScore = -infinity;
                    }

                    allowNullWindow = false;
                    SearchResult nullResult = Negamax(
                            currentDepth, state, nullAlpha, nullBeta, allowNullWindow);

                    // If the guess is correct, return immediately.
                    if (nullResult.GetValue() == expectedScore)
                    {
                        correctGuesses++;
                        return nullResult;
                    }

                    incorrectGuesses++;
                }
            }

            // Find the best move recursively.
            // checkMoves will be equal to bottomRow when there are no more valid
            // moves.
            while (checkedMoves != Grid.bottomRow)
            {
                int move = GetNextMove(ref index, ref checkedMoves, entryBestMove,
                    validMovesMask, playerThreats);
                if (isFirstChild)
                {
                    bestMove = move;
                }

                // Apply the move and recurse.
                state.Move(move, currentPlayer);
                SearchResult childResult
                    = -Negamax(currentDepth + 1, state, -beta, -alpha, allowNullWindow);
                state.UndoMove(move, currentPlayer);

                if (childResult.score > result.score)
                {
                    result = childResult;
                    bestMove = move;

                    if (result.GetValue() > alpha)
                    {
                        alpha = result.GetValue();
                        // Check if this a cutoff.
                        if (alpha >= beta)
                        {
                            betaCutoffs++;
                            if (isFirstChild)
                            {
                                betaCutoffsOnFirstChild++;
                            }
                            if (index <= 0)
                            {
                                betaCutoffsOnOrderedChildren++;
                            }

                            break;
                        }
                    }
                }

                isFirstChild = false;
            }

            Debug.Assert(bestMove != -1);

            // Determine the type of node, so the score can be used correctly
            // after a lookup.
            int flag;
            if (result.score <= originalAlpha)
            {
                flag = NodeTypeUpper;
                allNodes++;
            }
            else if (result.score >= beta)
            {
                flag = NodeTypeLower;
                cutNodes++;
            }
            else
            {
                flag = NodeTypeExact;
                pvNodes++;
            }

            // Store the score in the t-table in case the same state is reached later.
            int moveToStore = (usingFlippedPosition) ? 6 - bestMove : bestMove;
            transpositionTable.Add(currentDepth, moveToStore, hash, result.score, flag);

            if (currentDepth == moveNumber)
            {
                finalMove = bestMove;
            }

            return result;
        }
Ejemplo n.º 8
0
        public void Perft()
        {
            Grid grid = new Grid(7, 6, 0);

            const int maxDepth = 10;

            int[] results = new int[maxDepth];
            int[] expected = new int[] { 1, 7, 49, 238, 1120, 4263, 16422,
            54859, 184275, 558186, 1662623, 4568683 };

            HashSet<ulong> visitedStates = new HashSet<ulong>();

            PerftHelper(grid, 0, maxDepth, results, visitedStates);

            for (int i = 0; i < maxDepth; i++)
            {
                Assert.AreEqual(expected[i], results[i]);
            }
        }
Ejemplo n.º 9
0
        public void UndoMoveTest()
        {
            for (int player = 0; player < 2; player++)
            {
                Grid referenceGrid = new Grid(width, height, seed);
                Grid testGrid = new Grid(width, height, seed);

                for (int y = 0; y < height; y++)
                {
                    for (int move = 0; move < width; move++)
                    {
                        referenceGrid.Move(move, player);
                        testGrid.Move(move, player);
                        testGrid.UndoMove(move, player);
                        testGrid.Move(move, player);

                        Assert.IsTrue(testGrid.Equals(referenceGrid));
                        Assert.AreEqual(testGrid.Hash, referenceGrid.Hash);
                    }
                }
            }
        }
Ejemplo n.º 10
0
        public void MoveTestPerPlayer()
        {
            for (int player = 0; player < 2; player++)
            {
                Grid grid = new Grid(width, height, seed);
                TileState expectedState = (TileState)player;
                for (int row = 0; row < height; row++)
                {
                    for (int column = 0; column < width; column++)
                    {
                        grid.Move(column, player);

                        // Test that only the expected tiles are filled.
                        for (int testrow = 0; testrow < height; testrow++)
                        {
                            for (int testcolumn = 0; testcolumn < width; testcolumn++)
                            {
                                if (testrow < row || (testrow == row && testcolumn <= column))
                                {
                                    Assert.AreEqual(expectedState, grid[testrow, testcolumn]);
                                }
                                else
                                {
                                    Assert.AreEqual(TileState.Empty, grid[testrow, testcolumn]);
                                }
                            }
                        }
                    }
                }
            }
        }
Ejemplo n.º 11
0
        public void MoveTestAlternatingPlayer()
        {
            Grid grid = new Grid(width, height, seed);
            int currentPlayer = 0;

            for (int row = 0; row < height; row++)
            {
                for (int column = 0; column < width; column++)
                {
                    grid.Move(column, currentPlayer);
                    currentPlayer = 1 - currentPlayer;

                    // Test that only the expected tiles are filled with the correct player.
                    for (int testrow = 0; testrow < height; testrow++)
                    {
                        for (int testcolumn = 0; testcolumn < width; testcolumn++)
                        {
                            if (testrow < row || (testrow == row && testcolumn <= column))
                            {
                                // Note: if size is an odd number then the expression below
                                // must be (testcolumn + testrow) % 2.
                                TileState expectedState = (TileState)(testcolumn % 2);
                                Assert.AreEqual(expectedState, grid[testrow, testcolumn]);
                            }
                            else
                            {
                                Assert.AreEqual(TileState.Empty, grid[testrow, testcolumn]);
                            }
                        }
                    }
                }
            }
        }
Ejemplo n.º 12
0
        public void IsGameOverVerticalTest()
        {
            Grid grid = new Grid(width, height, seed);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(0, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(0, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(0, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(0, 0);
            Assert.AreEqual(0, grid.IsGameOver());
            Assert.AreEqual(true, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));

            grid = new Grid(width, height, seed);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(width - 1, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(width - 1, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(width - 1, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(width - 1, 1);
            Assert.AreEqual(1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(true, grid.IsGameOver(1));

            grid = new Grid(width, height, seed);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(3, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(4, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(3, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(4, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(3, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(4, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(4, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(3, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(0, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(1, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(2, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(5, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(1, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(5, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(1, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(5, 1);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(1, 0);
            Assert.AreEqual(-1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(false, grid.IsGameOver(1));
            grid.Move(5, 1);
            Assert.AreEqual(1, grid.IsGameOver());
            Assert.AreEqual(false, grid.IsGameOver(0));
            Assert.AreEqual(true, grid.IsGameOver(1));
        }
Ejemplo n.º 13
0
        public void HashAndFlippedHashTestEqualOnSymmetricBoard()
        {
            Grid grid = new Grid(7, 6, 0);

            grid.Move(3, 0);
            grid.Move(3, 1);
            grid.Move(3, 0);
            grid.Move(3, 1);
            grid.Move(3, 0);
            grid.Move(3, 1);

            Assert.AreEqual(grid.Hash, grid.FlippedHash);
        }
Ejemplo n.º 14
0
 public Player(string name, Grid.Colors colour)
 {
     this.Name = name;
     this.colour = colour;
 }