Thursday, March 21, 2013

Android Tic Tac Toe Game Logic - Part 2

In this section of the Tic Tac Toe tutorial we will implement several of the classes that we created in part one of the tutorial.

Implementing the GameManager Interface

The GameManager is responsible for handling the overall state of the game, including determining whether or not a move is valid and if the player or AI opponent has won the game. The implementation of the interface is posted below.


package com.dreamdom.tictactoe.gamedriver;

import java.util.ArrayList;
import java.util.Collections;

public class ClassicGameManager implements GameManager {

 // Constants
 public static final int NUM_ROWS = 3;
 public static final int NUM_COLS = 3;

 private int mStartingTurn;
 private ArrayList<TicTacToeDrawable> mTileList;

 private ArrayList<TicTacToeDrawable> mAvailableTiles;
 private GameAI mGameAI;
 private int mGameState;

 /**
  * 
  * @param tileList
  *            All the tiles on the gameboard. Must be non-null
  * @param gameAI
  *            The default gameAI for the game. Must be non-null
  */
 public ClassicGameManager(ArrayList<TicTacToeDrawable> tileList,
   GameAI gameAI) {
  mGameAI = gameAI;
  mTileList = tileList;

  // Init available tiles
  mAvailableTiles = new ArrayList<TicTacToeDrawable>(mTileList);
  Collections.copy(mAvailableTiles, mTileList);

  // Initialize the turn and game state
  mStartingTurn = TicTacToeConstants.TURN_PLAYER;
  mGameState = TicTacToeConstants.GAME_STATE_PLAYING;

 }

 @Override
 public void playerClickedTile(TicTacToeDrawable tile) {

  // The player is always x
  if (tile.getState() == TicTacToeConstants.TILE_STATE_EMPTY)
   changeTileValue(tile, TicTacToeConstants.TILE_STATE_X);

  if (mGameState == TicTacToeConstants.GAME_STATE_PLAYING) {

   // have the gameAI play
   TicTacToeDrawable t = mGameAI.playPiece(this);
   changeTileValue(t, TicTacToeConstants.TILE_STATE_O);
  }

 }

 @Override
 public void setStartingTurn(int startTurn) {
  mStartingTurn = startTurn;
 }

 @Override
 public boolean checkWin(int tileState, int row, int col) {

  int northSouth = 0;
  int eastWest = 0;
  int northwestSoutheast = 0;
  int northeastSouthwest = 0;

  // Loop through all the pieces
  for (TicTacToeDrawable curTile : mTileList) {
   int curRow = curTile.getRow();
   int curCol = curTile.getCol();
   int curState = curTile.getState();

   if (curState == tileState) {

    if (curCol == col) {
     if (curRow < row || curRow > row)
      northSouth++;
    } else if (curRow == row) {
     if (curCol < col || curCol > col)
      eastWest++;
    } else if ((curRow - row) == (curCol - col)) {
     northwestSoutheast++;
    } else if ((curRow - row) == -(curCol - col)) {
     northeastSouthwest++;
    }
   }
  }

  // Check for a win in all the possible directions
  if (northSouth == 2)
   return true;
  if (eastWest == 2)
   return true;
  if (northwestSoutheast == 2)
   return true;
  if (northeastSouthwest == 2)
   return true;

  return false;
 }

 @Override
 public void setGameAI(GameAI gameAI) {
  mGameAI = gameAI;
 }

 @Override
 public void setTileList(ArrayList<TicTacToeDrawable> tileList) {
  mTileList = tileList;

  mAvailableTiles = new ArrayList<TicTacToeDrawable>(mTileList);
  Collections.copy(mAvailableTiles, mTileList);

 }

 @Override
 public void reset() {
  // Change all the tiles back to being available
  mAvailableTiles = new ArrayList<TicTacToeDrawable>(mTileList);
  Collections.copy(mAvailableTiles, mTileList);

  // Loop through and set all the tiles to empty
  for (TicTacToeDrawable curTile : mAvailableTiles)
   curTile.setState(TicTacToeConstants.TILE_STATE_EMPTY);

  // Set the game state to ready
  mGameState = TicTacToeConstants.GAME_STATE_PLAYING;

  // If the computer starts, have them play
  if (mStartingTurn == TicTacToeConstants.TURN_COMPUTER) {
   TicTacToeDrawable t = mGameAI.playPiece(this);
   changeTileValue(t, TicTacToeConstants.TILE_STATE_O);
  }

 }

 @Override
 public void changeTileValue(TicTacToeDrawable tile, int value) {
  // Set the state and update the list of available tiles
  tile.setState(value);
  mAvailableTiles.remove(tile);

  // Check for the win
  if (checkWin(value, tile.getRow(), tile.getCol())) {
   if (value == TicTacToeConstants.TILE_STATE_X) {
    mGameState = TicTacToeConstants.GAME_STATE_PLAYER_WINS;
   } else if (value == TicTacToeConstants.TILE_STATE_O) {
    mGameState = TicTacToeConstants.GAME_STATE_COMPUTER_WINS;
   }
  }

  // Check for a cats game
  if (mGameState == TicTacToeConstants.GAME_STATE_PLAYING
    && mAvailableTiles.size() <= 0) {
   mGameState = TicTacToeConstants.GAME_STATE_CATS_GAME;
  }
 }

 @Override
 public ArrayList<TicTacToeDrawable> getAvailableTiles() {
  return mAvailableTiles;
 }

 @Override
 public int getGameState() {
  return mGameState;
 }
}

The checkWin function is implemented by simply checking the four different directions you can win. It takes a parameter tileState, which is the state of the tile that was last placed. The check win function is called everytime a tile has been placed.

Implementing the GameAI Interface

In this version of Tic Tac Toe that we are building, we are going to implement two different AIs. An easy AI and a difficult AI.

Implementing the EasyAI

EasyAI is actually somewhat of a misnomer. The AI is easy to beat, but a more descriptive name would be random AI. It is certainly possible for this AI to win, but not very likely if it is facing a determined opponent. This implementation just places a piece randomly on the game board.

package com.dreamdom.tictactoe.gamedriver;

import java.util.ArrayList;

public class EasyGameAI implements GameAI {

 @Override
 public TicTacToeDrawable playPiece(GameManager gameManager) {
  
  // Pick a piece at random
  ArrayList<TicTacToeDrawable> availableTiles = gameManager.getAvailableTiles();
  TicTacToeDrawable t = availableTiles.get((int) (availableTiles.size()*Math.random()));
  
  return t;
 }

}


Implementing the DifficultAI

The difficult AI is a little smarter. It will check to see if it can put a piece down that will cause it to win. If it can, it will perform this move. If it can't it will check to see if it can block the player from winning, and go forward with this move. If all else fails it places a piece randomly.

package com.dreamdom.tictactoe.gamedriver;

import java.util.ArrayList;

public class HardGameAI implements GameAI {

 /**
  * Play piece
  * Assumes the player is X and the computer is O
  */
 @Override
 public TicTacToeDrawable playPiece(GameManager gameManager) {
  
  ArrayList<TicTacToeDrawable> availableTiles = gameManager.getAvailableTiles();
  
  // Loop through all the tiles and to check for a win
  for(TicTacToeDrawable t : availableTiles) {
   if(gameManager.checkWin(TicTacToeConstants.TILE_STATE_O, t.getRow(), t.getCol())) {
    return t;
   }
  }
  
  // Loop through all the tiles and prevent a player win
  for(TicTacToeDrawable t : availableTiles) {
   if(gameManager.checkWin(TicTacToeConstants.TILE_STATE_X, t.getRow(), t.getCol())) {
    return t;
   }
  }
  
  // Default to picking a random tile
  TicTacToeDrawable t = availableTiles.get((int) (availableTiles.size()*Math.random()));
  
  return t;
 }

}

Keep in mind that this AI was not designed to be unbeatable, but rather just something slightly more complex than the EasyAI.

Next in the Tutorial Series

Now that the logic for playing the game exists, we will implement it in an Android App.

No comments:

Post a Comment