В этой серии мы создаем простую арифметическую игру для Android. Игра будет отображать интерфейс в стиле калькулятора для пользователей, постоянно задавая вопросы и отслеживая, сколько правильных ответов они наберут подряд. В первых двух частях серии мы создали пользовательский интерфейс в стиле калькулятора и реализовали большую часть логики игрового процесса, в том числе предлагая пользователю выбрать уровень сложности, задавая вопросы, отвечая на вводимые пользователем данные и обновляя счет. В этой последней части серии мы свяжем некоторые слабые стороны, реализуем функциональность высоких результатов и сохраним данные экземпляра приложения, чтобы обеспечить непрерывность при изменении состояния.
Формат серии
Эта серия статей о создании арифметической игры будет выпущена в трех частях:
Обзор игры
Ниже приведен скриншот игры, в которой я научу вас строить:
Приложение выбирает арифметические вопросы, используя случайно выбранные операторы и операнды, детали которых определяются уровнем сложности. Пользовательский интерфейс обновляется всякий раз, когда пользователь вводит ответ. Чтобы реализовать оставшуюся функциональность, мы добавим обработку ко всем классам Activity, а также создадим вспомогательный класс для моделирования информации о баллах — информация о баллах будет использоваться для отображения высоких баллов на соответствующем экране и их сохранения во время игры.
1. Сохранить рекорды
Шаг 1
В прошлый раз мы реализовали большую часть обработки игрового процесса в классе PlayGame . Давайте улучшим этот класс сейчас, сохранив рекорды, пока пользователь играет. Высокие баллы — это количество вопросов, на которые даны правильные ответы подряд, а на экране «Лучшие результаты» отображаются первые десять. В своем игровом классе добавьте следующие операторы импорта:
1
2
3
4
5
6
7
8
|
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;
|
Затем добавьте переменные экземпляра для приложения Общие настройки и имя файла:
1
2
|
private SharedPreferences gamePrefs;
public static final String GAME_PREFS = «ArithmeticFile»;
|
В методе onCreate после строки, в которой вы устанавливаете представление содержимого, создайте экземпляр объекта Shared Preferences:
1
|
gamePrefs = getSharedPreferences(GAME_PREFS, 0);
|
Шаг 2
Мы запишем счет в Общие предпочтения, когда пользователь ответит на вопрос неправильно после правильного ответа хотя бы на один вопрос. Мы также установим рекорд, если активность будет уничтожена во время игры. Поскольку мы будем устанавливать высокий балл из нескольких мест в классе, давайте добавим вспомогательный метод:
1
2
3
|
private void setHighScore(){
//set high score
}
|
В этом методе мы будем иметь дело с деталями проверки того, является ли текущий счет первой десяткой. Внутри метода сначала получите результат, используя вспомогательный метод, который мы добавили в прошлый раз:
1
|
int exScore = getScore();
|
Мы хотим продолжить, только если оценка больше нуля:
1
2
3
|
if(exScore>0){
//we have a valid score
}
|
Поместите оставшуюся часть кода метода в этот условный блок. Сначала получите редактор общих настроек:
1
|
SharedPreferences.Editor scoreEdit = gamePrefs.edit();
|
Для каждого рекорда мы включим счет и дату, поэтому создайте объект формата даты:
1
|
DateFormat dateForm = new SimpleDateFormat(«dd MMMM yyyy»);
|
Получите дату и отформатируйте ее:
1
|
String dateOutput = dateForm.format(new Date());
|
Извлеките все существующие оценки из общих настроек, используя имя переменной, которое мы также будем использовать при установке новых рекордов:
1
|
String scores = gamePrefs.getString(«highScores», «»);
|
Проверьте, есть ли какие-либо существующие оценки:
1
2
3
4
5
6
|
if(scores.length()>0){
//we have existing scores
}
else{
//no existing scores
}
|
Блок else проще, поэтому сначала выполните его:
1
2
|
scoreEdit.putString(«highScores», «»+dateOutput+» — «+exScore);
scoreEdit.commit();
|
Мы просто добавляем новый счет вместе с датой, с тире между ними — каждый счет в списке будет использовать этот формат. Возвращаясь к блоку if для случаев, когда существуют высокие оценки, нам нужно сделать что-то более сложное. Мы хотим сохранить только первые десять оценок пользователя, поэтому мы собираемся создать вспомогательный класс для моделирования одного объекта оценки, который будет реализовывать интерфейс Comparable, позволяющий сортировать сохраненные оценки и сохранять только первые десять.
Шаг 3
Добавьте новый класс в исходный пакет вашего приложения, назвав его «Оценка». Расширьте строку объявления открытия, чтобы реализовать интерфейс Comparable:
1
|
public class Score implements Comparable<Score>
|
Мы будем сравнивать объекты Score друг с другом. Задайте классу Score две переменные экземпляра для номера и даты оценки:
1
2
|
private String scoreDate;
public int scoreNum;
|
Для простоты мы используем открытую переменную для номера оценки, чтобы упростить ее при сравнении объектов Score — в качестве альтернативы вы можете использовать закрытую переменную и добавить метод get для номера оценки. Добавьте метод конструктора, который создает эти экземпляры:
1
2
3
4
|
public Score(String date, int num){
scoreDate=date;
scoreNum=num;
}
|
Теперь добавьте метод CompareTo, который позволит нам отсортировать результаты, чтобы определить первую десятку:
1
2
3
4
5
6
|
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?
}
|
На самом деле это противоречит тому, что вы обычно ожидаете от метода CompareTo . Как правило, такие методы возвращают отрицательное целое число, если переданный объект больше, и положительное целое число, если переданный объект меньше — мы делаем противоположное. Это связано с тем, что наибольшее количество баллов является лучшим, поэтому мы хотим, чтобы эти объекты сортировались по убыванию, а не по возрастанию, чтобы мы могли хранить только десять лучших.
Наконец добавьте метод для возврата текста отображения даты:
1
2
3
4
|
public String getScoreText()
{
return scoreDate+» — «+scoreNum;
}
|
Шаг 4
Вернувшись в свой класс PlayGame , в методе setHighScore теперь мы можем создать список объектов Score:
1
|
List<Score> scoreStrings = new ArrayList<Score>();
|
Мы будем хранить рекорды в общих настройках как одну строку с разделителем канала, поэтому разделим их теперь:
1
|
String[] exScores = scores.split(«\\|»);
|
Теперь выполните цикл, создав объект Score для каждого рекорда, разделив каждый на дату и номер, а затем добавив его в список:
1
2
3
4
|
for(String eSc : exScores){
String[] parts = eSc.split(» — «);
scoreStrings.add(new Score(parts[0], Integer.parseInt(parts[1])));
}
|
После этого для цикла, создайте объект Score для текущей оценки и добавьте его в список:
1
2
|
Score newScore = new Score(dateOutput, exScore);
scoreStrings.add(newScore);
|
Сортировать результаты:
1
|
Collections.sort(scoreStrings);
|
Это позволит отсортировать объекты Score в соответствии с методом сравнения, который мы определили в объявлении класса, поэтому первые десять объектов Score в отсортированном списке будут лучшими десятью. Теперь мы можем просто добавить первую десятку в строку с разделителями канала и записать ее в Shared Preferences:
1
2
3
4
5
6
7
8
9
|
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();
|
Обратите внимание, что мы используем метод getScoreText, в который мы добавили класс Score. Мы сохраняем новые оценки, используя имя переменной, которое мы использовали для извлечения их из общих настроек ранее в методе, как мы делали, когда не было существующих оценок.
Шаг 5
Теперь вы можете использовать метод setHighScore — сначала в методе onClick в блоке кнопки ввода еще , где вы выставляете счет на ноль, потому что пользователь ввел неправильный ответ. Перед строкой, в которой вы обновляете счет Текстовое представление:
1
|
setHighScore();
|
Мы также хотим установить высокий балл, если действие будет уничтожено, поэтому добавьте в ваш класс следующий метод:
1
2
3
4
|
protected void onDestroy(){
setHighScore();
super.onDestroy();
}
|
Таким образом, пользователь не потеряет свой счет. Чтобы предотвратить случаи, когда приложение сохраняет высокий балл при определенных изменениях состояния, таких как изменение ориентации, что будет ненужным, поскольку мы будем обрабатывать сохранение состояния вручную, откройте файл манифеста и расширьте элемент Activity для класса игрового процесса следующим образом:
1
2
3
4
|
<activity
android:name=».PlayGame»
android:configChanges=»keyboardHidden|orientation|screenSize» >
</activity>
|
2. Сохранить состояние экземпляра
Шаг 1
Прежде чем мы закончим с игровым заданием, давайте сохраним состояние экземпляра, чтобы данные игры сохранялись при изменениях состояния. Добавьте следующий метод в класс:
1
2
3
4
|
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
//save state
}
|
Внутри метода сохраните счет и уровень:
1
2
3
|
int exScore = getScore();
savedInstanceState.putInt(«score», exScore);
savedInstanceState.putInt(«level», level);
|
Завершите метод, вызвав метод суперкласса:
1
|
super.onSaveInstanceState(savedInstanceState);
|
Шаг 2
Мы сохранили данные счета и уровня, теперь давайте восстановим их. В onCreate у вас уже должен быть условный блок, в котором вы проверяете переданный пакет дополнений. Измените этот раздел, чтобы включить другое условие, которое будет выполнено первым. Замените следующий код:
1
2
3
4
5
6
|
Bundle extras = getIntent().getExtras();
if(extras !=null)
{
int passedLevel = extras.getInt(«level», -1);
if(passedLevel>=0) level = passedLevel;
}
|
С этой исправленной версией:
01
02
03
04
05
06
07
08
09
10
11
|
if(savedInstanceState!=null){
//restore state
}
else{
Bundle extras = getIntent().getExtras();
if(extras !=null)
{
int passedLevel = extras.getInt(«level», -1);
if(passedLevel>=0) level = passedLevel;
}
}
|
Мы переместили существующую проверку на дополнительные элементы в блок else , сначала проверив, сохранили ли мы данные состояния экземпляра. В блоке if получите уровень и оценку, обновив интерфейс:
1
2
3
|
level=savedInstanceState.getInt(«level»);
int exScore = savedInstanceState.getInt(«score»);
scoreTxt.setText(«Score: «+exScore);
|
Ваше приложение теперь может справляться с изменениями состояния. Вы также можете сохранить отображаемый в данный момент вопрос, но мы просто позволим приложению выбрать новый вопрос, если изменение состояния препятствует его сохранению. При таких изменениях, как ориентация, которые мы включили в элемент Activity Manifest, этот код фактически не будет выполняться, так как приложение не будет воссоздавать Activity. Сохраненные данные состояния предоставляют резервную копию в случаях, когда приложение собирается быть убитым, например, если на переднем плане находится другая активность.
3. Отображение рекордов
Шаг 1
Мы занимались сохранением рекордов, теперь давайте их покажем. В главном классе Activity вашего приложения добавьте содержимое кнопки else if для High Scores в onClick , запустив следующее намерение:
1
2
|
Intent highIntent = new Intent(this, HighScores.class);
this.startActivity(highIntent);
|
Шаг 2
Теперь откройте свой класс активности рекордов. Добавьте следующий импорт:
1
2
3
|
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.TextView;
|
Добавьте метод onCreate внутри класса, задав представление содержимого для макета, который мы создали ранее:
1
2
3
4
5
|
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_high);
}
|
После настройки представления контента, но все еще внутри onCreate , восстановите текстовое представление, которое мы добавили в макет для отображения рекордов:
1
|
TextView scoreView = (TextView)findViewById(R.id.high_scores_list);
|
Теперь найдите Shared Preferences, используя константу, которую мы определили в классе игрового процесса:
1
|
SharedPreferences scorePrefs = getSharedPreferences(PlayGame.GAME_PREFS, 0);
|
Разделите строку на массив рекордов:
1
|
String[] savedScores = scorePrefs.getString(«highScores», «»).split(«\\|»);
|
Итерируйте по счетам, добавляя их в одну строку с новыми строками между ними:
1
2
3
4
|
StringBuilder scoreBuild = new StringBuilder(«»);
for(String score : savedScores){
scoreBuild.append(score+»\n»);
}
|
Отобразите результат в текстовом представлении:
1
|
scoreView.setText(scoreBuild.toString());
|
Вот и завершился рекордный результат!
4. Закончите Как играть в активность
Шаг 1
У нас осталось одно задание — выполнить упражнение «Как играть». Откройте класс, который вы создали для него. Добавьте следующий импорт:
1
|
import android.os.Bundle;
|
Внутри класса добавьте onCreate и установите представление контента для созданного нами макета:
1
2
3
4
5
|
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_how);
}
|
Шаг 2
Теперь откройте свой основной класс Activity и запустите How to Play Activity из другого if для кнопки в onClick :
1
2
|
Intent helpIntent = new Intent(this, HowToPlay.class);
this.startActivity(helpIntent);
|
Вывод
Это приложение завершено! Запустите его, чтобы увидеть его в действии. В этой серии мы использовали структуры Java и Android для создания интерактивной арифметической игры, в которой пользователь пытается ответить на максимально возможное количество вопросов подряд. Мы реализовали несколько уровней и арифметических типов вопросов с операторами и операндами, адаптированными к игровой логике. Приложение автоматически сохраняет рекорды и данные о состоянии, с общими элементами приложения, включая отображение рекордов и полезную информацию об игре. Посмотрите, можете ли вы придумать какие-либо способы улучшить приложение и использовать его в качестве основы для будущего развития.