// Runs an entire game using a parallelized version of MCTS limited by time public State RunParallelTimeLimitedMCTS(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)); } DIRECTION result = ParallelTimeLimitedMCTS(currentState, timeLimit, deck.Clone()); PlayerMove move = new PlayerMove(); move.Direction = result; if (result == (DIRECTION)(-1)) { // game over return currentState; } gameEngine.SendUserAction(move); // 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; } }
// Returns a list of all player moves private List<Move> GetAllPlayerMoves() { List<Move> moves = new List<Move>(); foreach (DIRECTION direction in Enum.GetValues(typeof(DIRECTION))) { if (IsValidMove(direction)) { PlayerMove move = new PlayerMove(direction); moves.Add(move); } } return moves; }
// 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); } }
// 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; }
// 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); }
// 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; }
// Starts a game for the user to play private static void StartGame() { GameEngine game = new GameEngine(); bool gameOver = false; CleanConsole(); // main game loop while (!gameOver) { CleanConsole(); String nextCard = ""; if (game.nextCard == -1) nextCard = "BONUS CARD"; else nextCard = game.nextCard.ToString(); Console.WriteLine("Next card: " + nextCard); Console.WriteLine(BoardHelper.ToString(game.currentState.Grid)); DIRECTION action = GetUserInput(); PlayerMove move = new PlayerMove(action); gameOver = game.SendUserAction(move); } CleanConsole(); Console.WriteLine("Next card: "); Console.WriteLine(BoardHelper.ToString(game.currentState.Grid)); Console.WriteLine("GAME OVER! Final score: " + game.currentState.CalculateFinalScore()); Console.ReadLine(); // to avoid console closing immediately }
// Takes a player action and executes it, updates peek card and // generates a new random tile public bool SendUserAction(PlayerMove action) { currentState = currentState.ApplyMove(action); // only continue game if action was valid (if something moved on the grid) if (currentState.columnsOrRowsWithMovedTiles.Count != 0) { GenerateNewCard(); if (CheckForGameOver()) { return true; } UpdatePeekCard(); } return false; }