Create a Hangman Game: User Interaction
In this series, we are creating a Hangman game for the Android platform. So far, we've built the application's user interface, including images, drawables, and layouts. In this third and final installment, we are going to focus on user interaction.
Introduction
Adding user interaction to the game involves several aspects, including detecting whether the user wins or loses a game as well as responding to each event. We'll also add a help button to the action bar and add the ability to navigate the game.
To refresh your memory, this is what the final game will look like.
1. Prepare the User Interface
Step 1
As we saw in the previous tutorial, the game activity presents the gallows area with the six body parts drawn to the screen. When a new game starts, the body parts need to be hidden, only showing them when the user makes a wrong guess. Open the game's activity class and start by adding the following import statements to it.
1
2
3
4
5
6
7
import
android.app.AlertDialog;
import
android.content.DialogInterface;
import
android.support.v4.app.NavUtils;
import
android.view.Menu;
import
android.view.MenuItem;
import
android.view.View;
import
android.widget.ImageView;
Next, declare five instance variables in the class's interface.
01
02
03
04
05
06
07
08
09
10
//body part images
private
ImageView[] bodyParts;
//number of body parts
private
int
numParts=
6
;
//current part - will increment when wrong answers are chosen
private
int
currPart;
//number of characters in current word
private
int
numChars;
//number correctly guessed
private
int
numCorr;
You could change the number of body parts if, for example, you'd like to add several levels of difficulty to the game. By storing the current body part (currPart
), we can add one body part at a time in case the player makes a wrong guess. We use the letter count of the target word and the number of right guesses to keep track of the player's progress in the current game. We periodically check if the player has won or lost the game.
In the onCreate
method of the game's activity class, right before we invoke playGame
, we instantiate the image view array and fetch the body part images we placed in the layout. This code snippet also determines the order in which the body parts are shown when the player makes a wrong guess. We start with the head and end with the legs.
1
2
3
4
5
6
7
bodyParts =
new
ImageView[numParts];
bodyParts[
0
] = (ImageView)findViewById(R.id.head);
bodyParts[
1
] = (ImageView)findViewById(R.id.body);
bodyParts[
2
] = (ImageView)findViewById(R.id.arm1);
bodyParts[
3
] = (ImageView)findViewById(R.id.arm2);
bodyParts[
4
] = (ImageView)findViewById(R.id.leg1);
bodyParts[
5
] = (ImageView)findViewById(R.id.leg2);
1
2
3
4
5
6
7
| import android.app.AlertDialog; import android.content.DialogInterface; import android.support.v4.app.NavUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; |
01
02
03
04
05
06
07
08
09
10
| //body part images private ImageView[] bodyParts; //number of body parts private int numParts= 6 ; //current part - will increment when wrong answers are chosen private int currPart; //number of characters in current word private int numChars; //number correctly guessed private int numCorr; |
currPart
), we can add one body part at a time in case the player makes a wrong guess. We use the letter count of the target word and the number of right guesses to keep track of the player's progress in the current game. We periodically check if the player has won or lost the game.onCreate
method of the game's activity class, right before we invoke playGame
, we instantiate the image view array and fetch the body part images we placed in the layout. This code snippet also determines the order in which the body parts are shown when the player makes a wrong guess. We start with the head and end with the legs.
1
2
3
4
5
6
7
| bodyParts = new ImageView[numParts]; bodyParts[ 0 ] = (ImageView)findViewById(R.id.head); bodyParts[ 1 ] = (ImageView)findViewById(R.id.body); bodyParts[ 2 ] = (ImageView)findViewById(R.id.arm1); bodyParts[ 3 ] = (ImageView)findViewById(R.id.arm2); bodyParts[ 4 ] = (ImageView)findViewById(R.id.leg1); bodyParts[ 5 ] = (ImageView)findViewById(R.id.leg2); |
Step 2
In the playGame
method, append the following code snippet. We set currPart
to 0
, set numChars
to the length of the target word, and set numCorr
to 0
.
1
2
3
currPart=
0
;
numChars=currWord.length();
numCorr=
0
;
Before we start the game, the body parts need to be hidden.
1
2
3
for
(
int
p =
0
; p < numParts; p++) {
bodyParts[p].setVisibility(View.INVISIBLE);
}
The next screenshot shows how the game should look when a new game is about to begin.
playGame
method, append the following code snippet. We set currPart
to 0
, set numChars
to the length of the target word, and set numCorr
to 0
.
1
2
3
| currPart= 0 ; numChars=currWord.length(); numCorr= 0 ; |
1
2
3
| for ( int p = 0 ; p < numParts; p++) { bodyParts[p].setVisibility(View.INVISIBLE); } |
2. Respond to User Clicks
Step 1
When we created the layout for the letter buttons, we declared an onClick
method. Let's add this to the game's activity.
1
2
3
public
void
letterPressed(View view) {
//user has pressed a letter to guess
}
When the player taps a letter button to make a guess, letterPressed
receives a reference to the view. This allows us to figure out which letter the player has chosen. To find out which letter the player has tapped, we use the following code snippet.
1
String ltr=((TextView)view).getText().toString();
Next, we get the character from the string.
1
char
letterChar = ltr.charAt(
0
);
We also disable the letter button and update the background drawable to show the player that the letter has already been played.
1
2
view.setEnabled(
false
);
view.setBackgroundResource(R.drawable.letter_down);
In the next step, we loop through the characters of the target word to verify whether the player's guess is in it. Each letter of the target word is compared with the player's guess. If the player's guess matches a letter in the target word, we increment numCorr
, set correct
to true
to indicate that the player made a good guess, and update the letter's text color from white to black to make it visible. The for
loop continues until every letter of the target word has been checked. This is important as a letter can occur more than once in the target word.
1
2
3
4
5
6
7
8
boolean
correct =
false
;
for
(
int
k =
0
; k < currWord.length(); k++) {
if
(currWord.charAt(k)==letterChar){
correct =
true
;
numCorr++;
charViews[k].setTextColor(Color.BLACK);
}
}
onClick
method. Let's add this to the game's activity.
1
2
3
| public void letterPressed(View view) { //user has pressed a letter to guess } |
letterPressed
receives a reference to the view. This allows us to figure out which letter the player has chosen. To find out which letter the player has tapped, we use the following code snippet.
1
| String ltr=((TextView)view).getText().toString(); |
1
| char letterChar = ltr.charAt( 0 ); |
1
2
| view.setEnabled( false ); view.setBackgroundResource(R.drawable.letter_down); |
numCorr
, set correct
to true
to indicate that the player made a good guess, and update the letter's text color from white to black to make it visible. The for
loop continues until every letter of the target word has been checked. This is important as a letter can occur more than once in the target word.
1
2
3
4
5
6
7
8
| boolean correct = false ; for ( int k = 0 ; k < currWord.length(); k++) { if (currWord.charAt(k)==letterChar){ correct = true ; numCorr++; charViews[k].setTextColor(Color.BLACK); } } |
Step 2
Next, we need to verify whether the player has won or lost the game after their guess, or made a wrong guess but can still continue. Still inside letterPressed
, start by checking if the player has made a good guess.
1
2
3
if
(correct) {
//correct guess
}
If she did make a good guess, check if she's guessed all the letters of the target word.
1
2
3
if
(numCorr == numChars) {
//user has won
}
If this is true, we notify the player that she's won the game. The first thing we do is disabling the letter buttons. We do this by implementing another helper method,disableBtns
. Implement this method after letterPressed
.
1
2
3
4
5
6
public
void
disableBtns() {
int
numLetters = letters.getChildCount();
for
(
int
l =
0
; l < numLetters; l++) {
letters.getChildAt(l).setEnabled(
false
);
}
}
In disableBtns
, we loop through the views via the adapter and disable each button. If the user has won the game, we invoke disableBtns
and display an alert dialog to the user. In the alert dialog, we also ask the player if they want to play another game.
Take a moment to look over this if you're not familiar with dialogs on Android. We set the properties including title and a message including confirmation of the correct answer. We add a button to play again to the alert dialog, which calls the playGame
method. We also add a button to exit, which takes the player back to the main activity.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
if
(numCorr == numChars) {
// Disable Buttons
disableBtns();
// Display Alert Dialog
AlertDialog.Builder winBuild =
new
AlertDialog.Builder(
this
);
winBuild.setTitle(
"YAY"
);
winBuild.setMessage(
"You win!\n\nThe answer was:\n\n"
+currWord);
winBuild.setPositiveButton(
"Play Again"
,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
id) {
GameActivity.
this
.playGame();
}});
winBuild.setNegativeButton(
"Exit"
,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
id) {
GameActivity.
this
.finish();
}});
winBuild.show();
}
letterPressed
, start by checking if the player has made a good guess.
1
2
3
| if (correct) { //correct guess } |
1
2
3
| if (numCorr == numChars) { //user has won } |
disableBtns
. Implement this method after letterPressed
.
1
2
3
4
5
6
| public void disableBtns() { int numLetters = letters.getChildCount(); for ( int l = 0 ; l < numLetters; l++) { letters.getChildAt(l).setEnabled( false ); } } |
disableBtns
, we loop through the views via the adapter and disable each button. If the user has won the game, we invoke disableBtns
and display an alert dialog to the user. In the alert dialog, we also ask the player if they want to play another game.playGame
method. We also add a button to exit, which takes the player back to the main activity.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
| if (numCorr == numChars) { // Disable Buttons disableBtns(); // Display Alert Dialog AlertDialog.Builder winBuild = new AlertDialog.Builder( this ); winBuild.setTitle( "YAY" ); winBuild.setMessage( "You win!\n\nThe answer was:\n\n" +currWord); winBuild.setPositiveButton( "Play Again" , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { GameActivity. this .playGame(); }}); winBuild.setNegativeButton( "Exit" , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { GameActivity. this .finish(); }}); winBuild.show(); } |
Step 3
If the user hasn't won the game, we need to verify if she has answered incorrectly, but still has some guesses left. Inside the else if
block, we show the next body part and increment the number of incorrect guesses with 1
.
1
2
3
4
5
6
7
8
if
(correct) {
//correct guess
}
else
if
(currPart < numParts) {
//some guesses left
bodyParts[currPart].setVisibility(View.VISIBLE);
currPart++;
}
else if
block, we show the next body part and increment the number of incorrect guesses with 1
.
1
2
3
4
5
6
7
8
| if (correct) { //correct guess } else if (currPart < numParts) { //some guesses left bodyParts[currPart].setVisibility(View.VISIBLE); currPart++; } |
Step 4
After the else if
, we can safely assume that the player has lost the game. We start by disabling the buttons as we did earlier and we display an alert dialog stating that the player has lost the game. We also include the correct answer and offer the option to play another game.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
else
{
//user has lost
disableBtns();
// Display Alert Dialog
AlertDialog.Builder loseBuild =
new
AlertDialog.Builder(
this
);
loseBuild.setTitle(
"OOPS"
);
loseBuild.setMessage(
"You lose!\n\nThe answer was:\n\n"
+currWord);
loseBuild.setPositiveButton(
"Play Again"
,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
id) {
GameActivity.
this
.playGame();
}});
loseBuild.setNegativeButton(
"Exit"
,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
id) {
GameActivity.
this
.finish();
}});
loseBuild.show();
}
Believe it or not, we're done implementing the user interaction aspect of the game. All that's left for us to do is add a few enhancements to the interface.
else if
, we can safely assume that the player has lost the game. We start by disabling the buttons as we did earlier and we display an alert dialog stating that the player has lost the game. We also include the correct answer and offer the option to play another game.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
| else { //user has lost disableBtns(); // Display Alert Dialog AlertDialog.Builder loseBuild = new AlertDialog.Builder( this ); loseBuild.setTitle( "OOPS" ); loseBuild.setMessage( "You lose!\n\nThe answer was:\n\n" +currWord); loseBuild.setPositiveButton( "Play Again" , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { GameActivity. this .playGame(); }}); loseBuild.setNegativeButton( "Exit" , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { GameActivity. this .finish(); }}); loseBuild.show(); } |
3. Complete the Action Bar
Step 1
Let's finish up this tutorial by adding a help button to the action bar. I won't go into too much detail, but feel free to experiment with this in your own applications. Depending on the API levels you target, support for navigating by means of the action bar is provided with little or no coding. To ensure the action bar allows navigating back to the main activity, add the following inside the onCreate
method in the game's activity.
1
getActionBar().setDisplayHomeAsUpEnabled(
true
);
In the manifest, remember that we specified the main activity as the parent of the game's activity. This tells the operating system that navigating back/up from the game's activity should bring the user back to the main activity. Your project should have a main menu resource. Open it and take a look at its contents. By default, it will have a settings action, which we don't need for our game. Insert the following code snippet, to add a help button.
1
2
3
4
5
<
item
android:id
=
"@+id/action_help"
android:icon
=
"@drawable/android_hangman_help"
android:showAsAction
=
"ifRoom"
android:title
=
"help"
/>
Remember that we listed the help icon in the first tutorial of this series. You can add more buttons to your action bar later if you like. If you do, you'll need to extend the activity code we cover below.
We don't need the action bar functions in the main activity so if Eclipse added theonCreateOptionsMenu
method to your main activity class, feel free to remove it.
onCreate
method in the game's activity.
1
| getActionBar().setDisplayHomeAsUpEnabled( true ); |
1
2
3
4
5
| < item android:id = "@+id/action_help" android:icon = "@drawable/android_hangman_help" android:showAsAction = "ifRoom" android:title = "help" /> |
onCreateOptionsMenu
method to your main activity class, feel free to remove it.Step 2
Back in the game's activity, set the screen to use the main menu by adding the following method.
1
2
3
4
5
@Override
public
boolean
onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return
true
;
}
Add another method to specify what should happen when the user tries to navigate back/up or press the help button in the action bar.
01
02
03
04
05
06
07
08
09
10
11
12
13
@Override
public
boolean
onOptionsItemSelected(MenuItem item) {
switch
(item.getItemId()) {
case
android.R.id.home:
NavUtils.navigateUpFromSameTask(
this
);
return
true
;
case
R.id.action_help:
showHelp();
return
true
;
}
return
super
.onOptionsItemSelected(item);
}
Navigating up will take the user back to the main activity. The android.R.id.home
action corresponds to the call to setDisplayHomeAsUpEnabled
we added inonCreate
.
Step 3
1
2
3
4
5
| @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true ; } |
01
02
03
04
05
06
07
08
09
10
11
12
13
| @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: NavUtils.navigateUpFromSameTask( this ); return true ; case R.id.action_help: showHelp(); return true ; } return super .onOptionsItemSelected(item); } |
android.R.id.home
action corresponds to the call to setDisplayHomeAsUpEnabled
we added inonCreate
.
Add another instance variable at the top of the class for the help information.
1
private
AlertDialog helpAlert;
We'll use another helper method to show the help information.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public
void
showHelp() {
AlertDialog.Builder helpBuild =
new
AlertDialog.Builder(
this
);
helpBuild.setTitle(
"Help"
);
helpBuild.setMessage(
"Guess the word by selecting the letters.\n\n"
+
"You only have 6 wrong selections then it's game over!"
);
helpBuild.setPositiveButton(
"OK"
,
new
DialogInterface.OnClickListener() {
public
void
onClick(DialogInterface dialog,
int
id) {
helpAlert.dismiss();
}});
helpAlert = helpBuild.create();
helpBuild.show();
}
The action bar support we've included will only work for API levels of 11 and up, and navigation support will only work for API levels of 16 and up. To support older versions, you'll need to use the support libraries for the action bar and navigation.
1
| private AlertDialog helpAlert; |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
| public void showHelp() { AlertDialog.Builder helpBuild = new AlertDialog.Builder( this ); helpBuild.setTitle( "Help" ); helpBuild.setMessage( "Guess the word by selecting the letters.\n\n" + "You only have 6 wrong selections then it's game over!" ); helpBuild.setPositiveButton( "OK" , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { helpAlert.dismiss(); }}); helpAlert = helpBuild.create(); helpBuild.show(); } |
0 comments: