GameBoard.cs
unknown
csharp
2 years ago
16 kB
11
Indexable
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 () {
}
}
Editor is loading...