Пишем тактическую игру про цифры под Android
Когда я только взялся за программирование (3 месяца назад), я быстро понял, что лучше сразу начинать заниматься своими проектами. Невозможно с утра до вечера сидеть за книгами или курсами, но если вы начнете делать что-то свое, то запросто просидите за разработкой с утра до утра.
Эта статья — небольшой туториал по тому, как сделать логическую игру с ботом. Игра будет выглядеть вот так:
*Подробно опишу правила еще раз в разделе про ИИ.
- Начали программировать несколько часов назад. Вам будет сложно, лучше предварительно пройдите какой-нибудь небольшой курс по введению в Android-разработку, разберитесь с двумерными массивами и интерфейсами. А потом загрузите проект с гитхаба. Комментарии и эта статья помогут вам разобраться, что и как работает.
- Уже умеете программировать, но еще не можете назвать себя опытными. Вам будет интересно, потому что вы очень быстро сможете сделать свою игру. Я взял на себя грязную работенку по построению логики игры и ui-составляющей, вам же оставляю творческую часть. Вы можете сделать другой режим игры (2 на 2, онлайн и т.п.), изменить алгоритмы бота, создать уровни и т.д.
- Опытные. Вам может быть интересно подумать над ИИ — написать его не так легко, как кажется на первый взгляд. Так же я был бы очень рад получить от вас замечания по коду — уверен, далеко не все я сделал оптимально.
Сейчас я заново создам проект (чтобы ничего не упустить) и последовательно опишу все шаги. Постараюсь писать код в хорошем тоне, но будут и плохие места, на которые я пошел ради сокращения объема.
- Создадим проект
- Напишем бота
- Напишем класс для игры
- Займемся ui
Все как обычно: создаем новый проект, далее-далее-далее-финиш. Учитывая, что часть аудитории может быть представлена группой «Начали программировать несколько часов назад», приведу подробную инструкцию.
Обратите внимания, проект делается в Android Studio. Вместо «livermor» в Company Domain укажите что-то свое
Поменяйте вверху Android на Project. На скрине приведен пример, как и где создавать классы.
Пишем ботаПервая идея, которая мне пришла, — просчитать все ходы до конца. Либо до n-го хода. Но как просчитывать ходы? Давайте введем понятие лучшего хода. Наверняка, это такой ход, который максимизирует разницу между вашим ходом и лучшим ходом соперника. То есть вы просчитываете свой лучший ход, основываясь на том, что соперник будет просчитывать ваш лучший ход, ожидая, что вы просчитываете свой лучший ход, основываясь… И так до n. Самый последний ход будет представлять собой просто максимальное число в ряду.
Как по-вашему, нормальный это алгоритм для бота?
На самом деле, это даже хуже, чем просто выбирать максимум. Вы уже догадались, в чем проблема?
Дело в том, что мы предполагаем, что соперник будет совершать этот лучший ход. Мы можем выбрать -2, ожидая, что соперник возьмет -3 (его лучший ход, который оправдается в конце партии), но соперник возьмет да и пойдет в +6. Тогда мы все пересчитаем и пойдем в -5, ожидая, что соперник сходит в -4, а он опять возьмет да и выберет +8. И так далее — мы всегда совершаете долгосрочные ходы, и всегда проигрываем здесь и сейчас.
Самый простой способ сделать этот алгоритм работоспособным — поставить n = 2. То есть предполагать, что соперник просто выберет максимум из своего ряда, и самим искать такой ход, который максимизирует разницу между нашими ходами. К слову, это сделает бота вполне конкурентным.
Я пошел немного дальше и попробовал сделать бота человечнее — дал ему жадность. Другими словами, я приказал боту делать краткосрочные ходы, если можно извлечь разницу в указанное количество очков. В коде я обозвал эту разницу джекпотом, и бот срывает джекпот, если на горизонте планирования это не приведет к досрочному поражению (в комментариях к коду я все описал подробнее).
И последнее, прежде чем вы будете создавать класс для бота, опишу детальнее, что он из себя представляет. Бот необходим классу Игра для того, чтобы получить номер хода (в строке или в ряду). Все данные, которые изменяются на протяжении игры — очки игроков, булева матрица с разрешенными ходами, номер последнего совершенного хода — будут храниться в классе Игра. Соответственно, создавая сущность класса Бот, нам необходимо передать ему только неизменяемые в течение одной партии вещи: играет ли бот за строки или за ряды и матрицу с числами.
У Бота есть один public метод — сделать ход, к которому мы обращаемся каждый раз, когда хотим получить ход. Соответственно, в этот метод мы передаем все изменяемые значения.
То, что я обозвал protected, может быть использовано для наследования — то есть создания детей бота, public — для пользования другими классами, private — внутренняя кухня, о которой другим классам лучше не знать.
Если вы практически ничего не поймете — это нормально, я так же проходил первые свои туториалы. Класс для Бота — самый сложный, дальше будет легче.
Пишем класс для игрыВначале я предупреждал, что будут плохие места в коде. И вот одно из них. Вместо того, чтобы сперва написать родительский класс для управления игрой, а потом расширить его до конкретного класса игры с ботом, я сразу напишу класс игры с ботом. Делаю это для сокращения туториала.
Игровой класс, назовем его Game, нуждается в двух вещах: 1. Интерфейс для работы с ui-элементами; 2. Размер матрицы.
Работаем над пользовательским интерфейсомУбедитесь, что вверху у вас стоит Project, а не Android. На данный момент у нас есть 3 класса: созданные нами Bot и Game и уже существующий класс MainActivity. Сейчас нам предстоит изменить несколько xml-документов (обведенных красным), создать еще один класс для цифр-кнопок и создать drawable-элемент (показываю черной стрелкой, как это делается).
1. Запрещаем экрану поворачиваться: 2. Добавляем нужные нам цвета: 3. Меняем тему приложения: 4. Устанавливаем размеры: 5. Создаем фоны для кнопок: 6. Изменяем макет экрана:Для матрицы я буду использовать GridLayout — возможно, не самое лучшее решение, но оно показалось мне довольно простым и коротким.
Просто замените имеющийся код на мой — там пустой GridLayout (заполним его кодом в MainActivity) и два TextView-элемента для показателей очков игроков (RelativeLayout внутри другого RelativeLayout — для того, чтобы выравнять все по центру по вертикали. View «center» — для выравнивания показателей очков к центру по горизонтали).
Да, и не беспокойтесь, в preview вы ничего не увидите, кроме верхней надписи Бот, так и должно быть.