Chapter 7
Finishing Your First Game
In This Chapter
Handling the end of a hand and the end of a game
Developing a more sophisticated player AI
Using your own launcher icon
After you have every core element that enables you to represent the game state and make plays from both hands, your only remaining tasks are to handle the ends of hands and games — and to make a few improvements. You’re almost finished with your first game!
www.dummies.com/go/androidgameprogramming
Ending Hands and Games
You’ve got all the logic for making plays within a game, but hands and games can’t go on forever, right? You need to make sure the game can recognize states when the hand or game should come to an end so that scores can be tallied so the game can either
Progress to the next hand
Determine who won and who lost
Ending a hand
The hand should end when either player plays their last card. But then these three things should happen:
Update the scores.
Display a dialog indicating that the hand is over.
Start a new hand.
To handle the scoring first, you add a new variable to keep track of how many points were earned by a given player for the hand. You reset this variable after each hand. Add the following line to your variable declarations in GameView
:
private int scoreThisHand = 0;
To handle the scoring value for each card, you update the Card object. Open the Card
class and add the following variable declaration along with the others:
private int scoreValue = 0;
Now modify the constructor of Card to match Listing 7-1, and add the new getScoreValue() method.
Here’s a brief explanation of what the various lines do:
→5–10 These lines check the rank of the card and assign a score value to your variable when the card is created. In Crazy Eights, eights that remain in a player’s hand at the end of the game are worth 50 points to the opponent. Face cards are worth 10 points; aces, 1 point; and all other cards, their face values. There are other scoring conventions, so feel free to use your own, though you’ll stick with this standard one for now.
→16 You then need a method to get the score value when you tally the scores.
Next, you add the new method updateScores()
to your GameView
with the contents of Listing 7-2.
Listing 7-2: The New updateScores()
Method
private void updateScores() {
for (int i = 0; i < myHand.size(); i++) {
oppScore += myHand.get(i).getScoreValue();
scoreThisHand += myHand.get(i).getScoreValue();
}
for (int i = 0; i < oppHand.size(); i++) {
myScore += oppHand.get(i).getScoreValue();
scoreThisHand += oppHand.get(i).getScoreValue();
}
}
This listing loops through the hand that still has cards:
One hand is empty, so only one of these loops even does anything. If your hand is empty, you increment the score with the value of each card in your opponent’s hand.
You also increment the scoreThisHand
variable, which you use to inform the player of how many points were earned from this hand.
Likewise, when your opponent’s hand is empty, they get the point value of each card in your hand.
Now that the scores are updated, you display a new dialog to indicate that the hand has ended. It needs
Text to tell the player who ran out of cards and how many points were awarded
A button to dismiss the dialog and advance to the next hand
If you’re making a new dialog with different UI elements, you need a new layout. Follow these steps:
1. Right-click the res/layout
folder and select New⇒File.
2. Name the file end_hand_dialog.xml and click OK.
3. Modify the contents to match Listing 7-3.
Listing 7-3: The end_hand_dialog.xml
File
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
android:id=”@+id/endHandLayout”
android:layout_width=”275dp”
android:layout_height=”wrap_content”
android:orientation=”vertical”
android:layout_gravity=”top”
xmlns:android=”http://schemas.android.com/apk/res/android”
>
<TextView
android:id=”@+id/endHandText”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=””
android:textSize=”16sp”
android:layout_marginLeft=”5dp”
android:textColor=”#FFFFFF”
>
</TextView>
<Button
android:id=”@+id/nextHandButton”
android:layout_width=”125dp”
android:layout_height=”wrap_content”
android:text=”Next Hand”
>
</Button>
</LinearLayout>
This listing is an even simpler version of the Choose Suit dialog layout. Again, you’re using a LinearLayout
to align the elements vertically within the dialog, and you have only two elements:
TextView
You leave the default text for the TextView
empty for now because you’ll modify it by informing the player who ran out of cards on a given hand and how many points they earned from their opponent’s remaining cards.
Button
.
The button has the default text Next Hand
.
Now you add the method that gets called when a given hand is finished. Add the method shown in Listing 7-4 to your GameView
.
This listing is similar to the method that displays the Choose Suits dialog. It follows this process of events:
1. You create a new dialog, ensure that it doesn’t display a title, and then set the content to the new layout file.
2. You then call the updateScores()
method in line 5 to display the scoring information in the dialog. Line 6 creates a reference to the TextView
, and then the next few lines set the text, depending on who ran out of cards and how many points were earned.
3. Reference the button (starting with line 25), and set the onClickListener()
and its associated logic. You call a new method, initNewHand()
, which you haven’t written yet, and then dismiss the dialog.
You’ll also need the import:
import android.widget.TextView;
Now put in the initNewHand()
method. Add the code in Listing 7-5 to your GameView
.
Here’s a brief explanation of the lines in the listing:
→2 Reset the points earned for the hand.
→3–7 Determine who will play first in the next hand. The player who goes out gets to play first in the next hand, so you check to see whose hand is empty and set the myTurn
variable accordingly.
→8–13 These lines add the discard pile and both players’ hands back to the deck, and then clear the lists for the hands and the discard pile. You’re essentially putting all cards back into the deck.
→14–17 Deal the cards and add the top card to the discard pile, setting the valid suit and rank based on that card.
→18–20 Finally, if it isn’t the human player’s turn, you instruct the computer to play. Otherwise, the game simply waits for play input from the player.
Now that you have all the associated logic to handle the end of a hand, you need to call it when either player runs out of cards. Check in the two places where the human and computer make their play — for the human, in the ACTION_UP
case of onTouchEvent()
.
Modify the relevant code in ACTION_UP
that handles dropping a card onto the discard pile to match the code in Listing 7-6.
Starting with line 14, you simply add a check to see whether the player’s hand is empty:
If the player’s hand is empty, you call the endHand()
method.
Otherwise, you continue the game by either calling the Choose Suits dialog or passing the turn to the computer.
What about the computer player? Find your makeComputerPlay()
method in GameView and modify it to match Listing 7-7.
You’ve added a check from line 35 after playing a card, to see whether the opponent’s hand is empty. If so, you call the endHand()
method. You should be able to launch the game and play out a real-life hand of Crazy Eights!
If you go out first, you should see a dialog similar to the one in Figure 7-1.
In this case, the human player ran out of cards first and earned 46 points from the opponent’s hand. Note also that the scores displayed at the top and bottom of the screen have been updated. At this point, you can play endless rounds of Crazy Eights, with the scores updating after every hand. But there’s no way for the game to end. Typically, a game ends when one player’s score reaches a predetermined goal. In this case, you use 300 points.
Ending a game
If you end the game when one player reaches or exceeds 300 points, you don’t need to do much other than modify the endHand()
method. Points are accumulated only at the conclusion of a hand, so you simply need to add some additional logic to detect end-of-game conditions and display different information in your existing End Hand dialog.
Figure 7-1: Displaying the end-of-hand dialog.
Modify your endHand()
method to match Listing 7-8.
Here’s a brief explanation of various lines in the listing:
→9–24 You’ve added conditionals to check whether either score equals or exceeds 300. If so, you display a different message, indicating that the game is over and who won, and asking whether the player wants to play another game.
→27 Detect whether either score exceeds 300, and if so, set the text on the button to New Game
instead of the default Next Hand
.
→32–35 The only other modification is zeroing out both players’ scores if the game is over before initializing a new hand.
Wrapping Up the Game
After you have all the necessary logic for playing not just one game but also starting new games, all the essential nuts and bolts are in place. However, the computer player is still not a capable opponent.
You’ll make improvements to make the game more competitive before updating the launcher icon and wrapping everything up.
Coding the opponent AI
You aren’t doing anything particularly sophisticated to the AI. Crazy Eights is a simple game, and its strategy, accordingly, isn’t complicated. Some games warrant different levels of strength for computer players, but that’s not necessary here. You simply want the computer player to provide a decent challenge.
There’s no need to make the game play optimally, because it’s a relatively simple game. Your players may not appreciate a computer that plays better than they do!
In the case of the Crazy Eights player, you make only two significant changes to improve it, which should make it suitable for average play. Both have to do with playing an 8:
Ensure that the computer plays an 8 only when it has no other legal play.
Be smart about which suit the computer chooses when it plays an 8.
Open your ComputerPlayer
class and modify the makePlay()
method to match Listing 7-9.
In the previous version of this method, you loop through the computer player’s hand, and if a given card is a legal play, set it as the play to make and then return it. Here, you separate out the logic for playing an 8 and a non-8:
Lines 3–16 loop through the computer’s hand the first time, determining whether each card that isn’t an 8 is a legal move.
If a legal non-8 move is found, it’s set to the current play.
Lines 17–25 are executed only if a non-8 legal play is found. If not, this loop looks for an 8 in the computer player’s hand, and if one is found, it’s set to the current play.
If no legal play is found, 0 is returned, indicating that the computer must draw a card.
This change means that the computer player is holding its 8 cards longer and playing them only when necessary, which is generally a smart way to play.
Next, you modify the chooseSuit()
method, as shown in Listing 7-10.
When the computer player plays an 8 and chooses a suit, it should choose the suit that is most prevalent in its hand. Before, it always picked diamonds (which isn’t an effective strategy).
Follow the steps below to improve the suit choice when the computer player plays an 8:
1. Add four variables to track the number of each suit (lines 3–6).
2. In Lines 7–21, loop through the computer player’s hand and count the number of each suit.
3. From line 22 on, check to see which suit count is greater than all the others.
• If it’s greater, set it to the suit to be returned.
• Otherwise, the default of diamonds is returned.
Give it a shot. You should find that this version of the computer player is a reasonably good challenge.
Making your own launcher icon
After the game is finished, you still haven’t updated the launcher icon from the default. Designing good-looking icons is harder than you might think. Your launcher icon should be simple, visually appealing, and instantly recognizable. If you have money in the budget, consider hiring a graphic designer to make your icon.
The icon should look good at different sizes because you’ll provide a resized icon for the four current screen densities. Figure 7-2 shows the Crazy Eights icon at each of the four generalized screen densities.
Figure 7-2: The Crazy Eights icon for each screen density.
From left to right, the icons are for
xhdpi (96 x 96)
hdpi (72 x 72)
mdpi (48 x 48)
ldpi (36 x 36)
The icon graphic has to be located in the proper res/drawable
folder, and each file must have the same name. The default launcher is named ic_launcher
, so I typically replace that graphic with my own and name it the same as the old default graphic. For example, you should have an icon graphic named ic_launcher
sized at 96 x 96 pixels in your res/drawable-xhdpi
folder, one named ic_launcher
sized 72 x 72 pixels in your res/drawable-hdpi
folder, and so on for all four icons.
If you decide to name your icon graphic differently, update your manifest. If you open it, you see in the application tag the android:icon
attribute, which points to the appropriate image file for a particular screen density.
After you’ve updated the icon, launch the game and return to the app directory to see the updated icon.
In the following chapters, I walk you through the steps to implement the popular carnival game Whack-a-Mole, which provides a strong basis for developing games of this type.