GameBoard.cs

mail@pastecode.io avatar
unknown
csharp
a year ago
16 kB
5
Indexable
Never
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

// This class maintains the game using the ACTUAL game board for TicTacToe. It allows each player to take a turn and determines
// whether there is a win/loss or tie. It also allows for restarting new games.   It contains the minimax function for the AI. 
// You will need to complete the minimx funtion. 

public class GameBoard : MonoBehaviour {
	
	public enum PLAYERS_ID { PLAYER_NONE = 0, PLAYER_ONE ,PLAYER_TWO };  // player one is the user, player two is the AI
	
	public const int MAX_COLUMNS = 3; 	// Max width of game board  (set to 4 for 4x4 grid)
	public const int MAX_ROWS = 3;      // Max height of game board (set to 4 for 4x4 grid)
    public const int CONNECT_NUM = 3;   // Number of squares to connect in row in order to win (set to 4 for 4x4 grid)

    public const int MAX_DEPTH = 9;     // max depth level for miniMax algorithm
                                        // use this value to set the max search in your tree for the minimax algorithm.  
                                        // Test your solution by setting this value to 1,3,5,9 for 3x3 game board.  
                                        // Set this value to 3,4,5 for testing with a 4x4 board. 
                                        // Higher values will cause SIGNIFICANT slow down on a 4x4 grid.
    public const int INFINITY = 5000000;
	
	public GameObject gamePiecePrefab;  // prefab representing one game piece (or board space) 
	
	static bool gameFinished = false;   // true if game is over
	
	static public GamePiece[,] gameBoard = new GamePiece[MAX_ROWS, MAX_COLUMNS];  // 2D array/grid representing the ACTUAL game board

    static PLAYERS_ID winningPlayer = PLAYERS_ID.PLAYER_NONE;   // stores the winner of each game played
	
	//
	//This function is used to claim a move (by playerID) on the actual game board
	//
	static public void BoardClicked( int clickedRow, int clickedColumn, bool isOccupied, PLAYERS_ID playerID )
	{
        // if the game isn't finished and a valid, unoccupied spot was clicked
		if (!gameFinished && clickedColumn >= 0 && clickedColumn < MAX_COLUMNS && clickedRow >= 0 && clickedRow < MAX_ROWS )
		{
			if ( !isOccupied  )
			{
				Color colourPiece;  //Find the colour for  specific player
				if(playerID == PLAYERS_ID.PLAYER_TWO)
				{
					colourPiece = new Color(0,0,255);
				}
				else
				{
					colourPiece = new Color(255,0,0);
				}
				
                // Add the player to the game board 
				gameBoard[clickedRow,clickedColumn].AddPlayerToSquare(playerID,clickedRow,clickedColumn);
				gameBoard[clickedRow,clickedColumn].SetColor(colourPiece);
				
                // Check for win or tie
				if ( CheckForWinner(gameBoard[clickedRow,clickedColumn]) )
				{
					print ("Player " + playerID + " has won!!!!") ;
					winningPlayer = playerID;
					gameFinished = true;
				}	
				else if( CheckForTie() )
				{
					winningPlayer = PLAYERS_ID.PLAYER_NONE;
					print ("GAME TIED!!!!") ;
					gameFinished = true;
				}
			}
		}
	}
    //
    //This function implements the Minimax algorithm
    //
    //moveboard board   --represents the current game board as a scratch pad to make moves on (before actually making the move)
    //                  --board also holds the actual player's turn for the game (first player in the tree)
    //Move nextMove     --represents the next move to be made on the game board
    //                  --nexMove also holds a score value for the move
    //                  --PossibleMoves in nextMove represents the children of the "nextMove" (if this is empty then there are no moves to make, 
    //                    if there are children then each child represents a possible to move to make)
    //
    //evaluatePlayer    --represents the players turn on the tree ( it will change as we traverse each level of the tree )
    //
    //maxDepth          --represents the number of levels to use in the minimax algorithm (how far down the tree do we want to go?)
    //
    //currentDepth      --represents the current leve we are evaluating
    //
    //return            --this function returns the best move to make on the board

    static public Move MiniMax( moveBoard board, Move parentMove, GameBoard.PLAYERS_ID currentPlayerToEvaluate, int maxDepth, int currentDepth )
	{
		//
        //create a bestmove to return
        //
		Move bestScore = new Move();


		//if game is over ( no more moves left tie/win/loss ) || we have reached our max depth to search i.e. currentDepth == maxDepth
		if (board.IsGameOver() || currentDepth == maxDepth || (currentDepth != 0 && parentMove.PossibleMoves.Count == 0))
		{
			// STOP RECURSION: 
			// Evaluate the board (i.e. int score = board.EvalBoardForAI() )

			int score = board.EvalBoardForAI();
			parentMove.Score = score;
			parentMove.Depth = currentDepth;
			parentMove.Copy(bestScore);
		}
		else
		{
			//
			// We haven't reached a terminal node  or the end of the game so traverse the children of "nextMove" and call minimax on each child.
			//
			// BEFORE LOOPING: 

			Move currentBubbledUpScore = null;

			//
			// Initialize the bestmove "so far" object 
			//

			parentMove.Copy(bestScore);

			bestScore.Score = INFINITY; //Used for minimizing.

			//  Ask: whose turn is it on the tree? Keep track of the max or min score for all the possible moves (children)


			if (board.MaximizingPlayer == currentPlayerToEvaluate)            // init value to keep track of max score i.e. bestMove.Score = -INFINITY;

			{
				bestScore.Score = -INFINITY;            // init value to keep track of min score

			}

			foreach (Move move in parentMove.PossibleMoves)// START LOOP of moves in possibles moves (loop through siblings, that is the nextMove.PossibleMoves list),these are the children of "nextMove" : 

			{


				board.makeMove(move, currentPlayerToEvaluate);              //for each possible move, "move", make the "move" ( take the turn  on the scratch pad ) before calling minimax for the that "move".				// 

				currentBubbledUpScore = MiniMax(board, move, GetNextPlayerID(currentPlayerToEvaluate), maxDepth, currentDepth + 1);
				// CALL miniMax here using the board you just made a move on and the "move" (remember to switch to next player (GetNextPlayerID()) and to ADD one to depth value).
				// NOTE: miniMax will return a score for the board with the move that was just made. 
				// We need to keep track of the scores for the siblings (all moves in PossibleMoves) to 
				// find either the max score of all the siblings or the min score of all the siblings (this will depend on which player is making the move).
				//

				//
				// Remove move from our game board (game board scratch pad) so that the next sibling can make its move.
				// i.e. board.RemoveMoveFromGameMoveBoard(move);
				//

				board.RemoveMoveFromGameMoveBoard(move);

				//
				//if the current player to evaluate is the AI (MaximizingPlayer), 
				if (board.MaximizingPlayer == currentPlayerToEvaluate)
				{
					//take the highest score found of all the moves
					if(currentBubbledUpScore.Score >= bestScore.Score)
					{
						bestScore.Score = currentBubbledUpScore.Score;
						bestScore.Row = move.Row;
						bestScore.Column = move.Column;
						bestScore.Depth = currentBubbledUpScore.Depth;
					}
				}
                else
                {
                    if (currentBubbledUpScore.Score <= bestScore.Score)
                    {
                        bestScore.Score = currentBubbledUpScore.Score;
                        bestScore.Row = move.Row;
                        bestScore.Column = move.Column;
                        bestScore.Depth = currentBubbledUpScore.Depth;
                    }
                }

                //
                //else the opponent is the current player to evaluate so 
                //  take the lowest score of all the moves
                //

                // END LOOP of PossibleMoves  
            }

		}
		return bestScore;
	}
	
    // Used to get the next player (used in turn taking)
	static public PLAYERS_ID GetNextPlayerID( PLAYERS_ID currPlayer )
	{
		PLAYERS_ID nextPlayer = GameBoard.PLAYERS_ID.PLAYER_NONE;

		//Update the current player to the next player
		if( currPlayer == GameBoard.PLAYERS_ID.PLAYER_ONE)
		{
			nextPlayer = GameBoard.PLAYERS_ID.PLAYER_TWO;
		}
		else if (currPlayer == GameBoard.PLAYERS_ID.PLAYER_TWO)
		{
			nextPlayer = GameBoard.PLAYERS_ID.PLAYER_ONE;
		}
		return nextPlayer;
	}
	
	// This function is called by the AI when it is the AI's turn to "click" the board.
	static Move GetBestMove( GameBoard.PLAYERS_ID player )
	{
		//make a moveBoard of the current state of the board (it will be used as a scratch pad for minimax).
		moveBoard currentMoveBoard = new moveBoard();
		//this move board is used as a scratch pad to examine possible next moves, copy the current ACTUAL board onto the moveboard (scratch pad)
		currentMoveBoard.SetCurrentGameBoardToMoveBoard(player);
        //using the moveboard the minimax function finds the next BEST move
		Move bestMove = MiniMax( currentMoveBoard, currentMoveBoard.Root, player, MAX_DEPTH, 0 );
        // once the minimax function returns with the best move, return this move 
		return bestMove;
	} 
	
	static public void AI_BoardTurn( )
	{
		if (!gameFinished)
		{
			//Get the best move from the AI. Player_TWO is the AI and will be "maximing" in the minimax function
			Move bestMove = GetBestMove(PLAYERS_ID.PLAYER_TWO);
            // bestMove will contain the next best move for the AI
            // Make the best move on the ACTUAL board	
            BoardClicked(bestMove.Row, bestMove.Column, false, PLAYERS_ID.PLAYER_TWO );
		}
	}
	
	//if there are no more moves and no winner/loser then there is a tie
	public static bool CheckForTie( )
	{
		bool gameTied = true;
        //Assumes that there is not a win  on the board (assumes other function checks for win).  
        //If there is not a win or loss on the board and the there are no unoccupied spots then there is a tie.
		for (int i = 0; i < MAX_ROWS; i++)
		{
			for(int j = 0; j < MAX_COLUMNS; j++)
			{
				if(!(gameBoard[i,j].Occupied) )
				{
					gameTied = false;
				}
			}
		}
		return gameTied;
	}

	//checks the actual game board for a winner
	public static bool CheckForWinner( GamePiece playerGamePiece )
	{
		bool bWon = false;
		PLAYERS_ID playerTurnID = playerGamePiece.PlayerID;
		
		for (int i = 0; i < MAX_ROWS; i++)
		{
			for(int j = 0; j < MAX_COLUMNS; j++)
			{
				GamePiece gamePiece = gameBoard[i,j];
				if( playerTurnID == gamePiece.PlayerID )
				{
					int foundMatchCount = 0;
					//Check above,
					if( CheckPieces( gamePiece,ref foundMatchCount, playerTurnID, -1,0 ) )
					{
						bWon = true;
						//found a winner 
						break;
					}
					foundMatchCount = 0;
					//Check below,
					if( CheckPieces( gamePiece, ref foundMatchCount,playerTurnID, 1, 0) )
					{
						//found a winner 
						bWon = true;
						break;
					}
					foundMatchCount = 0;
					
					//Check right,
					if( CheckPieces( gamePiece, ref foundMatchCount,playerTurnID, 0, 1) )
					{
						//found a winner 
						bWon = true;
						break;
					}
					foundMatchCount = 0;
					
					//Check Left,
					if( CheckPieces( gamePiece, ref foundMatchCount,playerTurnID, 0, -1) )
					{
						//found a winner 
						bWon = true;
						break;
					}
					foundMatchCount = 0;
					
					//check upper right (above, right)
					if( CheckPieces( gamePiece, ref foundMatchCount,playerTurnID, -1,1) )
					{
						//found a winner 
						bWon = true;
						break;
					}

					foundMatchCount = 0;
					//check upper left (above, left)
					if( CheckPieces( gamePiece, ref foundMatchCount, playerTurnID, -1,-1) )
					{
						//found a winner 
						bWon = true;
						break;
					}

					foundMatchCount = 0;
					//check lower right (below, right)
					if( CheckPieces( gamePiece, ref foundMatchCount,playerTurnID, 1, 1) )
					{
						//found a winner 
						bWon = true;
						break;
					}
					
					foundMatchCount = 0;
					//check lower left (below, left)
					if( CheckPieces( gamePiece,ref foundMatchCount,playerTurnID, 1, -1) )
					{
						//found a winner 
						bWon = true;
						break;
					}
				}
			}
			if(bWon)
			{
				break;
			}
		}
		
		return bWon;
	}
    // Checks for matches for a win for playerTurnID, in the direction of rowOffset, and colOffset
    static public bool CheckPieces(GamePiece playerGamePiece, ref int foundMatchCount, PLAYERS_ID playerTurnID, int rowOffset, int colOffset)
    {
        bool bFoundWin = false;
        if (playerGamePiece.PlayerID == playerTurnID)
        {
            foundMatchCount++;
            if (foundMatchCount == CONNECT_NUM)
            {
                bFoundWin = true;
            }
            else if ( (playerGamePiece.Row >= 0 && playerGamePiece.Row < MAX_ROWS) && (playerGamePiece.Column >= 0 && playerGamePiece.Column < MAX_COLUMNS)
                    && foundMatchCount < CONNECT_NUM )
            {
                if ( ((playerGamePiece.Row + rowOffset) >= 0 && (playerGamePiece.Row + rowOffset) < MAX_ROWS) && ((playerGamePiece.Column + colOffset) >= 0 && (playerGamePiece.Column + colOffset) < MAX_COLUMNS))
                    bFoundWin = CheckPieces(gameBoard[playerGamePiece.Row + rowOffset, playerGamePiece.Column + colOffset], ref foundMatchCount, playerTurnID, rowOffset, colOffset);
            }
            if (bFoundWin)
            {
                playerGamePiece.SetColor(new Color(0, 255, 0));
            }
        }
        return bFoundWin;
    }

    //Initialize the actual game board
    void InitGameBoard(bool init)
    {
        gameFinished = false;
        winningPlayer = PLAYERS_ID.PLAYER_NONE;

        for (int i = 0; i < MAX_ROWS; i++)
        {
            for (int j = 0; j < MAX_COLUMNS; j++)
            {
                if (init)
                {
                    GameObject gamePieceObj = (GameObject)Instantiate(gamePiecePrefab);
                    gameBoard[i, j] = gamePieceObj.GetComponent<GamePiece>();
                }
                gameBoard[i, j].InitPlayerSquare(i, j);
            }
        }
    }
    //Update the UI
    void OnGUI(){
		
		bool newGameButtonClicked = GUILayout.Button("NewGame");
		bool AITurnButtonClicked = GUILayout.Button("AI Turn");
		
        //Start a new game if necessary
		if(newGameButtonClicked)
		{
			InitGameBoard(false);
		}
		
        //Let the AI take a turn
		if(AITurnButtonClicked)
		{
			AI_BoardTurn();
		}

		//check for game end
		if (gameFinished)
		{
			if (winningPlayer == PLAYERS_ID.PLAYER_NONE )
			{
				 GUI.Label( new Rect(0,300,300,300), "Game Tied!");
			}
			else if (winningPlayer == PLAYERS_ID.PLAYER_ONE )
			{
				 GUI.Label (new Rect(0,300,300,300), "Player has won!");
			}
			else if(winningPlayer == PLAYERS_ID.PLAYER_TWO)
			{
				 GUI.Label (new Rect(0,300,300,300), "AI has won!");
			}
		}
	}
	
	// Use this for initialization
	void Start () {
		InitGameBoard(true);
	}
	
	// Update is called once per frame
	void Update () {
	
	}

	
	
}