public static void AIMinMaxSearchAsyncBegin(this IGameBoardState gbs, int searchLevels, Player.PlayerNumber currentPlayer) { if (jobStatus != AIMinMaxJobStatus.None) { throw new System.Exception("Only 1 async job should run at a time. This must be enforced in the game logic."); } jobStatus = AIMinMaxJobStatus.Started; LevelsSearched = 0; StatesSearched = 0; MovesSearched = 0; Debug.Log("AI Thread Started"); //clone gameboardstate to ensure it is not a MonoBehavior - we need to pass it to a new thread IGameBoardState gbs_clone = gbs.Clone(); jobThread = new Thread(() => { ///must run at least one level to complete jobResult = gbs_clone.AIMinMaxSearch(1, currentPlayer); LevelsSearched++; //code is in an iterative deepening pattern, but I is initialized to the deepest level for testing //usually i should start at 2 and iterate forward for (int i = searchLevels; i <= searchLevels; i++) { AIMinMaxResult res = gbs_clone.AIMinMaxSearch(i, currentPlayer); if (jobStatus == AIMinMaxJobStatus.StopRequested) { break; } LevelsSearched++; jobResult = res; } lock (jobLock) { jobStatus = AIMinMaxJobStatus.Finished; Debug.Log("AI Thread Finished"); } }); jobThread.IsBackground = true; jobThread.Start(); }
private IEnumerator MovePieceEvent() { AIMinMaxResult aiResult = null; Move nextMove = null; yield return(new WaitForSeconds(1f)); //render the gameboard to the 2d representation uiController.RenderBoard(gameBoard); while (true) { foreach (Player p in players) { currentPlayer = p; currentPlayer.playerState = Player.PlayerState.Thinking; StartCoroutine(uiController.FadeText(uiController.PlayerTurnText, "Go " + currentPlayer.playerNumber.ToString() + "!", currentPlayer.PieceTint)); if (currentPlayer.playerType == Player.PlayerType.Human) { while (currentPlayer.playerState == Player.PlayerState.Thinking) { yield return(new WaitForSeconds(MoveTime)); if (currentPlayer.playerType == Player.PlayerType.Computer) { break; } } if (currentPlayer.playerType == Player.PlayerType.Human) { nextMove = currentPlayer.selectedMove; } } if (currentPlayer.playerType == Player.PlayerType.Computer) { //start thinking asyncronously gameBoard.AIMinMaxSearchAsyncBegin(currentPlayer.AIThoughtDepth + 1, currentPlayer.playerNumber); while (AIMinMax.jobStatus == AIMinMaxJobStatus.Started || AIMinMax.jobStatus == AIMinMaxJobStatus.StopRequested) { yield return(new WaitForSeconds(MoveTime)); if (AIMinMax.jobStatus == AIMinMaxJobStatus.Started && AIMinMax.StatesSearched > MaxStatesToSearch) { gameBoard.AiMinMaxSearchAsyncStopRequest(); } } //finish thinking and get the move aiResult = gameBoard.AIMinMaxSearchAsyncEnd(); lastResult = aiResult; nextMove = aiResult.Move; gameBoard.GetPiece(nextMove.piece).ZoomToPiece(); yield return(new WaitForSeconds(1f)); currentPlayer.selectedMove = aiResult.Move; currentPlayer.playerState = Player.PlayerState.Moving; } //this is the best place to stop if the game should be paused while (Paused) { yield return(new WaitForSeconds(MoveTime)); } Piece movedPiece = gameBoard.GetPiece(nextMove.piece); gameBoard.Move(nextMove); movedPiece.ZoomToPiece(true); //render the gameboard to the 2d representation uiController.RenderBoard(gameBoard); yield return(new WaitForSeconds(2f)); currentPlayer.playerState = Player.PlayerState.Waiting; if (CheckGameOver()) { yield break; } } } }
/// <summary> /// Recursive alpha beta pruning minimax search strategy. /// </summary> /// <param name="gbs">The Game Board State.</param> /// <param name="searchLevels">The number of levels to search (depth)</param> /// <param name="currentPlayer">The current player's turn.</param> /// <param name="BestMove">The best move found during the search</param> /// <param name="alpha">Starting alpha value.</param> /// <param name="beta">Starting beta value.</param> /// <returns></returns> public static AIMinMaxResult AIMinMaxSearch(this IGameBoardState gbs, int searchLevels, Player.PlayerNumber currentPlayer, bool QuiescenceSearch = false, double alpha = -1.0, double beta = 1.0) { Move BestMove = null; double checkWinner = gbs.CheckWinner(); //cutoff for search (recursive base cases) if (checkWinner == -1.0) { return(new AIMinMaxResult(BestMove, -1.0, 1)); } if (checkWinner == 1.0) { return(new AIMinMaxResult(BestMove, 1.0, 1)); } if (searchLevels == 0) { return(new AIMinMaxResult(BestMove, gbs.CalculateUtility(), 1)); } AIMinMaxResult result = null; long statesSearched = 0; //iterate by looking at all possible moves for each piece List <IPieceState> pieces = gbs.GetAlivePieces().Where(s => s.GetPlayer() == currentPlayer).Select(s => s).ToList(); foreach (IPieceState piece in pieces.Shuffle()) { List <Move> moves; if (QuiescenceSearch) { moves = MoveGenerator.GetCaptureMoves(gbs, piece); } else { moves = MoveGenerator.GetMoves(gbs, piece); } MovesSearched += moves.Count; //perform each move on a cloned board and search clone recursively, swapping players each turn foreach (Move move in moves.Shuffle()) { IGameBoardState clone = gbs.Clone(); clone.Move(move.piece, move.space); if (currentPlayer == Player.PlayerNumber.Player1) { result = clone.AIMinMaxSearch(searchLevels - 1, Player.PlayerNumber.Player2, true, alpha, beta); statesSearched += result.TotalStatesSearched; if (statesSearched > StatesSearched) { StatesSearched = statesSearched; } if (result.AlphaBeta > alpha) { alpha = result.AlphaBeta; BestMove = move; } //beta cut off if (beta <= alpha) { break; } } else /* (currentPlayer == Player.PlayerNumber.Player2) */ { result = clone.AIMinMaxSearch(searchLevels - 1, Player.PlayerNumber.Player1, true, alpha, beta); statesSearched += result.TotalStatesSearched; if (statesSearched > StatesSearched) { StatesSearched = statesSearched; } if (result.AlphaBeta < beta) { beta = result.AlphaBeta; BestMove = move; } //alpha cut off if (beta <= alpha) { break; } } if (jobStatus == AIMinMaxJobStatus.StopRequested && LevelsSearched > 0) { searchLevels = 1; } } if (jobStatus == AIMinMaxJobStatus.StopRequested && LevelsSearched > 0) { searchLevels = 1; } } //no moves found, treat as a base case if (BestMove == null) { return(new AIMinMaxResult(BestMove, gbs.CalculateUtility(), 1)); } //Create a result and return it return(new AIMinMaxResult(BestMove, result.AlphaBeta, statesSearched)); }