public void GetPossibleMovesOneMoveMade() { TTTBoard board = new TTTBoard(); board.MakeMove(new TTTMove(1, 2)); Assert.AreEqual(8, board.PossibleMoves().Count); }
//Debugging purposes protected void DebugBoard(TTTBoard gameBoard) { for (int i = 0; i < 3; ++i) { string line = ""; for (int j = 0; j < 3; ++j) { switch (gameBoard.board[j][i].state) { case TTT.SquareState.Empty: line += " ___ "; break; default: line += " "; line += gameBoard.board[j][i].state; line += " "; break; } } Debug.Log(line); } }
public void InvariantTests0() { TTTBoard board = new TTTBoard(3); List <int[]> squares = board.GetEmptySquares(); int count = squares.Count; Assert.AreEqual(9, count, "Empty squares"); }
public override IEnumerator GetDecision(TTTBoard board) { currentTime = System.DateTime.Now; isDoneMoving = false; //Reset previous data anticipatedResult = new MoveScore(0, 0); statesSampled = 0; culledStates = 0; moveChoices.Clear(); viableChoices.Clear(); //Copy the board to avoid affecting the real one TTTBoard testBoard = new TTTBoard(board); //Get all free squares - the player may cheat, but the AI does not testBoard.PopulateMoveQueue(moveChoices); //Error checking for if we try to edit a board that's complete if (moveChoices.Count > 0) { chosenMove = moveChoices.Peek(); } else { UnityEngine.Debug.LogError("TRYING TO PLAY ON ALREADY COMPLETE BOARD"); isDoneMoving = true; chosenMove = Vector2Int.one * -1; } //Allows us to check whether or not the following coroutine has finished, by saving a reference to a bool BoolCheck newBc = new BoolCheck(); StartCoroutine(GetBestMovesRecursive(testBoard, playingAs, 0, anticipatedResult, newBc, 2)); //Keep waiting for previous functions to complete before moving on while (!newBc.complete) { yield return(new WaitForEndOfFrame()); } //Debug info Debug.Log("Computer player " + playingAs.ToString() + " processed " + statesSampled + " unique states, " + "best case scenario: " + anticipatedResult.outcome + " after " + anticipatedResult.depth + " turns"); string tot = ""; for (int i = 0; i < viableChoices.Count; ++i) { Vector2Int vChoice = viableChoices.Dequeue(); viableChoices.Enqueue(vChoice); tot += vChoice; tot += " "; } Debug.Log("Choices: " + tot); }
public override IEnumerator GetDecision(TTTBoard board) { isDoneMoving = false; //Keep doing the coroutine until a decision has been made while (!IsDoneMoving) { yield return(new WaitForEndOfFrame()); hoverPiece.gameObject.SetActive(false); //reset alpha value if (lastHovered != null) { lastHovered.mr.material.color = new Color(1, 1, 1, 0); } //check the mouse's raycast into the scene and test if its colliding with a TicTacToe square //Also set the hovering piece to that location Ray ray = mainCam.ScreenPointToRay(Input.mousePosition); RaycastHit rch; if (Physics.Raycast(ray, out rch, 1000f, catchMouseLayer)) { //If it hits a square, set the player's hovering game piece over that square hoverPiece.gameObject.SetActive(true); hoverPiece.transform.position = rch.collider.transform.position + new Vector3(0, 0.1f, 0); ClickableSquare cs; //Get a reference to the square if (rch.collider.TryGetComponent(out cs)) { //Set alpha of square so that players can tell where they are hovering over lastHovered = cs; cs.mr.material.color = new Color(1, 1, 1, 0.1f); //If the player has clicked that square, set the player's 'chosen move' to that square //Deactivate the hovering game piece and set conditions to exit the while loop if (hasClicked) { hoverPiece.gameObject.SetActive(false); chosenMove = new Vector2Int(cs.MySquareNumber / 3, cs.MySquareNumber % 3); isDoneMoving = true; hasClicked = false; //reset alpha values if (lastHovered != null) { lastHovered.mr.material.color = new Color(1, 1, 1, 0); } } } } } }
public void CanDrawBoardTest() { TTTBoard board = new TTTBoard(); board.MakeMove(new Nought(), 0, 0); board.MakeMove(new Cross(), 2, 2); ConsoleBoardPainter painter = new ConsoleBoardPainter(); board.PaintBoard(painter); }
void Restart() { gameBoard = new TTTBoard(TTTBoard.TTTPiece.X); XTurn = true; waiting = false; minWait = false; CancelInvoke("CancelMinWait"); DrawBoard(); }
void ReceiveTurn(ITurn turn) { //Debug.Log (gameBoard.ToString()); gameBoard = (TTTBoard)((TTTTurn)turn).ApplyTurn(gameBoard); XTurn = !XTurn; //Debug.Log (gameBoard.ToString()); DrawBoard(); waiting = false; }
/// <summary> /// Called when the start button is pressed <para/> /// Initialises the <see cref="mcts"/> tree search object and instantiates the root node <para/> /// Also creates as many starting nodes as the user specified /// </summary> public void StartButtonPressed() { //Create an empty board instance, which will have whatever game the user chooses assigned to it Board board; //Assign whatever game board the user has chosen to the board instance switch (HashUIController.GetGameChoice) { case 0: board = new TTTBoard(); displayBoardModel = false; break; case 1: board = new C4Board(); displayBoardModel = true; //Create a C4 Board GameObject and obtain a reference to its BoardModelController Component GameObject boardModel = Instantiate(Resources.Load("C4 Board", typeof(GameObject))) as GameObject; boardModelController = boardModel.GetComponent <BoardModelController>(); boardModelController.Initialise(); break; case 2: board = new OthelloBoard(); displayBoardModel = false; break; default: throw new System.Exception("Unknown game type index has been input"); } mcts = new TreeSearch <Node>(board); //Calculate the position of the root node and add an object for it to the scene Vector3 rootNodePosition = BoardToPosition(mcts.Root.GameBoard); GameObject rootNode = Instantiate(Resources.Load("HashNode"), rootNodePosition, Quaternion.identity) as GameObject; rootNode.transform.parent = transform; rootNode.GetComponent <HashNode>().AddNode(null, mcts.Root, false); rootNode.GetComponent <HashNode>().Initialise(rootNodePosition); //Add the root node to the position and object map nodePositionMap.Add(rootNodePosition, rootNode); nodeObjectMap.Add(mcts.Root, rootNode); //Create the amount of starting nodes specified by the user for (int i = 0; i < HashUIController.GetStartingNodeInput(); i++) { PerformStep(true); } //Swap out the current menu panels HashUIController.SetMenuPanelActive(false); HashUIController.SetNavigationPanelActive(true); }
public void MakeMoveInNonEmptyCell() { TTTBoard board = new TTTBoard(); //Make a move on this board board.MakeMove(new TTTMove(2, 2)); //Attempt to make another move in the same place as the last move, which should throw an InvalidMoveException Assert.Throws <InvalidMoveException>(() => board.MakeMove(new TTTMove(2, 2))); }
public void NoWinnerTest() { TTTBoard board = new TTTBoard(); //Make a move, if there is a winner, the winner flag will be set, but there is no winner, so it shouldn't board.MakeMove(new TTTMove(0, 0)); //Check that the winner flag has not been set, as there is no winner Assert.AreEqual(-1, board.Winner); }
public TTTBoard(TTTBoard oldBoard) { Size = oldBoard.Size; board = new TTTPiece[Size]; for (int i = 0; i < board.Length; i++) { board[i] = oldBoard.board[i]; } player = oldBoard.player; }
public void TTboardClone() { TTTBoard board = new TTTBoard(3); TTTBoard newBoard = board.Clone(); board.Move(0, 0, Player.PLAYERX); board.Move(0, 1, Player.PLAYERO); int emptyBoard = board.GetEmptySquares().Count; int emptyNewBoard = newBoard.GetEmptySquares().Count; Assert.AreNotEqual(emptyNewBoard, emptyBoard, "Bad clone"); }
public void GetPossibleMovesFullBoard() { TTTBoard board = new TTTBoard(); for (int y = 0; y < board.Height; y++) { for (int x = 0; x < board.Width; x++) { board.MakeMove(new TTTMove(x, y)); } } Assert.AreEqual(0, board.PossibleMoves().Count); }
public void WinTestHorizontal() { TTTBoard board = new TTTBoard(); //Make moves such that player 1 should win with a horizontal victory board.MakeMove(new TTTMove(0, 2)); board.MakeMove(new TTTMove(0, 0)); board.MakeMove(new TTTMove(1, 2)); board.MakeMove(new TTTMove(2, 0)); board.MakeMove(new TTTMove(2, 2)); //Player 1 should have won the game Assert.AreEqual(1, board.Winner); }
public void WinTestDownwardsDiagonal() { TTTBoard board = new TTTBoard(); //Make moves such that player 1 should win with a downwards diagonal victory board.MakeMove(new TTTMove(0, 0)); board.MakeMove(new TTTMove(0, 1)); board.MakeMove(new TTTMove(1, 1)); board.MakeMove(new TTTMove(2, 0)); board.MakeMove(new TTTMove(2, 2)); //Player 1 should have won the game Assert.AreEqual(1, board.Winner); }
public IGameState ApplyTurn(IGameState state) { TTTBoard board = (TTTBoard)state; board.SetPiece(x, player); if (board.player == TTTBoard.TTTPiece.X) { board.player = TTTBoard.TTTPiece.O; } else { board.player = TTTBoard.TTTPiece.X; } return(board); }
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 MakeMoveTest() { //Create a new board and make a move in it TTTBoard board = new TTTBoard(); board.MakeMove(new TTTMove(1, 0)); //Check that the move was made correctly Assert.AreEqual(1, board.GetCell(1, 0)); //Make a move on the board for the second player board.MakeMove(new TTTMove(2, 1)); //Check that the move was made correctly Assert.AreEqual(2, board.GetCell(2, 1)); }
public float Evaluate(IGameState state) { TTTBoard board = (TTTBoard)state; if (board.Winner(player)) { return(maxValue); } else if (board.Loser(player)) { return(minValue); } else { return(0); } }
public void DrawTest() { TTTBoard board = new TTTBoard(); //Make moves on the board until it is full and there is no winner board.MakeMove(new TTTMove(0, 0)); board.MakeMove(new TTTMove(1, 1)); board.MakeMove(new TTTMove(2, 2)); board.MakeMove(new TTTMove(0, 1)); board.MakeMove(new TTTMove(0, 2)); board.MakeMove(new TTTMove(2, 0)); board.MakeMove(new TTTMove(2, 1)); board.MakeMove(new TTTMove(1, 2)); board.MakeMove(new TTTMove(1, 0)); //Check that the game has ended in a draw Assert.AreEqual(0, board.Winner); }
public void CreateBoardTest() { TTTBoard board = new TTTBoard(); //Check that the current player is player 1 Assert.AreEqual(1, board.CurrentPlayer); //Ensure that the created board is empty for (int y = 0; y < board.Height; y++) { for (int x = 0; x < board.Width; x++) { Assert.AreEqual(0, board.GetCell(x, y)); } } //Ensure that the winner value is - 1 Assert.AreEqual(-1, board.Winner); }
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"); }
public void DuplicateTest() { //Create a new board and make a move in it TTTBoard boardA = new TTTBoard(); boardA.MakeMove(new TTTMove(1, 1)); //Duplicate the board and store it in a new board instance TTTBoard boardB = (TTTBoard)boardA.Duplicate(); //Ensure the move made before duplication is present in both boards Assert.AreEqual(1, boardA.GetCell(1, 1)); Assert.AreEqual(1, boardB.GetCell(1, 1)); //These two board instances should share no memory, lets prove it by making moves in each of them and checking the other boardA.MakeMove(new TTTMove(2, 0)); Assert.AreEqual(2, boardA.GetCell(2, 0)); Assert.AreEqual(0, boardB.GetCell(2, 0)); boardB.MakeMove(new TTTMove(1, 2)); Assert.AreEqual(0, boardA.GetCell(1, 2)); Assert.AreEqual(2, boardB.GetCell(1, 2)); }
public void GetPossibleMovesEmptyBoard() { TTTBoard board = new TTTBoard(); Assert.AreEqual(9, board.PossibleMoves().Count); }
//Repeatedly called until the player moves //Makes a function call to AI's logic algorithm in ComputerPlayer public abstract IEnumerator GetDecision(TTTBoard board);
/// <summary> /// Called when the start/stop button is pressed <para/> /// If MCTS is not running, then it will be started <para/> /// If MCTS is running, this will make it finish early /// </summary> public void StartStopButtonPressed() { //Starts or ends MCTS depending on when the button is pressed if (mcts == null) { //Create an empty board instance, which will have whatever game the user chooses assigned to it Board board; //Assign whatever game board the user has chosen to the board instance switch (TreeUIController.GetGameChoice) { case 0: displayBoardModel = false; board = new TTTBoard(); break; case 1: displayBoardModel = true; board = new C4Board(); //Create a C4 Board GameObject and obtain a reference to its BoardModelController Component GameObject boardModel = Instantiate(Resources.Load("C4 Board", typeof(GameObject))) as GameObject; boardModelController = boardModel.GetComponent <BoardModelController>(); boardModelController.Initialise(); break; case 2: displayBoardModel = false; board = new OthelloBoard(); break; default: throw new System.Exception("Unknown game type index has been input"); } //Assign whatever visualisation type the user has chosen switch (TreeUIController.GetVisualisationChoice) { case 0: visualisationType = VisualisationType.Standard3D; break; case 1: visualisationType = VisualisationType.Disk2D; break; case 2: visualisationType = VisualisationType.Cone; break; default: throw new System.Exception("Unknown visualisation type: encountered"); } //Initialise MCTS on the given game board mcts = new TreeSearch <NodeObject>(board); //Obtain the time to run mcts for from the input user amount timeToRunFor = TreeUIController.GetTimeToRunInput; timeLeft = timeToRunFor; //Run mcts asyncronously RunMCTS(mcts); TreeUIController.StartButtonPressed(); } else { //Stop the MCTS early mcts.Finish(); } }
//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(); } }
public void SimulateTest() { TTTBoard board = new TTTBoard(); board.SimulateUntilEnd(); }