Вернемся к разработке нашего первого приложения под Андроид. На прошлом уроке мы начали делать функционал (набор исполняемого кода и данных необходимых для выполнения определенных вычислительных операций) генерации числа, которое компьютер по правилом игры должен “загадать”. Напоминаю, нам нужны следующие условия для такого числа:
- Число должно состоять только из 4-х цифр
- Все цифры числа должны быть разными
Методов решения этой задачи несколько, по этому нужно просто какой-то выбрать. Заранее скажу как я буду это делать. Сначала я получу 4 цифры числа случайным образом таким образом, чтобы каждое следующее случайное число – значение цифры загадываемого числа, отличалось от уже всех ранее сгенерированных. Это даст выполнение правила, что все цифры числа должны быть разными. С первым условием всё просто, мы просто будем генерировать от 1 до 4-х.
Однако, прежде чем разбираться как же тут получается число со всеми необходимыми условиями, необходимо отвлечься и познакомиться с некоторыми конструкциями кода. В Java есть 3 вида циклов, т.е. конструкций кода, позволяющих несколько раз выполнять один и тот же код (циклическое выполнение).
Первый из них мы уже много раз встречали – это обычный цикл for. В области параметров этого цикла есть три части кода, разделенные точкой с запятой. Первая часть инициализирует переменную (задаёт ей начальное значение), вторая часть проверяет соблюдается ли условие (будет ли результат этого условия равен значению true), третья часть – выражение, которое должно выполняться после каждой итерации (этап прохождения по циклу). Пока значение второй части true, цикл будет совершать итерации (хоть до бесконечности). Давайте это рассмотрим прямо из нашего нового метода:
for (int i = 1; i < 5; i++) {}
Сначала объявляем и инициализируем переменную i значением 1. Во второй части будем сравнивать – значение переменной i для выполнения итерации должно быть меньше 5. В третьей части производим инкремент этой переменной (тема прошлого урока). Таким образом, каждую итерацию i будет увеличиваться на единицу и в конечном итоге достигнет значения 5. Условие i < 5 перестанет выполняться, цикл завершится и управление будет передано первому выражению следующему за циклом (за закрывающей скобкой этого цикла).
Вторая конструкция цикла у нас тоже в этом методе есть и выглядит так:
do {
} while (isNoOk);
Тут цикл начнёт выполняться без всяких условий сразу же, но вот будет ли выполнен повторно определяется условием после завершения этого цикла, что не скажешь о предыдущем, где чтобы начать выполнение цикла (вхождение в цикл) нам требовалось условие i < 5, это же условие требовалось и для продолжения его выполнения. Тут, как понимаете, по логике получается немного по другому. Тут стоит обратить внимание, что если переменная i в цикле for (который ещё иногда называют по-старинке “цикл for…next”) видима как в объявлении цикла, так и внутри него, то в цикле “do…while” нет инициализации, а все используемые там переменный должны быть либо объявлены где-то перед этим циклом, либо иметь более глобальную видимость, например, быть переменными класса (один из прошлых уроков). В связи с этим получается, что переменная isNoOK должна быть объявлена перед этим циклом. Если посмотрите на код метода generateComNumber(), я именно так и сделал. Итак, подытожим. Цикл do…while начинает выполняться без всяких условий и будет выполняться до тех пор, пока значение внутри while будет true, т.е. например, цикл
do { }
while(true);
будет выполняться “вечно” и никогда не завершиться, пока работа приложения не будет прервана извне этого приложения (действиями пользователя или действиями операционной системы). По этому задача программиста сделать так, чтобы внутри этого цикла было что-то, что может поменять условия для выхода из цикла. Для этого я выполняю действия влияющие на это:
do {
num = random.nextInt(10);
isNoOK = compNumber.indexOf(“” + num) > -1;
} while (isNoOK);
Сначала я получаю случайное число num в диапазоне 0…9, затем преобразовываю его в строку и пытаюсь найти в уже сформированной итоговой строке compNumber. Функция indexOf как раз это и делает. Если искомая строка не найдена, результат будет равен минус 1, а если найдена, то позиции искомой строки и строке поиска. Сама позиция нам не нужна, тут нам просто нужно проверить есть ли это в строке. Обратите внимание как я получаю из числа строку. На одном из прошлых уроков я говорил, что иногда компилятор правильно делает такое преобразование если ничего не указывать, но по всем правилам преобразование нужно делать через String.valueOf(). Но есть и ещё метод – это прибавить число к пустой строке:
“”+num
Компилятор видит перед знаком плюс строку (пусть она и пустая) и понимает, что нужно производить конкатенацию строк, а значит число справа скорее всего не какой-то там идентификатор ресурса, а обычное число.
Стоит сразу же сказать и о ещё одном виде цикла, который используется наравне с этими двумя. Это цикл
while(условие) {
}
Он является почти аналогом цикла do…while, но отличается только тем, что для первого вхождения в цикл нужно выполнения условия (для do…while этого не требовалось).
Из любого цикла можно выйти и не дожидаясь пока итерация закончится и наступит момент проверки условий. На примере цикла while делается это примерно так:
while (true) {
if (какое-то условие) {
break;
}
/* ничего этого выполнено не будет, если сработает break */
}
/* сюда будете передано управление после выполнения break */
В этом примере казалось бы условие цикла всегда будет true и цикл будет выполняться “вечно”, но это не так, т.к. внутри цикла встроена конструкция из условия, по истинному результату которой, будет выполнена команда break. Это команда прерывает цикл, выходит из него прямо с этой команды.
ВАЖНО! Команда break выходит только из текущего цикла. Если один цикл вложен в другой, выход будет осуществлён только из текущего цикла, но НЕ(!) из вышестоящего.
Все циклы имеют команду принудительной итерации. Это команда continue. Она работает похоже как break, только не прерывает цикл, а делает так, что цикл переходит на следующую итерацию, конечно же, если условия цикла, требуемые для этого, выполняются:
while (true) {
if (какое-то условие) {
continue;
}
/* ничего этого выполнено не будет, если сработает continue */
}
/* в данном примере здесь никогда управление не окажется, т.к. нет ничего чтобы прервало цикл. Это “вечный” цикл */
В следующем примере:
Цикл станет “вечным”, т.к. выход по break будет только из внутреннего цикла, но внешний продолжит выполнение. Это касается циклов всех типов.
Теперь, основываясь на наших новых знаниях о циклах, рассмотрим более подробно мой метод генерации числа. Сначала я объявил экземпляр класса Random и поместил его в переменную random. Этот класс позволяет программисту работать с генератором случайных чисел. Далее следует сброс compNumber – это строкова переменная класса, которую вы должны были добавить в домашнем задании. Далее делаем цикл от 1 до 4, а внутрь этого цикла помещаем ещё один, который будет выполняться до тех пор, пока не сгенерирует цифру числа, которой ещё нет в загадываемом числе. Когда функционал находит такую цифру, он конкатенирует её к compNumber и процесс повторяется 4 раза. Таким образом, в результате будет необходимое число. Посмотрите внимательно каждую команду этого метода generateComNumber(). Вы должны хорошо понимать какое действие будет выполняться в каждом выражении. Попробуйте мысленно или на бумаге воспроизвести как будет меняться значение compNumber при каждой итерации каждого цикла; мысленно воспроизведите как будет осуществляться вхождение в цикл do…while и выход из него при каких словиях.
Сделайте теперь так, чтобы этот метод запускался сразу при загрузке приложения, т.к. именно в этот момент игра уже должна быть готова принять первый вариант пользователя, желающего угадать число (чтобы было что отгадывать):
Теперь осталось дело за малым – при вводе варианта пользователя просто посчитать число быков и коров, и если количество быков равно 4, то значит пользователь отгадал число, в ином случае сказать пользователю каков результат его попытки.
Для начала напишем две эти функции, одна будет находить число быков, а вторая – число коров.
В домашнем задании вы должны были хорошо познакомиться с функцией substring() и что она делает в этом функционале, вы наверно уже поняли. Да, одна функция берет один символ из строки по индексу i из строки textNumber, другая тоже самое из строки compNumber. Первая переменная содержит число, которое ввел пользователь, а второе – которое “загадал” компьютер. Обратите внимание как происходит сравнение двух строк:
Строка1.equals(Строка2)
значение выражения истина, если Строка1 содержит тот же набор символов в той же последовательности, что и Строка2. В данном случае строки содержат по одному символу.
При расчете коров потребовалось выполнить цикл в цикле для того чтобы исключить из подсчета количество быков, которые бы неминуемо туда тоже попали, а по правилам игры корова – это совпадающая цифра числа находящаяся в другом разряде, но не в том же.
Очень внимательно посмотрите функцию подсчета коров. Вам должны быть понятно все от начала и до конца что там происходит. Если что-то оказывается неясным, лучше задать вопрос и разобраться в этом сейчас.
Осталось теперь только выполнить проверку после ввода числа, по этому допишите код для обработки события нажатия на соответствующую кнопку:
Проверьте выполнение кода, должно получиться как-то так:
Обратите внимание на всплывающее сообщение Toast. Как правильно его использовать в коде и какой будет результат. Такие сообщения вы наверно иногда встречаете в приложениях, когда на несколько секунд появляется информация поверх экрана, а потом сама исчезает. Для тестирования в данном случае такое подойти может, а вот для нашего игрового процесса нет. Нам нужно чтобы каждый вариант с результатом хранился в списке, и мы всегда могли посмотреть его, проанализировать и принять решение для следующего хода. Однако, список требует свежего восприятия, по этому начнём им заниматься на следующем уроке.
Задание по уроку:
1. Ответьте на вопрос, в функции подсчета коров сколько раз будет выполнена проверка условия “if (number.substring…”?
2. Чему будет равна сумма p+q после выполнения следующего кода:
3. Самостоятельно разберитесь с тем, как правильно сделать выход из двух вложенных циклов, если во внутреннем цикле наступает такое требование.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 5 Урок 7
El Vinto, 2023 (Copyright)