Пишем игру "Жизнь" под Android используя C++ и Qt за час.
Всем привет! Недавно видел на пабликах типа ТП посты мол "делаем игру жизнь на java за час", "делаем игру жизнь на python за час", вот решил сделать пост, делаем игру жизнь под андроид используя C++ и библиотеки Qt за час.
Важно отметить что игра занимает ~100 строчек кода(исключая h файлы, символы переноса и т.п.), а также то, что созданная игра будет работать и под винду, и под андроид, и под линукс, и под ios и под mac os x.
И так начнем. Правила игры жизнь я думаю вы знаете. Есть поле с клетками. Клетка может быть живая или мертвая. По прошествии хода, если вокруг мертвой клетки есть 3 живых, она становится живой. Если вокруг живой клетки будет 1 или 0 живых, то она становится мертвой, также она становится мертвой если вокруг нее более 3 живых клеток. Впринципе все, подробнее вы можете прочесть на википедии.
И так что у нас должно быть:
1. GUI.
Поле с N*M клеток, кнопка "Новая игра", кнопка "Начать эволюцию". Поле забито прямогуольниками, изначально все пустые. При нажатии на клетку она окрашивается в черный или белый цвет, в зависимости от того какой цвет был там раньше.
2. Логика. Мы должны иметь двумерный массив(используем QT'шный вектор), в котором 0 отмечена мертвая клетка а единицей живая клетка. У меня была цель создать игру примерно за час, и поэтому этот 0 и 1 соответствуют черному и белому цветам многоугольников. Соответственно у нас есть массив с указателями на наши прямоугольники. Это не хорошо, ибо логика должна быть отделена от GUI, но для уменьшения размера кода - решил сделать так.
Должны быть реализованы функции подсчета соседей для каждой из клеток, зная положение клетки по X и Y. После каждого хода, мы рассчитываем количество соседей для каждой из клеток, и в зависимости от состояния клетки и количества её соседей добавляем клетку в массив(вектор), для изменения её состояния. Если за 1 ход не было добавлено ни 1 клетки, то игра заканчивается, выводим пользователю количество ходов
Если вы не знакомы с наследованием и библиотекой STD в C++, то вы являетесь новичком в C++ и возможно код покажется вам запутанным. К сожалению не хотелось бы писать про все это в этом посте, ибо для новичков информации в интернете - тонны, а переписывать еще раз это - просто не зачем. Если вы с этим знакомы, но незнакомы с сигнально-слотовой системой Qt тоже порекомендую прочитать over9000 материалов по этой теме. Если потребуются пояснения я отвечу в комментах.
И так, как выглядит GUI:
Скачиваем Qt Creator с официального сайта Qt(qt.io), выбираем бесплатный вариант для GPL приложений. Запускаемся, создаем новый проект, используя QWidget, с графической формой
Переходим к созданию. Переходим в режим дизайн и добавляем на форму "Graphics View" и 2 Push Button. Примерно вот так как на следующей картинке, и кликая правой кнопкой мыши по форме выбираем (Скомпоновать по сетке)
GUI готово! Теперь нам нужно перейти к непосредственно программированию.
QGraphicsView - это виджет, который может отображать графические элементы. QGraphicsView отображает элементы, который расположены на QScene. На QScene мы размешаем элементы в декартовой системе координат, а с помощью QGrahicsView мы её отображаем(можно поворачивать, увеличивать масштаб и т.п.).
Элементы бывают разных типов. Есть простые - QGraphicsRectItem - это многоугольник, который к слову нам и понадобиться. Есть QGraphicsPixMapItem который отобразит нам изображение. Есть QGraphicsLineItem - это линия. И другие.
И так, первая задача. Разместить на сцене N*M QGraphicsRectItem'ов - наших клеток. Мертвой клеткой будет считаться клетка, с Kletka->brush().color() == Qt::white живой Kletka-> brush().color() == Qt::black. И запихнуть все указатели на QGraphicsRectItem в отдельный двумерный вектор, чтобы мы могли с ними работать.
Однако, для этой задачи нам не подойдет "чистый" QGraphicsRectItem. Нужно будет создать класс наследник, и в нем реализовать свою обработку нажатия на клетку. Ведь в самом начале игры нам нужно будет выставить первичные мертвые и живые клетки на поле.
Т.е. по нажатию на наш прямоугольник, он должен окрашиваться в требуемый цвет. А у чистого QGraphicsRectItem обработка данного события пустая.
Создадим класс LifeRect основанный на QGraphicsRectItrem., и переопределим в нем mousePressEvent
Теперь займемся реализацией. Конструктор будет использовать конструктор с такими же параметрами как у QGraphicsRectItem(позиция по x, позиция по y, широта, высота, родитель). В событии mousePressEvent будет вызываться функция changeColor.
Реализация liferect.c
И так, переходим дальше. Работаем с нашим виджетом(формой) - widget.c. В этом классе реализуется логика игры.
Что нам необходимо?
1. Таймер. Каждые 0.1 секунды будет происходить 1 ход.
2. Слоты нажатия на кнопку "Старт" и "Новая игра" соответственно.
3. Размеры поля - X и Y клеток. Принимаются через конструктор.
4. Переменная для хранения количества итераций(для вывода при завершении игры)
5. Двумерный вектор(используется QVector, а не std::vector) который хранит указатели на созданные нами LifeRect'ы. К его элементам мы будем обращаться, и будем представлять живое и мертвое состояние по его цвету, как я уже и писал ранее. Если среди читателей будут новички C++ в комментах могу помочь. Просто представляйте что это массив.
6. Функция для новой игры(обнуление поля, обнуления количества итераций).
7. Функция подсчета живых соседей рядом.
И так, начнем. Собственно вот заголовочный файл:
В конструкторе присвоим начальные данные(x,y из входных, 0 итераций, укажем то что сцена пока-что пустая, родительский элемент отсутствует. Проинициализируем таймер, но запускать его не будем
Создать слот on_pushButton нужно нажав правой кнопкой по кнопке на форме, выбрать перейти к слоту, и выбрать сигнал clicked()
Дальше - интереснее. И так, по нажатию на кнопку новая игра, мы должны нарисовать графическую сцену, с X*Y мертвыми многоугольниками, и наполнить двумерный вектор указателей на многоугольники ими же. Чтобы потом,когда игрок заполнит поле и нажмет на кнопку "СТАРТ" запускается таймер, по каждому тику таймера обходить этот вектор, вычислить количество живых соседей каждой клетки, и перерисовать требуемые.
Постарался максимально прокомментировать код.
К слову говоря, позиции осей X и Y в графической сцене таковы:
И так, мы прорисовали свои элементы, и уже можем потыкать и поменять их цвета, но пока-что не можем ничего запустить. Нужно реализовать функцию обработки каждого хода. И написать что по 2 кнопке мы запускаем таймер.
Осталось лишь только реализовать функцию подсчета соседей. Думаю, в ней проблем не будет. Мы просто смотрим на цвета наших 8 соседей(если вылезли за границы то перемещаемся в начало или конец соответственно) и выдаем на выход их количество
Все готово! Запускаем и играем на Linux, Windows или вашем Mac.
Что потребуется(если вкратце) для запуска на Android?
1. Пакеты Android SDK и Android NDK(для C++).
2. Устройство андроид или AVD.
3. Apache Ant.
Вы должны сконфигурировать ваш Qt Creator, в таком виде(ваши пути), и добавить в проектах сборку Android. Потребуется скачать в AVD драйвера USB, и API требуемых версий Android. Затем уже попробовать установить приложение на телефон/эмулятор. Я могу помочь, или привести ссылки где это подробно разбирается в комментах
Пробуем на стареньком LG Optimus One с Android 2.3.3. - все работает как часы и на старых андроидфонах. К слову говоря можете попробывать как все выглядет в реальности(перед созданием) по ссылке - https://play.google.com/store/apps/details?id=com.mousemove.... (без рекламы и прочего, как собрал так и залил)
Исходники - https://cloud.mail.ru/public/AsLq/4y4hufCQ7