public void TTboardCheckWin() { TTTBoard board = new TTTBoard(3); TTTBoard newBoard = board.Clone(); Assert.AreEqual(Player.NONE, board.CheckWin(), "Check Win game in progres 1"); board.Move(0, 0, Player.PLAYERO); board.Move(0, 1, Player.PLAYERX); Assert.AreEqual(Player.NONE, board.CheckWin(), "Check Win game in progres 2"); board.Move(1, 0, Player.PLAYERO); board.Move(0, 2, Player.PLAYERX); Assert.AreEqual(Player.NONE, board.CheckWin(), "Check Win game in progres 3"); board.Move(2, 0, Player.PLAYERO); Assert.AreEqual(Player.PLAYERO, board.CheckWin(), "Check Win should be game winner 4"); }
public void TTboardCheckWin2() { TTTBoard board = new TTTBoard(3); TTTBoard newBoard = board.Clone(); Assert.AreEqual(Player.NONE, board.CheckWin(), "Check Win game in progres 1"); board.Move(0, 0, Player.PLAYERO); board.Move(0, 1, Player.PLAYERO); Assert.AreEqual(Player.NONE, board.CheckWin(), "Check Win game in progres 2"); board.Move(1, 0, Player.PLAYERX); board.Move(1, 1, Player.PLAYERX); Assert.AreEqual(Player.NONE, board.CheckWin(), "Check Win game in progres 3"); board.Move(1, 2, Player.PLAYERO); board.Move(0, 2, Player.PLAYERX); Assert.AreEqual(Player.NONE, board.CheckWin(), "heck Win game in progres 4"); board.Move(2, 0, Player.PLAYERO); board.Move(2, 1, Player.PLAYERO); board.Move(2, 2, Player.PLAYERX); Assert.AreEqual(Player.DRAW, board.CheckWin(), "heck Win game DRAW"); }
//Takes in the board, the type of symbol (X, O) to simulate being placed, the level of recursion, // a reference to the outcome to return, a check to say if it completed or was interrupted, and a previous best for AlphaBeta pruning private IEnumerator GetBestMovesRecursive(TTTBoard board, SquareState toPlace, int functionDepth, MoveScore returnOutcome, BoolCheck bc, int previousBest) { statesSampled += 1; //Outcome determines if the end result is a win, loss or tie. //It gets set to be the state we expect to get overriden, with 1 meaning a win and -1 meaning a loss //Because we expect the AI to only make moves that improve its odds of winning, we initialize it to -2 if it's the AI's turn //That way, it will get overriden immediately //The opposite is true for if it's the player's turn int outcome = ((toPlace == playingAs) ? -2 : 2); //Estimate of how long before the end of the game int movesUntilEndgame = 0; //Used to determine if the rest of the loop is garbage, due to alpha-beta pruning //The reason this is used as opposed to break; is to ensure that the list of move choices is kept consistent //The queue should be unaltered once the function passes to a higher recursion bool skipRestOfLoop = false; for (int i = 0; i < moveChoices.Count; ++i) { //For if alpha-beta determines all proceeding tests to be obsolete if (skipRestOfLoop) { //cycles through the queue until it returns to its starting arrangement moveChoices.Enqueue(moveChoices.Dequeue()); continue; } //Take a move from the queue to test Vector2Int testMove = moveChoices.Dequeue(); //Checks if the square is worth testing according to symmetry pruning - if it's not, skip the square and continue if (SymmetryPrune) { if (board.board[testMove.x][testMove.y].CheckIfCulled()) { moveChoices.Enqueue(testMove); culledStates += 1; continue; } } //Sets the square of the simulated board board.SetSquareState(testMove, toPlace); MoveScore moveScore = new MoveScore(0, 0); SquareState isVictory = board.CheckWin(); if (isVictory == SquareState.Empty) { //Nobody one, continue simulation if possible if (moveChoices.Count > 0) { //Set move score, which indicates (GameResult, GameLength). //Passes the current best outcome to conduct AlphaBeta BoolCheck newBc = new BoolCheck(); IEnumerator getReturn = GetBestMovesRecursive(board, (toPlace == SquareState.O) ? SquareState.X : SquareState.O, functionDepth + 1, moveScore, newBc, outcome); StartCoroutine(getReturn); //Do not continue until the inner function has succeeded while (!newBc.complete) { yield return(new WaitForEndOfFrame()); } //If moveScore is marked as invalid, it means to prune it //Move to the next iteration if (moveScore.invalid) { moveChoices.Enqueue(testMove); board.SetSquareState(testMove, SquareState.Empty); continue; } //Keeps track of how long the game will last, with the leaf node starting at 1 and counting up moveScore.depth += 1; } else { //Tie moveScore = new MoveScore(0, 1); } } else if (isVictory == playingAs) { //you won moveScore = new MoveScore(1, 1); } else { //Opponent won moveScore = new MoveScore(-1, 1); } moveChoices.Enqueue(testMove); board.SetSquareState(testMove, SquareState.Empty); if (AlphaBetaPrune && functionDepth > 0) { //AlphaBeta //The root function should not alpha-beta prune, only receive a series of possible moves if (playingAs == toPlace) { //If it's simulating your move and you can do better than the current best, your opponent will move to stop it //Mark the return as invalid to tell the outer function to skip it if (previousBest < moveScore.outcome) { culledStates += 1; returnOutcome.invalid = true; skipRestOfLoop = true; continue; } } else { //Opposite logic for opponent's turn if (previousBest > moveScore.outcome) { culledStates += 1; returnOutcome.invalid = true; skipRestOfLoop = true; continue; } } } if (playingAs == toPlace) { //If the move proposes a better result, then we overwrite our existing best move and options //If the result is net positive, we want a smaller MovesUntilEndgame. If net negative, we'll take the bigger. //If it's a tie, we don't care due to the fact that all tie games take the same amount of moves if (moveScore.outcome > outcome) { outcome = moveScore.outcome; movesUntilEndgame = moveScore.depth; if (functionDepth == 0) { viableChoices.Clear(); Debug.Log("VIABLE CHOICE: " + testMove + " AT DEPTH " + moveScore.depth + " RESULTING IN BETTER SCORE OF " + moveScore.outcome); viableChoices.Enqueue(testMove); } } else if (moveScore.outcome == outcome) { //Delay the game if losing, quicken the game if winning if (outcome > 0) { if (moveScore.depth < movesUntilEndgame) { movesUntilEndgame = moveScore.depth; if (functionDepth == 0) { viableChoices.Clear(); Debug.Log("VIABLE CHOICE: " + testMove + " AT DEPTH " + moveScore.depth + " RESULTING IN SCORE OF " + moveScore.outcome); viableChoices.Enqueue(testMove); } } else if (moveScore.depth == movesUntilEndgame) { if (functionDepth == 0) { Debug.Log("VIABLE CHOICE: " + testMove + " AT DEPTH " + moveScore.depth + " RESULTING IN SCORE OF " + moveScore.outcome); viableChoices.Enqueue(testMove); } } } else { if (moveScore.depth > movesUntilEndgame) { movesUntilEndgame = moveScore.depth; if (functionDepth == 0) { viableChoices.Clear(); Debug.Log("VIABLE CHOICE: " + testMove + " AT DEPTH " + moveScore.depth + " RESULTING IN SCORE OF " + moveScore.outcome); viableChoices.Enqueue(testMove); } } else if (moveScore.depth == movesUntilEndgame) { if (functionDepth == 0) { Debug.Log("VIABLE CHOICE: " + testMove + " AT DEPTH " + moveScore.depth + " RESULTING IN SCORE OF " + moveScore.outcome); viableChoices.Enqueue(testMove); } } } } } else { //Invert the checks during the Opponent's turn //The recursive function always starts on the AI's turn, so we can remove the isRoot checks during the opponent's logic if (moveScore.outcome < outcome) { outcome = moveScore.outcome; movesUntilEndgame = moveScore.depth; } else if (moveScore.outcome == outcome) { //Delay the game if losing, quicken the game if winning - but for the Opponent if (outcome < 0) { if (moveScore.depth < movesUntilEndgame) { movesUntilEndgame = moveScore.depth; } } else { if (moveScore.depth > movesUntilEndgame) { movesUntilEndgame = moveScore.depth; } } } } } //Return the best options we saved returnOutcome.outcome = outcome; returnOutcome.depth = movesUntilEndgame; //forces the program to stop for now, if the coroutine is taking too long to respond if (NonBlocking && (System.DateTime.Now - currentTime).Milliseconds >= millisecondsPerCycle) { yield return(new WaitForEndOfFrame()); currentTime = System.DateTime.Now; } //Tell the outer function that you have finished bc.complete = true; //Choose a square from the viable options once all the functions have completed if (functionDepth == 0) { isDoneMoving = true; currentTime = System.DateTime.Now; int toChoose = Random.Range(0, viableChoices.Count); for (int i = 0; i < toChoose; ++i) { viableChoices.Enqueue(viableChoices.Dequeue()); } chosenMove = viableChoices.Peek(); } }