Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Android SDK: Create an Arithmetic Game – High Scores and State Data

$
0
0

In this series we are creating a simple arithmetic game for Android. The game is going to display a calculator-style interface to users, repeatedly presenting questions and keeping track of how many correct answers they score in a row. In the first two parts of the series we built a calculator-style user interface and implemented the bulk of the gameplay logic, including prompting the user to choose a difficulty level, presenting questions, responding to user input, and updating the score. In this last part of the series we will tie up some loose ends, implement the high scores functionality, and save the app instance data to provide continuity when the state changes.


Series Format

This series on Creating an Arithmetic Game will be released in three parts:


Game Overview

The following is a screenshot of the game I’ll teach you how to build:

Arithmetic Game

The app chooses arithmetic questions using randomly selected operators and operands, with the details determined by the difficulty level. The user interface updates whenever the user enters an answer. To implement the remaining functionality, we will add processing to all of the Activity classes, as well as creating a helper class to model score information – the score information will be used to display high scores in the relevant screen and to save them during gameplay.


1. Save High Scores

Step 1

Last time we implemented most of the gameplay processing in the PlayGame class. Let’s enhance that class now by saving high scores as the user plays. High scores are going to be the number of questions answered correctly in a row, with the High Scores screen displaying the top ten. In your gameplay class, add the following import statements:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;
import android.content.SharedPreferences;

Next add instance variables for the application Shared Preferences and file name:

private SharedPreferences gamePrefs;
public static final String GAME_PREFS = "ArithmeticFile";

In the onCreate method, after the line in which you set the content view, instantiate the Shared Preferences object:

gamePrefs = getSharedPreferences(GAME_PREFS, 0);

Step 2

We will write the score to the Shared Preferences when the user answers a question incorrectly after answering at least one question correctly. We will also set the high score if the Activity is destroyed during gameplay. Since we will be setting the high score from more than one location in the class, let’s add a helper method:

private void setHighScore(){
//set high score
}

In this method we will deal with the details of checking whether or not the current score makes the top ten. Inside the method, first retrieve the score using the helper method we added last time:

int exScore = getScore();

We only want to proceed if the score is greater than zero:

if(exScore>0){
//we have a valid score
}

Place the remainder of the method code inside this conditional block. First, get the Shared Preferences Editor:

SharedPreferences.Editor scoreEdit = gamePrefs.edit();

For each high score, we will include the score and the date, so create a Date Format object:

DateFormat dateForm = new SimpleDateFormat("dd MMMM yyyy");

Get the date and format it:

String dateOutput = dateForm.format(new Date());

Retrieve any existing scores from the Shared Preferences, using a variable name we will also use when setting the new high scores:

String scores = gamePrefs.getString("highScores", "");

Check whether there are any existing scores:

if(scores.length()>0){
	//we have existing scores
}
else{
	//no existing scores
}

The else block is simpler, so complete it first:

scoreEdit.putString("highScores", ""+dateOutput+" - "+exScore);
scoreEdit.commit();

We simply add the new score along with the date, with a dash character between them – each score in the list will use this format. Back in the if block for cases where there are existing high scores, we have something a little more complex to do. We only want to store the user’s top ten scores, so we are going to create a helper class to model a single score object, which will implement the Comparable interface, allowing us to sort the stored scores and save only the top ten.

Step 3

Add a new class to your app’s source package, naming it “Score”. Extend the opening declaration line to implement the Comparable interface:

public class Score implements Comparable<Score>

We will be comparing Score objects against one another. Give the Score class two instance variables, for the score number and date:

private String scoreDate;
public int scoreNum;

For simplicity we are using a public variable for the score number to make it easier when we compare Score objects – you could alternatively use a private variable and add a get method for the score number. Add a constructor method instantiating these:

public Score(String date, int num){
	scoreDate=date;
	scoreNum=num;
}

Now add the compareTo method which will allow us to sort scores to determine the top ten:

public int compareTo(Score sc){
	//return 0 if equal
	//1 if passed greater than this
	//-1 if this greater than passed
	return sc.scoreNum>scoreNum? 1 : sc.scoreNum<scoreNum? -1 : 0;
}

This is actually contrary to what you may normally expect from a compareTo method. Typically, such methods return a negative integer if the passed object is greater and a positive integer if the passed object is lesser – we are doing the opposite. This is because the highest score is the best, so we want these objects to be sorted in descending rather than ascending order so that we can store only the ten best.

Finally add a method to return the date display text:

public String getScoreText()
{
    return scoreDate+" - "+scoreNum;
}

Step 4

Back in your PlayGame class, in the setHighScore method, we can now create a list of Score objects:

List<Score> scoreStrings = new ArrayList<Score>();

We will be storing the high scores in the Shared Preferences as one pipe-delimited string, so split that now:

String[] exScores = scores.split("\\|");

Now loop through, creating a Score object for each high score by splitting each one into its date and number, then adding it to the list:

for(String eSc : exScores){
	String[] parts = eSc.split(" - ");
	scoreStrings.add(new Score(parts[0], Integer.parseInt(parts[1])));
}

After this for loop, create a Score object for the current score and add it to the list:

Score newScore = new Score(dateOutput, exScore);
scoreStrings.add(newScore);

Sort the Scores:

Collections.sort(scoreStrings);

This will sort the Score objects according to the compareTo method we defined in the class declaration, so the first ten Score objects in the sorted list will be the top ten scores. Now we can simply add the first ten to a pipe-delimited string and write it to the Shared Preferences:

StringBuilder scoreBuild = new StringBuilder("");
for(int s=0; s<scoreStrings.size(); s++){
	if(s>=10) break;//only want ten
	if(s>0) scoreBuild.append("|");//pipe separate the score strings
	scoreBuild.append(scoreStrings.get(s).getScoreText());
}
//write to prefs
scoreEdit.putString("highScores", scoreBuild.toString());
scoreEdit.commit();

Notice that we use the getScoreText method we added the Score class. We save the new scores using the variable name we used to retrieve them from the Shared Preferences earlier in the method, as we did when there were no existing scores.

Step 5

Now you can use the setHighScore method – first in the onClick method in the enter button else block, where you set the score display to zero because the user has entered an incorrect answer. Before the line in which you update the score Text View:

setHighScore();

We also want to set the high score if the Activity is about to be destroyed, so add the following method to your class:

protected void onDestroy(){
	setHighScore();
	super.onDestroy();
}

This way the user will not lose their score. To prevent cases where the app saves the high score on certain state changes such as orientation change, which will be unnecessary since we will handle saving state manually, open your Manifest file and extend the Activity element for the gameplay class as follows:

<activity
	android:name=".PlayGame"
	android:configChanges="keyboardHidden|orientation|screenSize" ></activity>

2. Save Instance State

Step 1

Before we finish with the gameplay Activity, let’s save the instance state so that the game data persists through state changes. Add the following method to the class:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
//save state
}

Inside the method, save the score and level:

int exScore = getScore();
savedInstanceState.putInt("score", exScore);
savedInstanceState.putInt("level", level);

Complete the method by calling the superclass method:

super.onSaveInstanceState(savedInstanceState);

Step 2

We’ve saved the score and level data, now let’s restore them. In onCreate, you should already have a conditional block in which you test for a passed extras bundle. Amend this section to accommodate another conditional that will execute first. Replace the following code:

Bundle extras = getIntent().getExtras();
if(extras !=null)
{
	int passedLevel = extras.getInt("level", -1);
	if(passedLevel>=0) level = passedLevel;
}

With this amended version:

if(savedInstanceState!=null){
	//restore state
}
else{
	Bundle extras = getIntent().getExtras();
	if(extras !=null)
	{
		int passedLevel = extras.getInt("level", -1);
		if(passedLevel>=0) level = passedLevel;
	}
}

We have moved the existing check for extras to an else block, first testing whether we have saved instance state data. In the if block, retrieve the level and score, updating the UI:

level=savedInstanceState.getInt("level");
int exScore = savedInstanceState.getInt("score");
scoreTxt.setText("Score: "+exScore);

Your app can now cope with state changes. You could also save the currently displayed question, but we will simply allow the app to choose a new question if a state change prevents it being preserved. On changes such as orientation, which we included in the Activity Manifest element, this code will not actually execute, as the app will not recreate the Activity. The saved state data provides a backup in cases where the app is about to be killed, for example if another Activity is in the foreground.


3. Display High Scores

Step 1

We’ve dealt with saving high scores, now let’s display them. In your app’s main Activity class, complete the content of the else if for the High Scores button in onClick by launching the following Intent:

Intent highIntent = new Intent(this, HighScores.class);
this.startActivity(highIntent);

Step 2

Now open your High Scores Activity class. Add the following imports:

import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.TextView;

Add the onCreate method inside the class, setting the content view to the layout we created earlier:

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

After setting the content view but still inside onCreate, retrieve the Text View we added to the layout for displaying the high scores:

TextView scoreView = (TextView)findViewById(R.id.high_scores_list);

Now retrieve the Shared Preferences using the constant we defined in the gameplay class:

SharedPreferences scorePrefs = getSharedPreferences(PlayGame.GAME_PREFS, 0);

Split the string into an array of high scores:

String[] savedScores = scorePrefs.getString("highScores", "").split("\\|");

Iterate through the scores, appending them into a single string with new lines between them:

StringBuilder scoreBuild = new StringBuilder("");
for(String score : savedScores){
	scoreBuild.append(score+"\n");
}

Display the result in the Text View:

scoreView.setText(scoreBuild.toString());

That’s the High Scores Activity complete!

Arithmetic High Scores

4. Complete How to Play Activity

Step 1

We have one remaining task – to complete the How to Play Activity. Open the class you created for it. Add the following import:

import android.os.Bundle;

Inside the class, add onCreate and set the content view to the layout we created:

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

Step 2

Now open your main Activity class and launch the How to Play Activity from the else if for the button in onClick:

Intent helpIntent = new Intent(this, HowToPlay.class);
this.startActivity(helpIntent);

Conclusion

That’s the app complete! Run it to see it in action. In this series we have used Java and Android structures to create an interactive arithmetic game in which the user tries to answer as many questions correctly in a row as possible. We implemented multiple levels and arithmetic question types with operators and operands tailored to the game logic. The app saves high scores and state data automatically, with common app elements including a High Scores display and helpful information about the game. See if you can think of any ways you could enhance the app and use it as a foundation for future development.


Viewing all articles
Browse latest Browse all 1836

Trending Articles