Android: Tic Tac Toe

In this lab, you will incrementally implement an android application of Tic Tac Toe. At the end of the lab, WebIDE will provide you with a QR Code that you can scan with your phone to download your application.

Setting up the project

First, you will need to make sure you have the Java SDK, Eclipse, and the Android SDK installed. If you haven't already done so, follow the instructions here for the Java SDK, here for Eclipse, and here for Android.

Next, you need to download the TicTacToe project.

Finally, open Eclipse, goto File->Import->"Existing Projects Into Workspace" and select you unzipped TicTacToe project.

Android Overview

First, we need to discuss the structure of an Android application. The most important file in an Android application is the AndroidManifest.xml. This file specifies the name, permissions, and activities of an application. An activity is a class where the application starts.

Android projects consist of 3 different folders: The src folder contains all of your java classes. The gen folder contains java classes that android generates. The res folder contains all resources that an application might use, such as images, layouts, and values.

Android generates ids for all resources and places static final references in a generated file called "R.java." If you didn't get all that, don't worry because you only need to know how to use them. At any point in your code, just type the following:

R.<type of resource>.<name>

For example, you will be provided an x and o image for the two different players in Tic Tac Toe. All images are called a "drawable" resource in Android. So to use one of these images, we just type R.drawable.x or R.drawable.o. This will give us the id of the resource, which you will pass into methods to do different things, such as display the image. In this lab, you are provided all the resources you need, along with the Android Manifest file, so all you need to do is write the classes that go in the src folder. However, in future projects you may need to modify the manifest or layout files, so let's see what those files look like.

Manifest and Layout

The manifest is specified in XML (Extensible Markup Language). XML provides a way to specify data in a custom, easy to read format. If you have ever written HTML, you have seen XML (HTML is a form of XML).

Take a look in the AndroidManifest.xml file within your project. Even if you don't fully understand what everything means, you can still get some meaningful information. Notice how the manifest specifies the icon, label, name, and activity TicTacToe (which is TicTacToe.java).

The @ symbol tells android to get value from the resources (discussed on the previous step). For example, the @drawable/icon tells android to get the icon image from the drawable (image) resource. Likewise, the @string/app_name tells android to get the string labeled "app_name."

Additionally, layouts can be specified in XML. When you built layouts in App Inventor, it was creating an XML file. Specifying the layout in xml is faster, more readable, and simplifies your Java code. However, it is important to know that everything done in the XML layout can also be done programmatically. For example, in XML, we can write the following.

   <LinearLayout android:width="100px">
      <button android:text="Hey" />
   </LinearLayout>

In contrast, the following code accomplishes the same thing as the above XML. Note that we are required to make the button, and then we must manually add it to a layout.

   LinearLayout l = new LinearLayout(this);
   l.setWidth("100px");
   Button b = new Button(this);
   b.setText("Hey");
   l.addView(b);

You may have noticed in the XML layout, that the first tag is the name of the Java object. Also, each attribute starts with "android:" then the name of the attribute you want to set. Each attribute corresponds with an object's set method. For example, "android:text" is equivalent to the "setText" method.

TODO Write some of the tic tac toe layout. Add attributes "background" with value "@drawable/bg", "layout_height" with value "230px", and "layout_width" with value "230px" to the grid view tag in main.xml.

TicTacToe Activity

We specified the android activity as TicTacToe.java in the AndroidManifest.xml file, as seen in the AndroidManifest. In order to make the TicTacToe class an activity, we have to extend the Activity class. Extending a class lets us extend and/or override the functionality of another class. In this case, you want to override the onCreate method, which gets called when the application starts on an android device.

   @Override
   public void onCreate(Bundle savedInstanceState) {

   }


The bundle class keeps track of saved state. When overriding a method, it is always smart to call the parent method, just in case it does any set up we don't know about. This can be accomplished by the following code.

   super.onCreate(savedInstanceState);

Next, we must set the layout of the android application. To set a layout, we need to call the Activity method setContentView(int id). The id is specified in the generated R file under layout. Since TicTacToe extends Activity, we can call setContentView as if it was our own method. Try to call that method with the id of the layout named main.

TODO Open TicTacTow.java and set the content view as described above.

TicTacToe Design

There will be two classed in our TicTacToe program.

CellState will do three things: GameState will keep track of the following things: We need the GameState to perform the following actions: Lets implement CellState first.

CellState - Helper Methods

As mentioned before, Android uses ids for resources. Therefore, each image has an id. We can use the id as the state, which will make it easy for the CellState to display the current state's image.

Since the CellState will also be used to display the image, we will extend android's ImageView.

   public class CellState extends ImageView

We will declare a static final int called EMPTY as follows:

   private final int EMPTY = R.drawable.empty;

Now the variable EMPTY has the image id for an "empty" image. We could use R.drawable.empty everywhere instead, however, EMPTY is cleaner and easier to read. We declared the variable final so that it can not be changed anywhere in the code, since the empty image will always have the same id.

CellState has the following variables.

   private GameState mGameState;
   private int mState;


We need a method that will set the state of CellState. It needs to do two things: This method is implemented for you.

Method: setState
Visibility: public
Parameters: int state
Return: void
Description: Set the cell state to state.

   public void setState(int state) {
      mState = state;
      setImageResource(mState);
   }


Implement the following methods.

Method: clearState
Visibility: public
Parameters: none
Return: void
Description: Clear the cell state by setting the state with the id of the empty image.

Method: getState
Visibility: public
Parameters: none
Return: int
Description: Returns the state.

Method: isEmpty
Visibility: public
Parameters: none
Return: boolean
Description: Returns true if the state is empty, false otherwise.

CellState - Constructor and touch

First, lets implement the constructor. Since CellState extends ImageView, we should call the super constructor. We also need to call several ImageView methods to set some parameters. I went ahead and set those for you. However, when we create a new CellState, we want the state to be empty. You can do this by calling your clearState method.

TODO clear the state in the constructor

The last method in CellState is onTouchEvent. This method takes in a MotionEvent object and is called when ever a user touches the image on the android device. When the game starts, all the cells will have an "empty" image. When the user presses the cell (image), the onTouchEvent method will get called.

This methods should first check if the cell is empty. If it is empty and the game is not over, we want to set the cell state to the current player by calling mGameState.getCurrentPlayer(), then tell the game state to switch players since the current player just went. We can do this by calling mGameState.switchPlayers(). Else, we don't want to do anything since the cell is already full.

Note: We can check if the game is over by calling mGameState.isGameOver();

TODO Write the onTouch function code

GameState -Helper Methods

First, lets create three private final variables call P1, P2, and SIZE (like we did for EMPTY in CellState). P1 should be initialized to the x image id, P2 should be initialized to the o image id, and SIZE should be set to the number of cells in a Tic Tac Toe board. The first one is done for you.

//P1
public static final int P1 = R.drawable.x;

TODO write P2 and SIZE in GameState.java

We will also have the following variables:

   private Context mContext;
   private CellState[] mCells;
   private TextView mDisplay;
   private Integer mCurPlayer;


We need mContext for to pass it to each CellState (remember that the ImageView super constructor required it). However, you will use the following variables in the following methods. Now we need the following methods (Note: the first one is done for you as an example):

Method: setCurrentPlayer
Visibility: public
Parameters: int player
Return: void
Description: Set the current player to player and set the display text to R.string.player1 or R.string.player2, depending on player.

   public void setCurrentPlayer(int player) {
      mCurPlayer = player;
      if (mCurPlayer == P1) {
         mDisplay.setText(R.string.player1);
      } else {
         mDisplay.setText(R.string.player2);
   }
}


TODO Implement the rest of the methods

Method: getCurrentPlayer
Visibility: public
Parameters: none
Return: int player
Description: Return the current player.

Method: switchPlayer
Visibility: public
Parameters: none
Return: void
Description: Set the current player to P2 if the current player is P1, or vice versa.

Method: newGame
Visibility: public
Parameters: none
Return: void
Description: Clear the state of each cell and set the current player to P1.

Method: setEndMessage
Visibility: private
Parameters: int player
Return: void
Description: Set the display to R.string.gameover1 if player equals P1. Set the display to R.string.gameover2 if player equals P2. Else, set the display to R.string.gameovertie.

Method: getState
Visibility: public
Parameters: none
Return: int[]
Description: Return an int array containing the state of each cell.

Method: setState
Visibility: public
Parameters: int player, int[] state
Return: void
Description: Set the current player to player and each cell's state to the corresponding state in state.

GameState - Determine End Game

In additon to the helper methods on the previous step, you need methods to determine if the game is over. Implement the following methods. If you forget any of the member variables or helper methods, look on the previous step.

Method: isFull
Visibility: private
Parameters: none
Return: boolean
Description: Return false if there are any empty cells, else return true.

Method: checkRow
Visibility: private
Parameters: CellState c1, CellState c2, CellState c3
Return: boolean
Description: Return false if c1, c2, or c3 is empty. Return true if c1, c2, and c3 are the same player, else return false.

Method: isGameOver
Visibility: public
Parameters: none
Return: boolean
Description: If a player has three cells in a row (horizontally, vertically, and diagonally), set the end message to the winning player and return true. Else if the board is full, set the end message to a tie and return true. Else, return false.

GameState - Constructor

For the GameState constructor, we want to set its member variables to the passed in parameters, initialize mCells to new CellStates, and set the current player to P1. When initializing the CellStates, you will need to pass in the context and "this" which is the current GameState object. You can reference back to "CellState - Constructor an touch" for more information, if needed.

Again, GameState's variables are the following:

   private Context mContext;
   private CellState[] mCells;
   private TextView mDisplay;
   private Integer mCurPlayer;


The constructor's parameters are a Context named c, and a TextView named display.

TODO Implement the TODOs in the GameState constructor

GameState - BaseAdapter

There is one last piece for GameState. Back in the the TicTacToe Activity, after setting the content view, we mentioned a GridView within the layout. The GridView will be responsible displaying a view in a 3x3 grid. In our case, the CellState which is an ImageView. The GridView needs an "adapter,". which it will use to retrieve the view for each position in the grid.

Since out GameState already has an array of view (CellState), we need to have GameState extend BaseAdapter.

TODO: Make GameState extends BaseAdapter

Now we have to override 4 methods. Two of them we are not going to use, and can be seen below.

   @Override
   public Object getItem(int position) {
      return null;
   }

   @Override
   public long getItemId(int position) {
      return 0;
   }


TODO Go ahead and override the other two methods.

Method: getCount
Visibility: public
Parameters: none
Return: int
Description: Return the number of items in the grid.

Method: getView
Visibility: public
Parameters: int position, View convertView, ViewGroup parent
Return: View
Description: Return the CellState at position. Don't worry about the other two parameters.

Finishing TicTacToe.java

We now have all the pieces to finish up TicTacToe.java. Below shows what we currently have from the step "TicTacToe Activity."

   private GameState mState;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
   }


To get objects from the main layout. We need to call findViewById, and pass in an id from the R.java file. An example is below.

TextView display = (TextView)findViewById(R.id.player);

TODO Go ahead and retrieve the GridView using R.id.gridview and assign it to a variable called gridview.

TODO Also retrieve a Button using R.id.newGame and assign it into newGame.

TODO Create a new GameState and assign it into mState. For the context, pass in "this". If you forget what the constructor looks like, you can find it in "GameState - Constructor."

TODO Now that we have a game state, we can set the adapter (remember that GameState extends BaseAdapter) for the grid view that you retrieved above. You can do this by calling the grid view's setAdapter method.

The last thing we need to do is set the click listener for the button. When a user touches the button, the onClick method will get called.

TODO Inside the onClick method, set mState to a newGame.

Run your program!

Now that all the classes are finished, we can build and deploy the android apk file. Right click on your project, and click "Run As," then select Android Application.

Turn it in and Demo to Dr. Janzen

Create a zip file of your Eclipse project. Upload it to the Digital Dropbox in Blackboard (be sure to add and send). Demo your working app to Dr. Janzen during lab.