Ejemplo n.º 1
0
        // Runs an entire game using parallelized iterative deepening minimax
        public State RunParallelIterativeDeepening(bool print, int timeLimit)
        {
            bool gameOver = false;

            // update deck memory initially with observed cards on board
            this.UpdateDeckMemory(0, true);
            current = new State(game.currentState.Grid, GameEngine.PLAYER);
            int nextCard = game.nextCard;

            while (!gameOver)
            {
                if (print)
                {
                    Program.CleanConsole();
                    Console.Write(BoardHelper.ToString(current.Grid));
                }

                PlayerMove action = ((PlayerMove)ParallelIterativeDeepening(current, timeLimit, deck.Clone()));
                if (action.Direction != (DIRECTION)(-1))
                {
                    gameOver = game.SendUserAction(action);
                    current = new State(game.currentState.Grid, GameEngine.PLAYER);
                    if (nextCard > 0) this.UpdateDeckMemory(nextCard, false);
                    nextCard = game.nextCard;
                }
                else
                {
                    gameOver = true;
                    Program.CleanConsole();
                    Console.Write(BoardHelper.ToString(current.Grid));
                }
            }
            return current;
        }
Ejemplo n.º 2
0
        // Runs an entire game using MCTS limited by time
        public State RunTimeLimitedMCTS(bool print, int timeLimit)
        {
            // update deck memory initially with observed cards on board
            this.UpdateDeckMemory(0, true);
            int nextCard = this.gameEngine.nextCard;

            while (true)
            {
                currentState = new State(BoardHelper.CloneGrid(this.gameEngine.currentState.Grid), GameEngine.PLAYER);

                if (print)
                {
                    Program.CleanConsole();
                    Console.WriteLine(BoardHelper.ToString(currentState.Grid));
                }

                Node result = TimeLimitedMCTS(currentState, timeLimit, deck.Clone());

                if (result == null)
                {
                    // game over
                    return currentState;
                }
                gameEngine.SendUserAction((PlayerMove)result.GeneratingMove);

                // update deck memory only if the new card is not a bonus card (we know what value the new card has by keeping track of nextcard
                if (nextCard > 0) this.UpdateDeckMemory(nextCard, false);
                nextCard = this.gameEngine.nextCard;
            }
        }
Ejemplo n.º 3
0
 public Minimax(GameEngine game, int depth)
 {
     this.game = game;
     this.definedDepth = depth;
     this.current = new State(game.currentState.Grid, GameEngine.PLAYER);
     this.deck = new Deck();
 }
Ejemplo n.º 4
0
 public MCTS(GameEngine gameEngine)
 {
     this.gameEngine = gameEngine;
     this.deck = new Deck();
     this.currentState = new State(BoardHelper.CloneGrid(this.gameEngine.currentState.Grid), GameEngine.PLAYER);
     this.random = new Random();
 }
Ejemplo n.º 5
0
 public GameEngine()
 {
     deck = new Deck();
     int[][] grid = initializeGrid();
     currentState = new State(grid, COMPUTER);
     UpdatePeekCard();
 }
Ejemplo n.º 6
0
 public Node(Move move, Node parent, State state, Deck deck)
 {
     this.state = state;
     this.generatingMove = move;
     this.parent = parent;
     this.results = 0;
     this.visits = 0;
     this.children = new List<Node>();
     this.untriedMoves = state.GetMoves(deck);
 }
Ejemplo n.º 7
0
        // Returns the number of trapped cards in the game state
        // Use this heuristic as a penalty, i.e. subtract it
        public static double TrappedPenalty(State state)
        {
            double trapped = 0;

            for (int i = 0; i < GameEngine.COLUMNS; i++)
            {
                for (int j = 0; j < GameEngine.ROWS; j++)
                {
                    if (state.Grid[i][j] != 0)
                    {
                        // check neighbours in vertical direction
                        int neighbourRowAbove = j + 1;
                        int neighbourRowBelow = j - 1;
                        if (neighbourRowAbove < GameEngine.ROWS && j == 0) // trapped between wall below and higher card above
                        {
                            if (state.Grid[i][j] < 3 && state.Grid[i][neighbourRowAbove] >= 3)
                            {
                                trapped++;
                            }
                            else if (state.Grid[i][j] >= 3 && state.Grid[i][neighbourRowAbove] >= 3 && state.Grid[i][neighbourRowAbove] > state.Grid[i][j])
                            {
                                trapped++;
                            }
                        }
                        else if (neighbourRowBelow >= 0 && j == 3) // trapped between wall above and higher card below
                        {
                            if (state.Grid[i][j] < 3 && state.Grid[i][neighbourRowBelow] >= 3)
                            {
                                trapped++;
                            }
                            else if (state.Grid[i][j] >= 3 && state.Grid[i][neighbourRowBelow] >= 3 && state.Grid[i][neighbourRowBelow] > state.Grid[i][j])
                            {
                                trapped++;
                            }
                        }
                        else if (neighbourRowAbove < GameEngine.ROWS && neighbourRowBelow >= 0) // trapped between two higher cards
                        {
                            if (state.Grid[i][j] < 3 && state.Grid[i][neighbourRowBelow] >= 3 && state.Grid[i][neighbourRowAbove] >= 3)
                            {
                                trapped++;
                            }
                            else if (state.Grid[i][j] >= 3 && state.Grid[i][neighbourRowBelow] >= 3 && state.Grid[i][neighbourRowAbove] >= 3
                                && state.Grid[i][neighbourRowBelow] > state.Grid[i][j] && state.Grid[i][neighbourRowAbove] > state.Grid[i][j])
                            {
                                trapped++;
                            }
                        }

                        // check neighbours in horizontal direction
                        int neighbourColumnToRight = i + 1;
                        int neighbourColumnToLeft = i - 1;
                        if (neighbourColumnToRight < GameEngine.COLUMNS && i == 0) // trapped between wall to the left and higher card to the right
                        {
                            if (state.Grid[i][j] < 3 && state.Grid[neighbourColumnToRight][j] >= 3)
                            {
                                trapped++;
                            }
                            else if (state.Grid[i][j] >= 3 && state.Grid[neighbourColumnToRight][j] >= 3 && state.Grid[neighbourColumnToRight][j] > state.Grid[i][j])
                            {
                                trapped++;
                            }
                        }
                        else if (neighbourColumnToLeft >= 0 && i == 3) // trapped between wall to the right and higher card to the left
                        {
                            if (state.Grid[i][j] < 3 && state.Grid[neighbourColumnToLeft][j] >= 3)
                            {
                                trapped++;
                            }
                            else if (state.Grid[i][j] >= 3 && state.Grid[neighbourColumnToLeft][j] >= 3 && state.Grid[neighbourColumnToLeft][j] > state.Grid[i][j])
                            {
                                trapped++;
                            }
                        }
                        else if (neighbourColumnToRight < GameEngine.COLUMNS && neighbourColumnToLeft >= 0) // trapped between two higher cards
                        {
                            if (state.Grid[i][j] < 3 && state.Grid[neighbourColumnToRight][j] >= 3 && state.Grid[neighbourColumnToLeft][j] >= 3)
                            {
                                trapped++;
                            }
                            else if(state.Grid[i][j] >= 3 && state.Grid[neighbourColumnToRight][j] >= 3 && state.Grid[neighbourColumnToLeft][j] >= 3
                                && state.Grid[neighbourColumnToRight][j] > state.Grid[i][j] && state.Grid[neighbourColumnToLeft][j] > state.Grid[i][j])
                            {
                                trapped++;
                            }
                        }
                    }
                }
            }
            return trapped;
        }
Ejemplo n.º 8
0
 // Starts the time limited Monte Carlo Tree Search and returns the best child node
 // resulting from the search
 public Node TimeLimitedMCTS(State rootState, int timeLimit, Deck deck)
 {
     Stopwatch timer = new Stopwatch();
     Node bestNode = null;
     while (bestNode == null && !rootState.IsGameOver())
     {
         timer.Start();
         Node rootNode = TimeLimited(rootState, timeLimit, timer, deck);
         bestNode = FindBestChild(rootNode.Children);
         timeLimit += 10;
         timer.Reset();
     }
     return bestNode;
 }
Ejemplo n.º 9
0
 // Classic Minimax using alpha-beta pruning
 private Move MinimaxAlgorithm(State state, int depth, double alpha, double beta, Deck deck)
 {
     Move bestMove;
     if (depth == 0 || state.IsGameOver())
     {
         if (state.Player == GameEngine.PLAYER)
         {
             bestMove = new PlayerMove(); // default constructor creates dummy action
             bestMove.Score = AI.Evaluate(state);
             return bestMove;
         }
         else if (state.Player == GameEngine.COMPUTER)
         {
             bestMove = new ComputerMove(); // default constructor creates dummy action
             bestMove.Score = AI.Evaluate(state);
             return bestMove;
         }
         else
         {
             throw new Exception();
         }
     }
     if (state.Player == GameEngine.PLAYER)
         return Max(state, depth, alpha, beta);
     else
         return Min(state, depth, alpha, beta);
 }
Ejemplo n.º 10
0
        // MAX part of Minimax
        Move Max(State state, int depth, double alpha, double beta)
        {
            PlayerMove bestMove = new PlayerMove();
            double highestScore = Double.MinValue, currentScore = Double.MinValue;

            List<Move> moves = state.GetAllMoves();

            foreach (Move move in moves)
            {
                State resultingState = state.ApplyMove(move);
                currentScore = MinimaxAlgorithm(resultingState, depth - 1, alpha, beta, deck).Score;
                if (currentScore > highestScore)
                {
                    highestScore = currentScore;
                    bestMove = (PlayerMove)move;
                }
                alpha = Math.Max(alpha, highestScore);
                if (beta <= alpha)
                    break;
            }
            bestMove.Score = highestScore;
            return bestMove;
        }
Ejemplo n.º 11
0
        // Runs an entire game using classic Minimax to decide on moves
        internal State Run(bool print)
        {
            bool gameOver = false;

            // update deck memory initially with observed cards on board
            this.UpdateDeckMemory(0, true);
            current = new State(game.currentState.Grid, GameEngine.PLAYER);
            int nextCard = game.nextCard;

            while (!gameOver)
            {
                if (print)
                {
                    Program.CleanConsole();
                    Console.Write(BoardHelper.ToString(current.Grid));
                }

                PlayerMove action = ((PlayerMove)MinimaxAlgorithm(current, definedDepth, double.MinValue, double.MaxValue, deck.Clone()));
                if (action.Direction != (DIRECTION)(-1))
                {
                    gameOver = game.SendUserAction(action);
                    current = new State(game.currentState.Grid, GameEngine.PLAYER);
                    if(nextCard > 0) this.UpdateDeckMemory(nextCard, false);
                    nextCard = game.nextCard;
                }
                else
                {
                    gameOver = true;
                    Program.CleanConsole();
                    Console.Write(BoardHelper.ToString(current.Grid));
                }
            }
            return current;
        }
Ejemplo n.º 12
0
        // Evaluation function used by Minimax and Expectimax
        public static double Evaluate(State state)
        {
            if (state.IsGameOver()) return -1000;

            double smoothness = Smoothness(state);
            double monotonicity = Monotonicity(state);
            double emptycells = EmptyCells(state);
            double highestvalue = HighestCard(state);
            double trappedpenalty = TrappedPenalty(state);
            return smoothness_weight * smoothness + monotonicity_weight * monotonicity + emptycells_weight * emptycells + highestvalue_weight * highestvalue - trappedpenalty_weight * trappedpenalty;
        }
Ejemplo n.º 13
0
 // adds a child node to the list of children
 // after exploring a move - removes the move from untried
 public Node AddChild(Move move, State state, Deck deck)
 {
     Node child = new Node(move, this, state, deck);
     this.untriedMoves.Remove(move);
     this.children.Add(child);
     return child;
 }
Ejemplo n.º 14
0
        // Time limited MCTS
        private Node TimeLimited(State rootState, int timeLimit, Stopwatch timer, Deck deck)
        {
            Node rootNode = new Node(null, null, rootState, deck);

            while (true)
            {
                if (timer.ElapsedMilliseconds > timeLimit)
                {
                    if (FindBestChild(rootNode.Children) == null && !rootNode.state.IsGameOver())
                    {
                        timeLimit += 10;
                        timer.Restart();
                    }
                    else
                    {
                        return rootNode;
                    }

                }
                Node node = rootNode;
                State state = rootState.Clone();
                Deck clonedDeck = deck.Clone();

                // 1: Select
                while (node.UntriedMoves.Count == 0 && node.Children.Count != 0)
                {
                    node = node.SelectChild();
                    state = state.ApplyMove(node.GeneratingMove);
                    if (node.GeneratingMove is ComputerMove)
                    {
                        clonedDeck.Remove(((ComputerMove)node.GeneratingMove).Card);
                        if (clonedDeck.IsEmpty()) clonedDeck = new Deck();
                    }
                }

                // 2: Expand
                if (node.UntriedMoves.Count != 0)
                {
                    Move randomMove = node.UntriedMoves[random.Next(0, node.UntriedMoves.Count)];
                    if (randomMove is ComputerMove)
                    {
                        if (clonedDeck.IsEmpty()) clonedDeck = new Deck();
                        clonedDeck.Remove(((ComputerMove)randomMove).Card);
                        state = state.ApplyMove(randomMove);
                        node = node.AddChild(randomMove, state, clonedDeck);

                    }
                    else
                    {
                        state = state.ApplyMove(randomMove);
                        node = node.AddChild(randomMove, state, clonedDeck);
                    }
                }

                // 3: Simulation
                while (state.GetMoves(clonedDeck).Count != 0)
                {
                    Move move = state.GetRandomMove(clonedDeck);
                    if (move is ComputerMove)
                    {
                        if (clonedDeck.IsEmpty()) clonedDeck = new Deck();
                        clonedDeck.Remove(((ComputerMove)move).Card);
                        state = state.ApplyMove(move);
                    }
                    else
                    {
                        state = state.ApplyMove(move);
                    }
                }

                // 4: Backpropagation
                while (node != null)
                {
                    node.Update(state.GetResult());
                    node = node.Parent;
                }
            }
        }
Ejemplo n.º 15
0
        // Parallel time limited MCTS
        private DIRECTION ParallelTimeLimitedMCTS(State currentState, int timeLimit, Deck deck)
        {
            ConcurrentBag<Node> allChildren = new ConcurrentBag<Node>();
            int numOfChildren = currentState.GetMoves(deck).Count;

            Stopwatch timer = new Stopwatch();
            timer.Start();

            Parallel.For(0, NUM_THREADS, i =>
            {
                Node resultRoot = TimeLimited(currentState, timeLimit, timer, deck);
                foreach (Node child in resultRoot.Children)
                {
                    allChildren.Add(child);
                }
            });
            timer.Stop();

            List<int> totalVisits = new List<int>(4) { 0, 0, 0, 0 };
            List<double> totalResults = new List<double>(4) { 0, 0, 0, 0 };

            foreach (Node child in allChildren)
            {
                int direction = (int)((PlayerMove)child.GeneratingMove).Direction;
                totalVisits[direction] += child.Visits;
                totalResults[direction] += child.Results;
            }

            double best = Double.MinValue;
            int bestDirection = -1;
            for (int k = 0; k < 4; k++)
            {
                double avg = totalResults[k] / totalVisits[k];
                if (avg > best)
                {
                    best = avg;
                    bestDirection = k;
                }
            }
            if (bestDirection == -1) return (DIRECTION)(-1);
            return (DIRECTION)bestDirection;
        }
Ejemplo n.º 16
0
        // Arranges the tiles in a "snake"
        // As there are 8 different ways the tiles can be arranged in a "snake" on the grid, this method
        // finds the one that fits the best, allowing the AI to adjust to a different "snake" pattern
        public static double WeightSnake(State state)
        {
            double[][] snake1 = new double[][] {
                new double[]{20,9,4,.1},
                new double[]{19,10,3,0.2},
                new double[]{18,11,2,0.3},
                new double[]{17,12,1,0.4}
            };

            double[][] snake2 = new double[][] {
                new double[]{20,19,18,17},
                new double[]{9,10,11,12},
                new double[]{4,3,2,1},
                new double[]{0.1,0.2,0.3,0.4}
            };

            double[][] snake3 = new double[][]{
                new double[]{17,12,1,0.4},
                new double[]{18,11,2,0.3},
                new double[]{19,10,3,0.2},
                new double[]{20,9,4,0.1}
            };

            double[][] snake4 = new double[][] {
                new double[]{17,18,19,20},
                new double[]{12,11,10,9},
                new double[]{1,2,3,4},
                new double[]{0.4,0.3,0.2,0.1}
            };

            double[][] snake5 = new double[][] {
                new double[]{0.1,0.2,0.3,0.4},
                new double[]{4,3,2,1},
                new double[]{9,10,11,12},
                new double[]{20,19,18,17}
            };

            double[][] snake6 = new double[][] {
                new double[]{0.1,4,9,20},
                new double[]{0.2,3,10,19},
                new double[]{0.3,2,11,18},
                new double[]{0.4,1,12,17}
            };

            double[][] snake7 = new double[][] {
                new double[]{0.4,0.3,0.2,0.1},
                new double[]{1,2,3,4},
                new double[]{12,11,10,9},
                new double[]{17,18,19,20}
            };

            double[][] snake8 = new double[][] {
                new double[]{0.4,1,12,17},
                new double[]{0.3,2,11,18},
                new double[]{0.2,3,10,19},
                new double[]{0.1,4,9,20}
            };

            List<double[][]> weightMatrices = new List<double[][]>();
            weightMatrices.Add(snake1);
            weightMatrices.Add(snake2);
            weightMatrices.Add(snake3);
            weightMatrices.Add(snake4);
            weightMatrices.Add(snake5);
            weightMatrices.Add(snake6);
            weightMatrices.Add(snake7);
            weightMatrices.Add(snake8);

            return MaxProductMatrix(state.Grid, weightMatrices);
        }
Ejemplo n.º 17
0
 // Returns number of empty cells on board
 public static double EmptyCells(State state)
 {
     double numEptyCells = 0;
     for (int i = 0; i < GameEngine.COLUMNS; i++)
     {
         for (int j = 0; j < GameEngine.ROWS; j++)
         {
             if (state.Grid[i][j] == 0)
             {
                 numEptyCells++;
             }
         }
     }
     return numEptyCells;
 }
Ejemplo n.º 18
0
 // Returns the highest card on board
 public static double HighestCard(State state)
 {
     return BoardHelper.GetHighestCard(state.Grid);
 }
Ejemplo n.º 19
0
 // Iterative deepening minimax
 private Move IterativeDeepening(State state, int timeLimit, Deck deck)
 {
     int depth = 1;
     Stopwatch timer = new Stopwatch();
     Move bestMove = null;
     // start the search
     timer.Start();
     while (timer.ElapsedMilliseconds < timeLimit)
     {
         Tuple<Move, Boolean> result = RecursiveIterativeDeepening(state, depth, Double.MinValue, Double.MaxValue, timeLimit, timer, deck);
         if (result.Item2) bestMove = result.Item1; // only update bestMove if full recursion
         depth++;
     }
     return bestMove;
 }
Ejemplo n.º 20
0
        // Counts number of merges on board
        public static double Mergeability(State state)
        {
            double numMerges = 0;

            for (int i = 0; i < GameEngine.COLUMNS; i++)
            {
                for (int j = 0; j < GameEngine.ROWS; j++)
                {
                    // check merges horizontally
                    if (i + 1 < state.Grid.Length && state.Grid[i][j] != 0 && state.Grid[i + 1][j] != 0)
                    {
                        if (state.Grid[i][j] == 1 && state.Grid[i + 1][j] == 2
                            || state.Grid[i][j] == 2 && state.Grid[i + 1][j] == 1
                            || state.Grid[i][j] == state.Grid[i + 1][j])
                        {
                            if (i == 0) numMerges++;
                            else if (state.Grid[i - 1][j] != 0) numMerges++;
                        }

                    }
                    // check merges vertically
                    if (j + 1 < state.Grid.Length && state.Grid[i][j] != 0 && state.Grid[i][j + 1] != 0)
                    {
                        if (state.Grid[i][j] == 1 && state.Grid[i][j + 1] == 2
                            || state.Grid[i][j] == 2 && state.Grid[i][j + 1] == 1
                            || state.Grid[i][j] == state.Grid[i][j])
                        {
                            if (j == 0) numMerges++;
                            else if (state.Grid[i][j - 1] != 0) numMerges++;
                        }
                    }
                }
            }
            return numMerges;
        }
Ejemplo n.º 21
0
        // MIN part of Minimax
        Move Min(State state, int depth, double alpha, double beta)
        {
            ComputerMove bestMove = new ComputerMove();
            double lowestScore = Double.MaxValue, currentScore = Double.MaxValue;

            List<Move> moves = null;
            if (depth == definedDepth - 1)
            {
                int nextCard = game.nextCard;
                moves = state.GetAllComputerMoves(nextCard);
            }
            else
            {
                if (deck.IsEmpty()) deck = new Deck();
                moves = state.GetAllComputerMoves(deck);
            }

            foreach (Move move in moves)
            {
                deck.Remove(((ComputerMove)move).Card);
                State resultingState = state.ApplyMove(move);
                currentScore = MinimaxAlgorithm(resultingState, depth - 1, alpha, beta, deck).Score;
                if (currentScore < lowestScore)
                {
                    lowestScore = currentScore;
                    bestMove = (ComputerMove)move;
                }
                beta = Math.Min(beta, lowestScore);
                if (beta <= alpha)
                    break;
                deck.Add(((ComputerMove)move).Card);
            }
            bestMove.Score = lowestScore;
            return bestMove;
        }
Ejemplo n.º 22
0
        // returns a score ranking a state according to how the tiles are increasing/decreasing in all directions
        // increasing/decreasing in the same directions (for example generally increasing in up and right direction) will return higher score
        // than a state increasing in one row and decreasing in another row
        public static double Monotonicity(State state)
        {
            double left = 0;
            double right = 0;
            double up = 0;
            double down = 0;

            // up/down direction
            for (int i = 0; i < state.Grid.Length; i++)
            {
                int current = 0;
                int next = current + 1;
                while (next < state.Grid.Length)
                {
                    // skip empty cells
                    while (next < state.Grid.Length && state.Grid[i][next] == 0)
                        next++;
                    // check boundaries
                    if (next >= state.Grid.Length)
                        next--;

                    // only count instances where both cells are occupied
                    if (state.Grid[i][current] != 0 && state.Grid[i][next] != 0)
                    {
                        double currentValue = 0;
                        if (state.Grid[i][current] == 1 || state.Grid[i][current] == 2) currentValue = 1;
                        else currentValue = Math.Log(state.Grid[i][current] / 3) / Math.Log(2) + 2;

                        double nextValue  = 0;
                        if(state.Grid[i][next] == 1 || state.Grid[i][next] == 2) nextValue = 1;
                        else nextValue = Math.Log(state.Grid[i][next] / 3) / Math.Log(2) + 2;

                        if (currentValue > nextValue) // increasing in down direction
                            down += nextValue - currentValue;
                        else if (nextValue > currentValue) // increasing in up direction
                            up += currentValue - nextValue;
                    }

                    current = next;
                    next++;
                }
            }

            // left/right direction
            for (int j = 0; j < state.Grid.Length; j++)
            {
                int current = 0;
                int next = current + 1;
                while (next < state.Grid.Length)
                {
                    // skip empty cells
                    while (next < state.Grid.Length && state.Grid[next][j] == 0)
                        next++;
                    // check boundaries
                    if (next >= state.Grid.Length)
                        next--;

                    // only consider instances where both cells are occupied
                    if (state.Grid[current][j] != 0 && state.Grid[next][j] != 0)
                    {
                        double currentValue = 0;
                        if (state.Grid[current][j] == 1 || state.Grid[current][j] == 2) currentValue = 1;
                        else currentValue = Math.Log(state.Grid[current][j] / 3) / Math.Log(2) + 2;

                        double nextValue = 0;
                        if (state.Grid[next][j] == 1 || state.Grid[next][j] == 2) nextValue = 1;
                        else nextValue = Math.Log(state.Grid[next][j] / 3) / Math.Log(2) + 2;

                        if (currentValue > nextValue) // increasing in left direction
                            left += nextValue - currentValue;
                        else if (nextValue > currentValue) // increasing in right direction
                            right += currentValue - nextValue;
                    }

                    current = next;
                    next++;
                }
            }
            return Math.Max(up, down) + Math.Max(left, right);
        }
Ejemplo n.º 23
0
        // Runs an entire game using a parallelized version of iterative deepening minimax
        private Move ParallelIterativeDeepening(State state, int timeLimit, Deck deck)
        {
            Move bestMove = new PlayerMove();

            List<Move> moves = state.GetMoves(deck);
            ConcurrentBag<Tuple<double, Move>> scores = new ConcurrentBag<Tuple<double, Move>>();

            if (moves.Count == 0)
            {
                // game over
                return bestMove;
            }

            // create the resulting states before starting the threads
            List<State> resultingStates = new List<State>();
            foreach (Move move in moves)
            {
                State resultingState = state.ApplyMove(move);
                resultingStates.Add(resultingState);
            }

            Parallel.ForEach(resultingStates, resultingState =>
            {
                double score = IterativeDeepening(resultingState, timeLimit, deck.Clone()).Score;
                scores.Add(new Tuple<double, Move>(score, resultingState.GeneratingMove));
            });
            // find the best score
            double highestScore = Double.MinValue;
            foreach (Tuple<double, Move> score in scores)
            {
                PlayerMove move = (PlayerMove)score.Item2;
                if (score.Item1 > highestScore)
                {
                    highestScore = score.Item1;
                    bestMove = score.Item2;
                }
            }
            return bestMove;
        }
Ejemplo n.º 24
0
        // Arranges tiles in a corner and prefers higher cards along edges
        public static double Corner(State state)
        {
            double[][] corner1 = new double[][] {
                new double[]{20,12,4,0.4},
                new double[]{19,11,3,0.3},
                new double[]{18,10,2,0.2},
                new double[]{17,9,1,0.1}
            };

            double[][] corner2 = new double[][] {
                new double[]{0.4,4,12,20},
                new double[]{0.3,3,11,19},
                new double[]{0.2,2,10,18},
                new double[]{0.1,1,9,17}
            };

            double[][] corner3 = new double[][] {
                new double[]{17,9,1,0.1},
                new double[]{18,10,2,0.2},
                new double[]{19,11,3,0.3},
                new double[]{20,12,4,0.4}
            };

            double[][] corner4 = new double[][] {
                new double[]{0.1,1,9,17},
                new double[]{0.2,2,10,18},
                new double[]{0.3,3,11,19},
                new double[]{0.4,4,12,20}
            };

            double[][] corner5 = new double[][] {
                new double[]{20,19,18,17},
                new double[]{12,11,10,9},
                new double[]{4,3,2,1},
                new double[]{0.4,0.3,0.2,0.1}
            };

            double[][] corner6 = new double[][] {
                new double[]{17,18,19,20},
                new double[]{9,10,11,12},
                new double[]{1,2,3,4},
                new double[]{0.1,0.2,0.3,0.4}
            };

            double[][] corner7 = new double[][] {
                new double[]{0.4,0.3,0.2,0.1},
                new double[]{4,3,2,1},
                new double[]{12,11,10,9},
                new double[]{20,19,18,17}
            };

            double[][] corner8 = new double[][] {
                new double[]{0.1,0.2,0.3,0.4},
                new double[]{1,2,3,4},
                new double[]{9,10,11,12},
                new double[]{17,18,19,20}
            };
            List<double[][]> weightMatrices = new List<double[][]>();

            weightMatrices.Add(corner1);
            weightMatrices.Add(corner2);
            weightMatrices.Add(corner3);
            weightMatrices.Add(corner4);
            weightMatrices.Add(corner5);
            weightMatrices.Add(corner6);
            weightMatrices.Add(corner7);
            weightMatrices.Add(corner8);

            return MaxProductMatrix(state.Grid, weightMatrices);
        }
Ejemplo n.º 25
0
        // Recursive part of iterative deepening
        private Tuple<Move, bool> RecursiveIterativeDeepening(State state, int depth, double alpha, double beta, int timeLimit, Stopwatch timer, Deck deck)
        {
            Move bestMove;
            if (depth == 0 || state.IsGameOver())
            {
                if (state.Player == GameEngine.PLAYER)
                {
                    bestMove = new PlayerMove(); // default constructor creates dummy action
                    bestMove.Score = AI.Evaluate(state);
                    return new Tuple<Move, Boolean>(bestMove, true);
                }
                else if (state.Player == GameEngine.COMPUTER)
                {
                    bestMove = new ComputerMove(); // default constructor creates dummy action
                    bestMove.Score = AI.Evaluate(state);
                    return new Tuple<Move, Boolean>(bestMove, true);
                }
                else
                {
                    throw new Exception();
                }
            }
            if (state.Player == GameEngine.PLAYER){

                bestMove = new PlayerMove();
                double highestScore = Double.MinValue, currentScore = Double.MinValue;

                List<Move> moves = state.GetAllMoves();

                foreach (Move move in moves)
                {
                    State resultingState = state.ApplyMove(move);
                    currentScore = RecursiveIterativeDeepening(resultingState, depth - 1, alpha, beta, timeLimit, timer, deck).Item1.Score;
                    if (currentScore > highestScore)
                    {
                        highestScore = currentScore;
                        bestMove = (PlayerMove)move;
                    }
                    alpha = Math.Max(alpha, highestScore);
                    if (beta <= alpha)
                        break;
                    if (timer.ElapsedMilliseconds > timeLimit)
                    {
                        bestMove.Score = highestScore;
                        return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false
                    }
                }

                bestMove.Score = highestScore;
                return new Tuple<Move, Boolean>(bestMove, true);
            }
            else
            {
                bestMove = new ComputerMove();
                double lowestScore = Double.MaxValue, currentScore = Double.MaxValue;

                List<Move> moves = null;
                if (depth == definedDepth - 1)
                {
                    int nextCard = game.nextCard;
                    moves = state.GetAllComputerMoves(nextCard);
                }
                else
                {
                    if (deck.IsEmpty()) deck = new Deck();
                    moves = state.GetAllComputerMoves(deck);
                }

                foreach (Move move in moves)
                {
                    deck.Remove(((ComputerMove)move).Card);
                    State resultingState = state.ApplyMove(move);
                    currentScore = RecursiveIterativeDeepening(resultingState, depth - 1, alpha, beta, timeLimit, timer, deck).Item1.Score;
                    if (currentScore < lowestScore)
                    {
                        lowestScore = currentScore;
                        bestMove = (ComputerMove)move;
                    }
                    beta = Math.Min(beta, lowestScore);
                    if (beta <= alpha)
                        break;
                    deck.Add(((ComputerMove)move).Card);
                    if (timer.ElapsedMilliseconds > timeLimit)
                    {
                        bestMove.Score = lowestScore;
                        return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false
                    }
                }
                bestMove.Score = lowestScore;
                return new Tuple<Move, Boolean>(bestMove, true);
            }
        }
Ejemplo n.º 26
0
 // Returns the point score of the given game state
 public static double Points(State state)
 {
     return state.CalculateFinalScore();
 }
Ejemplo n.º 27
0
 // Applies the move and returns the result state
 internal State ApplyMove(Move move)
 {
     if (move is PlayerMove)
     {
         State result = new State(BoardHelper.CloneGrid(this.grid), GameEngine.COMPUTER);
         if (((PlayerMove)move).Direction == DIRECTION.LEFT)
         {
             return result.ApplyLeft();
         }
         else if (((PlayerMove)move).Direction == DIRECTION.RIGHT)
         {
             return result.ApplyRight();
         }
         else if (((PlayerMove)move).Direction == DIRECTION.UP)
         {
             return result.ApplyUp();
         }
         else if (((PlayerMove)move).Direction == DIRECTION.DOWN)
         {
             return result.ApplyDown();
         }
         return result;
     }
     else if (move is ComputerMove)
     {
         State result = new State(BoardHelper.CloneGrid(this.grid), GameEngine.PLAYER);
         result.grid[((ComputerMove)move).Position.Item1][((ComputerMove)move).Position.Item2] = ((ComputerMove)move).Card;
         result.generatingMove = (ComputerMove)move;
         return result;
     }
     else
     {
         throw new Exception();
     }
 }
Ejemplo n.º 28
0
        // Ranks the state according to how smooth it is
        // Smoothness prefers cards of similar values next to each other
        public static double Smoothness(State state)
        {
            double smoothness = 0;
            for (int i = 0; i < state.Grid.Length; i++)
            {
                for (int j = 0; j < state.Grid.Length; j++)
                {
                    if (state.Grid[i][j] != 0)
                    {
                        double currentValue = 0;
                        if (state.Grid[i][j] == 1 || state.Grid[i][j] == 2) currentValue = 1;
                        else currentValue = Math.Log(state.Grid[i][j] / 3) / Math.Log(2) + 2;

                        // we only check right and up for each tile
                        Cell nearestTileRight = FindNearestCard(new Cell(i, j), DIRECTION.RIGHT, state.Grid);
                        Cell nearestTileUp = FindNearestCard(new Cell(i, j), DIRECTION.UP, state.Grid);

                        // check that we found a tile (do not take empty cells into account)
                        if (nearestTileRight.IsValid() && state.Grid[nearestTileRight.x][nearestTileRight.y] != 0)
                        {
                            double neighbourValue = 0;
                            if (state.Grid[nearestTileRight.x][nearestTileRight.y] == 1 || state.Grid[nearestTileRight.x][nearestTileRight.y] == 2) neighbourValue = 1;
                            else neighbourValue = Math.Log(state.Grid[nearestTileRight.x][nearestTileRight.y] / 3) / Math.Log(2) + 2;
                            smoothness += Math.Abs(currentValue - neighbourValue);
                        }

                        if (nearestTileUp.IsValid() && state.Grid[nearestTileUp.x][nearestTileUp.y] != 0)
                        {
                            double neighbourValue = 0;
                            if (state.Grid[nearestTileUp.x][nearestTileUp.y] == 1 || state.Grid[nearestTileUp.x][nearestTileUp.y] == 2) neighbourValue = 1;
                            else neighbourValue = Math.Log(state.Grid[nearestTileUp.x][nearestTileUp.y] / 3) / Math.Log(2) + 2;
                            smoothness += Math.Abs(currentValue - neighbourValue);
                        }
                    }
                }
            }
            return -smoothness;
        }