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:
- Src (source)
- Gen (generated)
- Res (resources)
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:
- Keep the state of a cell: Empty, Player X, or Player O
- Handle touch interactions and change the state accordingly
- Display the correct image based on its state
GameState will keep track of the following things:
- The game board (a 3x3 grid or CellStates)
- The current player
We need the GameState to perform the following actions:
- Determine if a game is over
- Determine if the board is full
- Switch players
- Set the game message
- Start a new game
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:
- Set the variable mState to EMPTY
- Set the CellState image resource by calling setImageResource with the
image id (this will display the image in Android).
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.
- mCells: The array of CellStates
- mDisplay: A TextView object that displays text to the android
screen. You will need to use the setText method.
- mCurPlayer: The current players turn. This will be set to either P1 or P2.
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.