// runs an iterative deepening minimax search limited by the given timeLimit public Move IterativeDeepening(State state, double timeLimit) { int depth = 1; Stopwatch timer = new Stopwatch(); Move bestMove = null; // start the search timer.Start(); while (true) { if (timer.ElapsedMilliseconds > timeLimit) { if (bestMove == null) // workaround to overcome problem with timer running out too fast with low limits { timeLimit += 10; timer.Reset(); timer.Start(); } else { return bestMove; } } Tuple<Move, Boolean> result = IterativeDeepeningAlphaBeta(state, depth, Double.MinValue, Double.MaxValue, timeLimit, timer); if (result.Item2) bestMove = result.Item1; // only update bestMove if full recursion depth++; } }
public void changeState(State newState) { currentState.Exit(this); currentState = newState; currentState.Enter(this); }
public Node(Move move, Node parent, State state) { this.random = new Random(); this.state = state; this.generatingMove = move; this.parent = parent; this.results = 0; this.visits = 0; this.children = new List<Node>(); this.untriedMoves = state.GetMoves(); }
void Start() { audio.volume = Data.SfxVol%.75f; state = State.waiting; currentText = new System.Text.StringBuilder(); currentPage = 0; currentLetter = 0; pageStrings = new string[pages.Length]; for (int i = 0; i < pages.Length;i++ ) { pageStrings[i] = pages[i].text; } }
// Runs a Monte Carlo Tree Search limited by a given time limit public Node TimeLimited(State rootState, int timeLimit, Stopwatch timer) { Node rootNode = new Node(null, null, rootState); while (true) { if (timer.ElapsedMilliseconds > timeLimit) { if (FindBestChild(rootNode.Children) == null && !rootNode.state.IsGameOver()) { timeLimit += 10; timer.Reset(); timer.Start(); } else { return rootNode; } } Node node = rootNode; State state = rootState.Clone(); // 1: Select while (node.UntriedMoves.Count == 0 && node.Children.Count != 0) { node = node.SelectChild(); state = state.ApplyMove(node.GeneratingMove); } // 2: Expand if (node.UntriedMoves.Count != 0) { Move randomMove = node.UntriedMoves[random.Next(0, node.UntriedMoves.Count)]; state = state.ApplyMove(randomMove); node = node.AddChild(randomMove, state); } // 3: Simulation while (state.GetMoves().Count != 0) { state = state.ApplyMove(state.GetRandomMove()); } // 4: Backpropagation while (node != null) { node.Update(state.GetResult()); node = node.Parent; } } }
// 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) { Stopwatch timer = new Stopwatch(); Node bestNode = null; while (bestNode == null && !rootState.IsGameOver()) { timer.Start(); Node rootNode = TimeLimited(rootState, timeLimit, timer); bestNode = FindBestChild(rootNode.Children); timeLimit += 10; timer.Reset(); } return bestNode; }
// Iterative Deepening Expectimax search public Move IterativeDeepening(State state, double timeLimit) { int depth = 1; Stopwatch timer = new Stopwatch(); Move bestMove = null; // start the search timer.Start(); while (timer.ElapsedMilliseconds < timeLimit) { Tuple<Move, Boolean> result = IterativeDeepeningExpectimax(state, depth, timeLimit, timer); if (result.Item2) bestMove = result.Item1; // only update bestMove if full recursion depth++; } return bestMove; }
// recursive part of the minimax algorithm when used in iterative deepening search // checks at each recursion if timeLimit has been reached // if is has, it cuts of the search and returns the best move found so far, along with a boolean indicating that the search was not fully completed private Tuple<Move, Boolean> IterativeDeepeningAlphaBeta(State state, int depth, double alpha, double beta, double timeLimit, Stopwatch timer) { Move bestMove; if (depth == 0 || state.IsGameOver()) { if (state.Player == GameEngine.PLAYER) { bestMove = new PlayerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) // AI's turn { bestMove = new PlayerMove(); double highestScore = Double.MinValue, currentScore = Double.MinValue; List<Move> moves = state.GetMoves(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = IterativeDeepeningAlphaBeta(resultingState, depth - 1, alpha, beta, timeLimit, timer).Item1.Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } alpha = Math.Max(alpha, highestScore); if (beta <= alpha) { // beta cut-off 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 if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); double lowestScore = Double.MaxValue, currentScore = Double.MaxValue; List<Move> moves = state.GetMoves(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = IterativeDeepeningAlphaBeta(resultingState, depth - 1, alpha, beta, timeLimit, timer).Item1.Score; if (currentScore < lowestScore) { lowestScore = currentScore; bestMove = move; } beta = Math.Min(beta, lowestScore); if (beta <= alpha) break; 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); } else throw new Exception(); }
void Update() { if (state == State.waiting) { if (start) { Data.Paused = true; Data.PauseEnabled = false; state = State.displaying; pageChars=pageStrings[currentPage].ToCharArray(); style.fontSize = (int)(Screen.width * fontsize[currentPage]); style2.fontSize = (int)(Screen.width * fontsize[currentPage]); } } else if (state == State.displaying) { if (CustomInput.PauseFreshPress) { for (int i = Mathf.RoundToInt(currentLetter); i < pageChars.Length;i++ ) currentText.Append(pageChars[i]); state = State.paused; } else { if (currentLetter == 0) currentText.Append(pageChars[(int)currentLetter]); int a = (int)(currentLetter); float temp = textSpeed; if (CustomInput.AcceptHeld) temp *= 2; currentLetter += temp * Time.deltaTime; if (a < (int)currentLetter && currentLetter < pageChars.Length) { currentText.Append(pageChars[(int)currentLetter]); audio.PlayOneShot(audio.clip); } if (currentLetter >= pageChars.Length) { //currentText = new System.Text.StringBuilder(pageStrings[currentPage]); state = State.paused; } } } else { if (CustomInput.AcceptFreshPress || CustomInput.PauseFreshPress) { currentLetter = 0; currentPage++; if (currentPage >= pages.Length) { Data.Paused = false; Data.PauseEnabled = true; CustomInput.voidPause(); Destroy(this.gameObject); } else { pageChars = pageStrings[currentPage].ToCharArray(); currentText = new System.Text.StringBuilder(); state = State.displaying; style.fontSize = (int)(Screen.width * fontsize[currentPage]); style2.fontSize = (int)(Screen.width * fontsize[currentPage]); } } } }
// 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) { Node child = new Node(move, this, state); this.untriedMoves.Remove(move); this.children.Add(child); return child; }
// Applies the move to this state and returns the resulting state public State ApplyMove(Move move) { if (move is PlayerMove) { int[][] clonedBoard = BoardHelper.CloneBoard(this.board); if (((PlayerMove)move).Direction == DIRECTION.LEFT) { State state = ApplyLeft(clonedBoard); state.GeneratingMove = move; return state; } else if (((PlayerMove)move).Direction == DIRECTION.RIGHT) { State state = ApplyRight(clonedBoard); state.GeneratingMove = move; return state; } else if (((PlayerMove)move).Direction == DIRECTION.DOWN) { State state = ApplyDown(clonedBoard); state.GeneratingMove = move; return state; } else if (((PlayerMove)move).Direction == DIRECTION.UP) { State state = ApplyUp(clonedBoard); state.GeneratingMove = move; return state; } else throw new Exception(); } else if (move is ComputerMove) { State result = new State(BoardHelper.CloneBoard(this.board), points, GameEngine.PLAYER); int xPosition = ((ComputerMove)move).Position.Item1; int yPosition = ((ComputerMove)move).Position.Item2; int tileValue = ((ComputerMove)move).Tile; result.Board[xPosition][yPosition] = tileValue; result.GeneratingMove = move; return result; } else { throw new Exception(); } }
private State ApplyUp(int[][] clonedBoard) { List<Cell> merged = new List<Cell>(); for (int i = 0; i < clonedBoard.Length; i++) { for (int j = clonedBoard.Length - 1; j >= 0; j--) { if (BoardHelper.CellIsOccupied(clonedBoard, i, j) && j < 3) { int k = j; while (k < 3 && !BoardHelper.CellIsOccupied(clonedBoard, i, k + 1)) { int value = clonedBoard[i][k]; clonedBoard[i][k] = 0; clonedBoard[i][k + 1] = value; k = k + 1; } if (k < 3 && BoardHelper.CellIsOccupied(clonedBoard, i, k + 1) && !BoardHelper.TileAlreadyMerged(merged, i, k) && !BoardHelper.TileAlreadyMerged(merged, i, k + 1)) { // check if we can merge the two tiles if (clonedBoard[i][k] == clonedBoard[i][k + 1]) { int value = clonedBoard[i][k + 1] * 2; clonedBoard[i][k + 1] = value; merged.Add(new Cell(i, k + 1)); clonedBoard[i][k] = 0; points += value; } } } } } State result = new State(clonedBoard, this.points, GameEngine.COMPUTER); return result; }
private State ApplyRight(int[][] clonedBoard) { List<Cell> merged = new List<Cell>(); for (int j = 0; j < board.Length; j++) { for (int i = board.Length - 1; i >= 0; i--) { if (BoardHelper.CellIsOccupied(clonedBoard, i, j) && i < 3) { int k = i; while (k < 3 && !BoardHelper.CellIsOccupied(clonedBoard, k + 1, j)) { int value = clonedBoard[k][j]; clonedBoard[k][j] = 0; clonedBoard[k + 1][j] = value; k = k + 1; } if (k < 3 && BoardHelper.CellIsOccupied(clonedBoard, k + 1, j) && !BoardHelper.TileAlreadyMerged(merged, k, j) && !BoardHelper.TileAlreadyMerged(merged, k + 1, j)) { // check if we can merge the two tiles if (clonedBoard[k][j] == clonedBoard[k + 1][j]) { int value = clonedBoard[k + 1][j] * 2; clonedBoard[k + 1][j] = value; merged.Add(new Cell(k + 1, j)); clonedBoard[k][j] = 0; points += value; } } } } } State result = new State(clonedBoard, this.points, GameEngine.COMPUTER); return result; }
// Checks if two states are equal public bool Equals(State s) { if ((object)s == null) { return false; } for (int i = 0; i < GameEngine.ROWS; i++) { for (int j = 0; j < GameEngine.COLUMNS; j++) { if (board[i][j] != s.board[i][j]) return false; } } return true; }
// Recursive part of iterative deepening Expectimax private Tuple<Move, Boolean> IterativeDeepeningExpectimax(State state, int depth, double timeLimit, Stopwatch timer) { Move bestMove; if (depth == 0 || state.IsGameOver()) { if (state.Player == GameEngine.PLAYER) { bestMove = new PlayerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) // AI's turn { bestMove = new PlayerMove(); double highestScore = Double.MinValue, currentScore = Double.MinValue; List<Move> moves = state.GetMoves(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = IterativeDeepeningExpectimax(resultingState, depth - 1, timeLimit, timer).Item1.Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } 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 if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); // return the weighted average of all the child nodes's scores double average = 0; List<Cell> availableCells = state.GetAvailableCells(); List<Move> moves = state.GetAllComputerMoves(availableCells); int moveCheckedSoFar = 0; foreach (Move move in moves) { State resultingState = state.ApplyMove(move); average += StateProbability(((ComputerMove)move).Tile) * IterativeDeepeningExpectimax(resultingState, depth - 1, timeLimit, timer).Item1.Score; moveCheckedSoFar++; if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = average / moveCheckedSoFar; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } } bestMove.Score = average / moves.Count; return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); }