Статьи

Android SDK: создание арифметической игры — логика геймплея

В этой серии мы создаем простую арифметическую игру для Android. Игра будет отображать интерфейс в стиле калькулятора для пользователей, постоянно задавая вопросы и отслеживая, сколько правильных ответов они наберут подряд. В первой части серии мы создали пользовательский интерфейс, настроили основной класс Activity и заложили основы еще для трех классов Activity. В этой части мы будем работать над логикой игрового процесса, когда пользователь выбирает один из трех возможных уровней сложности. Мы реализуем игровой процесс вопросов и ответов с помощью четырех операторов: сложение, вычитание, умножение и деление. Игра выберет оператора и операнды случайным образом, причем процесс настраивается для каждого оператора.


Эта серия статей о создании арифметической игры будет выпущена в трех частях:


Ниже приведен скриншот игры, в которой я научу вас строить:

Арифметическая игра

Основное задание представляет три варианта: «Играть», «Как играть» и «Лучшие результаты». После нажатия опции Play пользователь должен выбрать уровень сложности, после чего начнется игровой процесс. Каждый ответ, введенный пользователем, будет отмечен галочкой или крестиком в качестве ответа, а счет будет увеличиваться до тех пор, пока пользователь не введет неправильный ответ или не выйдет из игры. Мы завершим функциональность «Как играть» и «Лучшие результаты» в заключительной части серии, а также сохраним состояние экземпляра приложения.


В прошлый раз мы подготовили основной класс Activity для прослушивания нажатий на три кнопки — теперь давайте реализуем то, что происходит, когда пользователь нажимает кнопку Play. Сначала добавьте следующий импорт в основной класс приложения:

1
2
3
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;

Мы хотим, чтобы пользователь выбрал один из трех уровней для запуска игры — добавьте переменную экземпляра в класс, чтобы сохранить имена уровней в массиве:

1
private String[] levelNames = {«Easy», «Medium», «Hard»};

В вашем методе onClick создайте диалог Alert в операторе if, который мы создали для кнопки Play.

1
AlertDialog.Builder builder = new AlertDialog.Builder(this);

Теперь установите детали диалога, передав массив имен уровней и настроив прослушиватель щелчков для параметров:

1
2
3
4
5
6
7
8
builder.setTitle(«Choose a level»)
    .setSingleChoiceItems(levelNames, 0, new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            dialog.dismiss();
            //start gameplay
            startPlay(which);
        }
    });

Когда уровень выбран, метод onClick для Alert Dialog вызывает вспомогательный метод, который мы добавим далее, передавая индекс выбранного уровня из массива. Прежде чем мы реализуем вспомогательный метод, все еще внутри оператора if для кнопки Play, создайте и покажите диалоговое окно:

1
2
AlertDialog ad = builder.create();
ad.show();
Выбор уровня

Теперь добавьте вспомогательный метод в класс после метода Activity onClick :

1
2
3
4
5
6
7
private void startPlay(int chosenLevel)
{
    //start gameplay
    Intent playIntent = new Intent(this, PlayGame.class);
    playIntent.putExtra(«level», chosenLevel);
    this.startActivity(playIntent);
}

Запускаем игровой процесс Activity, передавая номер уровня Intent. Мы будем реализовывать щелчки на двух других кнопках в заключительной части серии.


В прошлый раз мы создали класс Activity для игрового процесса. Eclipse должен был вставить следующую структуру структуры с любыми выбранными вами именами:

1
2
3
4
5
6
7
package com.example.youdothemath;
 
import android.app.Activity;
 
public class PlayGame extends Activity {
//class content
}

Добавьте следующие операторы импорта в ваш класс:

1
2
3
4
5
6
7
import java.util.Random;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

Вставьте метод onCreate и установите представление контента для макета, который мы создали в прошлый раз:

1
2
3
4
5
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_playgame);
}

Расширьте строку открытия объявления класса, чтобы реализовать прослушивание щелчка:

1
public class PlayGame extends Activity implements OnClickListener

Давайте добавим некоторые переменные экземпляра в класс для облегчения игрового процесса. Сначала целые числа для представления уровня, операндов, оператора и ответа:

1
private int level = 0, answer = 0, operator = 0, operand1 = 0, operand2 = 0;

Далее определим некоторые константы для четырех операторов, которые упростят обработку игрового процесса:

1
private final int ADD_OPERATOR = 0, SUBTRACT_OPERATOR = 1, MULTIPLY_OPERATOR = 2, DIVIDE_OPERATOR = 3;

Теперь добавьте массив для текста, который будет отображаться для каждого из этих операторов, с индексом массива, соответствующим константе в каждом случае:

1
private String[] operators = {«+», «-«, «x», «/»};

Диапазон операторов будет зависеть от уровня, выбранного пользователем, и используемого оператора (т.е. операнды сложения для простого уровня будут иметь другое возможное минимальное и максимальное число, чем операнды, используемые с другими операторами и на других уровнях) , Мы будем использовать генератор случайных чисел для выбора операндов, с минимумом и максимумом в каждом случае. Определите минимальное и максимальное числа для каждого оператора и каждого уровня, используя следующие двумерные массивы:

01
02
03
04
05
06
07
08
09
10
private int[][] levelMin = {
    {1, 11, 21},
    {1, 5, 10},
    {2, 5, 10},
    {2, 3, 5}};
private int[][] levelMax = {
    {10, 25, 50},
    {10, 20, 30},
    {5, 10, 15},
    {10, 50, 100}};

Каждый массив представляет уровни и операторы, с указанием минимума или максимума для трех уровней для сложения, затем вычитания, затем умножения, затем деления — используя тот же порядок, что и определенные нами константы. Например, минимальный операнд для сложения на средней сложности — 11, а максимальный операнд для вычитания на сложной сложности — 30. Цель этого станет более ясной, когда мы реализуем часть игрового процесса, где мы генерируем вопросы. Вы можете изменить минимальное и максимальное количество позже, если хотите.

Затем добавьте переменную экземпляра для генератора случайных чисел, который мы будем использовать в классе:

1
private Random random;

Наконец, добавьте переменные экземпляра для элементов пользовательского интерфейса, которые мы определили в файле макета в прошлый раз, включая текстовые представления для вопроса, ответа и оценки, представление изображения для изображения галочки или перекрестного ответа и кнопки для цифр 0-9 плюс ввод и ясно:

1
2
3
private TextView question, answerTxt, scoreTxt;
private ImageView response;
private Button btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, enterBtn, clearBtn;

Теперь давайте обратимся к методу onCreate . После существующего кода получите ссылки на текстовые и графические представления:

1
2
3
4
question = (TextView)findViewById(R.id.question);
answerTxt = (TextView)findViewById(R.id.answer);
response = (ImageView)findViewById(R.id.response);
scoreTxt = (TextView)findViewById(R.id.score);

Установите исходное изображение для галочки / перекрестного ответа, чтобы оно было изначально невидимым:

1
response.setVisibility(View.INVISIBLE);

Далее получите ссылки на номера, очистите и введите кнопки:

01
02
03
04
05
06
07
08
09
10
11
12
btn1 = (Button)findViewById(R.id.btn1);
btn2 = (Button)findViewById(R.id.btn2);
btn3 = (Button)findViewById(R.id.btn3);
btn4 = (Button)findViewById(R.id.btn4);
btn5 = (Button)findViewById(R.id.btn5);
btn6 = (Button)findViewById(R.id.btn6);
btn7 = (Button)findViewById(R.id.btn7);
btn8 = (Button)findViewById(R.id.btn8);
btn9 = (Button)findViewById(R.id.btn9);
btn0 = (Button)findViewById(R.id.btn0);
enterBtn = (Button)findViewById(R.id.enter);
clearBtn = (Button)findViewById(R.id.clear);

Прослушайте клики на всех этих кнопках:

01
02
03
04
05
06
07
08
09
10
11
12
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
btn3.setOnClickListener(this);
btn4.setOnClickListener(this);
btn5.setOnClickListener(this);
btn6.setOnClickListener(this);
btn7.setOnClickListener(this);
btn8.setOnClickListener(this);
btn9.setOnClickListener(this);
btn0.setOnClickListener(this);
enterBtn.setOnClickListener(this);
clearBtn.setOnClickListener(this);

Теперь извлеките номер уровня, который мы передали из основного Activity при запуске этого:

1
2
3
4
5
6
Bundle extras = getIntent().getExtras();
if(extras != null)
{
    int passedLevel = extras.getInt(«level», -1);
    if(passedLevel>=0) level = passedLevel;
}

Переменная уровня изначально была установлена ​​на ноль, поэтому, если число не получено, игра по умолчанию устанавливается на легкий уровень. После этого оператора if , все еще находящегося внутри onCreate , инициализируйте генератор случайных чисел:

1
random = new Random();

Наконец, вызовите вспомогательный метод для запуска игры — мы реализуем метод следующим образом:

1
chooseQuestion();

Добавьте метод, который вы вызвали в onCreate, к своему классу:

1
2
3
private void chooseQuestion(){
//get a question
}

Этот метод будет выполняться каждый раз, когда нам понадобится новый вопрос. Он будет выбирать оператора и операнды случайным образом в пределах диапазона, указанного для оператора и уровня. Метод выведет вопрос в пользовательский интерфейс, готовый к ответу пользователя.

Внутри нового метода сначала сбросьте текстовое представление ответа:

1
answerTxt.setText(«= ?»);

Это необходимо, поскольку в текстовом представлении ответа будет отображаться введенный пользователем ответ, поэтому его необходимо сбрасывать при каждом создании нового вопроса. Выберите произвольно выбранный оператор, убедившись, что он находится в диапазоне массива операторов (в нем четыре, но вы можете изменить это):

1
operator = random.nextInt(operators.length);

Выберите два случайных операнда, используя другой вспомогательный метод:

1
2
operand1 = getOperand();
operand2 = getOperand();

После вашего метода chooseQuestion добавьте новый вспомогательный метод в класс для выбора операндов:

1
2
3
private int getOperand(){
//return operand number
}

Для этого мы используем вспомогательный метод, так как он может вызываться повторно. Метод вернет целое число в соответствующем диапазоне. Добавьте содержание метода:

1
2
return random.nextInt(levelMax[operator][level] — levelMin[operator][level] + 1)
    + levelMin[operator][level];

Этот код гарантирует, что целое число находится в правильном диапазоне для текущего оператора и уровня.

Вернувшись в свой метод chooseQuestion , теперь вам нужно добавить некоторые квалификации в зависимости от оператора. Например, мы не хотим разрешать отрицательные ответы, поэтому операторы вычитания не должны давать отрицательный результат. Добавить условный тест:

1
2
3
4
5
6
if(operator == SUBTRACT_OPERATOR){
    while(operand2>operand1){
        operand1 = getOperand();
        operand2 = getOperand();
    }
}

Если второй операнд больше первого, мы просто продолжаем генерировать операнды, пока не получим пару, в которой первый операнд больше. С оператором деления нам нужны только ответы с целыми числами, поэтому добавьте еще одно условие:

1
2
3
4
5
6
7
else if(operator==DIVIDE_OPERATOR){
    while((((double)operand1/(double)operand2)%1 > 0) || (operand1==operand2))
    {
        operand1 = getOperand();
        operand2 = getOperand();
    }
}

На этот раз мы продолжим до тех пор, пока не получим деление, результатом которого будет целое число, также избегая случаев, когда два операнда равны Вы можете, конечно, доработать этот код, чтобы повысить эффективность игры.

Теперь у нас есть правильный набор операндов для выбранного оператора, поэтому давайте посчитаем ответ, используя оператор switch:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
switch(operator)
{
    case ADD_OPERATOR:
        answer = operand1+operand2;
        break;
    case SUBTRACT_OPERATOR:
        answer = operand1-operand2;
        break;
    case MULTIPLY_OPERATOR:
        answer = operand1*operand2;
        break;
    case DIVIDE_OPERATOR:
        answer = operand1/operand2;
        break;
    default:
        break;
}

Мы будем использовать это, чтобы проверить ответ пользователя позже. Теперь мы можем отобразить вопрос пользователю:

1
question.setText(operand1+» «+operators[operator]+» «+operand2);

Теперь у нас есть вопрос, отображаемый для пользователя, мы просто должны ответить на его нажатия на кнопки. Добавьте метод onClick в ваш класс:

1
2
3
4
5
@Override
public void onClick(View view)
{
//button clicked
}

Узнайте, какая кнопка была нажата:

1
2
3
4
5
6
7
8
9
if(view.getId()==R.id.enter){
    //enter button
}
else if(view.getId()==R.id.clear){
    //clear button
}
else {
    //number button
}

Помните, что мы настраиваем прослушиватели щелчков для цифровых кнопок, кнопки ввода и кнопки очистки. Сначала мы проверяем кнопки ввода, затем очищаем. Если событие click сработало и оно не было ни одним из них, это должна быть цифровая кнопка, которую мы будем обрабатывать в блоке else .

Давайте сначала обработаем кнопку очистки. В противном случае для кнопки очистки просто сбросьте текстовое представление ответа:

1
answerTxt.setText(«= ?»);

Теперь давайте обработаем цифровые кнопки. В блоке else начните с установки скрытого изображения галочки / крестика, поскольку мы не хотим, чтобы при отображении ответа пользователь отображал отзывы:

1
response.setVisibility(View.INVISIBLE);

Помните, что мы устанавливаем теги, представляющие соответствующие номера в каждой цифровой кнопке, когда мы добавляем их в макет. Это означает, что мы можем обрабатывать все цифровые кнопки одинаково, получая число, нажатое из тега:

1
int enteredNum = Integer.parseInt(view.getTag().toString());

Теперь у нас есть два возможных сценария: пользователь вводит первую цифру ответа или следующую цифру. Проверьте текущий отображаемый ответ и ответьте соответственно, установив или добавив только что нажатую цифру:

1
2
3
4
if(answerTxt.getText().toString().endsWith(«?»))
    answerTxt.setText(«= «+enteredNum);
else
    answerTxt.append(«»+enteredNum);

Наконец, мы можем обрабатывать нажатия на кнопку ввода. В блоке if для кнопки ввода сначала получите ответ, введенный пользователем, из текстового представления ответа:

1
String answerContent = answerTxt.getText().toString();

Проверьте, есть ли у нас ответ, если нет, мы ничего не делаем:

1
2
3
4
if(!answerContent.endsWith(«?»))
{
    //we have an answer
}

Тексту ответа всегда предшествует текст «=», с «=?» отображается, пока пользователь не нажмет цифровую клавишу, поэтому мы знаем, что у нас есть ответ. Внутри блока if теперь мы можем получить введенное число:

1
int enteredAnswer = Integer.parseInt(answerContent.substring(2));

Когда мы проверим ответ, мы обновим счет, поэтому вызовем другой вспомогательный метод, чтобы получить его:

1
int exScore = getScore();

После метода onClick добавьте этот новый метод, чтобы вернуть текущий счет, читая текстовое представление счета:

1
2
3
4
private int getScore(){
    String scoreStr = scoreTxt.getText().toString();
    return Integer.parseInt(scoreStr.substring(scoreStr.lastIndexOf(» «)+1));
}

Вернувшись в блок onClick для кнопки ввода, проверьте правильность ответа пользователя:

1
2
3
if(enteredAnswer==answer){
    //correct
}

Внутри этого блока ответьте на правильный ответ:

1
2
3
scoreTxt.setText(«Score: «+(exScore+1));
response.setImageResource(R.drawable.tick);
response.setVisibility(View.VISIBLE);

Мы обновляем текст партитуры, отображаем изображение галочки и настраиваем отображение изображения. После блока if добавьте else для неправильных ответов:

1
2
3
else{
    //incorrect
}

Внутри этого блока ответьте на неправильный ответ:

1
2
3
scoreTxt.setText(«Score: 0»);
response.setImageResource(R.drawable.cross);
response.setVisibility(View.VISIBLE);

Мы сбрасываем счет на ноль и отображаем изображение креста. Наконец, после остального выберите другой вопрос:

1
chooseQuestion();
Экран игровой активности

И так игра продолжается …


Мы завершили вторую часть серии! На этом этапе вы сможете запустить свое приложение и играть в игру, хотя экраны «Как играть» и «Лучшие результаты» пока не будут работать — мы завершим их в последнем уроке. В этом уроке мы реализовали логику игрового процесса, обрабатывая и реагируя на взаимодействие с пользователем. В заключительной части серии мы будем сохранять и восстанавливать рекорды, используя приложение Shared Preferences. Мы также сохраним данные состояния экземпляра, чтобы приложение продолжало функционировать после прерывания игры.