Добавлен , опубликован
Осваивая новый для себя язык Python и потратив две недели вечеров на ознакомление с ним, мне захотелось подняться на уровень выше и выбраться из tutorial hell, и сделать что-то большее, чем решение элементарных алгоритмических задачек, завернутых в 1 метод из 5-10 строк. Популярная игра "snake" показалась отличной идеей из-за маленького объема проекта и названия игры, которое комбинируется с языком, на который она написана. "Snake - Python" - "Змея на Змее".
Всего на проект ушло около недели вечерних посиделок, среди которых были разного рода проблемы и трудности, о которых я расскажу далее.
Я разделю повествование на этапы.
  1. Первые шаги и ожидания.
Идея сделать клон игры "snake" по началу оценивалась мной как очень легкий и маленький по объему проект. И так оно, собственно, и есть. Посмотрев на реализации других людей этой игры на Python, она зачастую укладывалась в один .py файл с размером около 200 строк. Но то, что меня отталкивало, так это уродский дизайн такого проекта, где вся игра заключается в один огромный метод, состоящий из длинного цикла на эти самые 100-200 строк. А если я захочу спустя время вернуться и что-то добавить или поправить, выдержат ли поиск нужной мелочи в этом болоте мои глаза?
На картинке ниже пример одного из популярных github-репозиториев по "змейке" на Python. Цикл в который записана вся игра начался и ушел в бесконечность.
Вдобавок, мне захотелось пощупать классы и объекты, создать из них разного рода взаимодействия и схемы. Поэтому было решено немного усложнить задачу и задизайнить это в ООП-стиле, так качественно, как я на своём текущем уровне могу это сделать. Разделение однофайловой простыни на модули поможет удобней поддерживать проект в будущем, сосредотачиваясь на этих самых модулях. (Спойлер: а в будущем правок будет очень много, поэтому подход окупился многократно).
  1. Pygame, боль и ненависть.
Если бы мне пришлось еще раз делать какую-либо игру, даже такую примитивную, как snake, я бы никогда в жизни не стал использовать библиотеку pygame, да и в целом Python. Я бы выбрал нормальный game engine для таких проектов.
Но это сейчас, а в начале своего пути я не видел ничего плохого в этом, просто мемное сочетание "сделать змейку на змейке", вдобавок все проекты, которые я видел на GitHub или YouTube, использовали именно pygame.
Возможно, я предвзят, и эта библиотека не так плоха, как кажется, да и использовать что-то стороннее я хотел как можно меньше. От сторонней библиотеки мне нужно было немного вещей:
  • Game-loop, чтобы моё приложение работало в реальном времени и относительно этого времени можно было бы выстраивать механизмы игры, создавая покадровую логику.
  • Создание окна и отрисовка в нем змеек, локации и ui элементов типа кнопок и текста.
  • Поддержка считывания ивентов с нажатия клавиатуры.
  • Коллизия объектов, которую я не нашел и поэтому написал сам, а она на самом деле была.
Не передать словами, сколько раз на самых простых и логичных вещах терминал выдавал мне сообщение об ошибке при запуске кода. Где-то все работало не так, как должно было быть. Допустим, каждый кадр надо было закрашивать окно черным цветом, чтобы "очистить" его, иначе все, что отрисовывалось в предыдущем кадре, сохранялось на экране, показывая змейку, которая ничего не ест, а бесконечно растет. Хотя в коде она адекватно себя ведет.
Не зная специфику того, как работает pygame, вы обрекаете себя на серьезные муки. А на это уйдет время. И пока вы увидите все возможные грабли, у вас явно поубавится желание продолжать свой проект. Тем более, когда вы недооцениваете задачу, надеясь взять проект наскоком, не желая тратить большое время на изучение технологии, которую используете.
Но так или иначе, вкус первой крови на второй вечер разработки был получен. У меня получилось создать окно, внутри которого бегала змейка. Радость заглушила воспоминания о боли из предыдущего вечера. Пробил это - пробью и следующие трудности.
Дальше была боль по части того, что game-loop не работал, потому что... в нем должны были содержаться pygame-ивенты, даже если они никак не используются далее. Какая-то переменная внутри цикла должна была получать класс ивентов, и только тогда все начинало работать. Пробил это - пробью и следующие трудности. Из-за этого далее возникли проблемы с тем, как вынести класс обработки ивентов из игрового цикла в отдельный модуль. Но это уже не было проблемой по морали. Рано или поздно ответ находился в интернете, или я находил его с помощью опытов. Тяп-ляп...
И вот змейка начинает бегать по сцене и кушать яблоки.
  1. Бесконечное усложнение задачи:
Следующие проблемы и задачи, которые я постоянно себе выдумывал, были на порядок сложней того, что я встретил в первые дни. Теперь трудностей не возникало, но я по ним скучал. Когда у меня что-то шло слишком легко, я намеренно усложнял себе задачу, из-за чего некоторые модули игры были переписаны с нуля по несколько раз.
Не сказать, что во всех случаях, когда я намеренно усложнял задачу, это было зря. Я отработал немалое количество вещей, которые хотел, но в целом лучше заранее иметь ограниченное ТЗ и не пытаться прыгать выше него, иначе это может уйти в бесконечность.
Первоначально предел ожиданий от того, что я за условную неделю напишу, заключался в сцене со змейкой, которая кое-как бегает по экрану и ест яблоки.
Кто мог подумать, что я захочу прикрутить к этому главное меню, несколько игровых режимов и чуть не покусился на машинное обучение?
Проект, который задумывался на неделю, мог украсть у меня месяц жизни, а то и более. А ведь на сэкономленные дни можно лежать на диванчике и ничего не делать.
Вот, допустим, я сделал сцену с главным меню, где код реагирует на нажатие мышкой по кнопкам.
  1. Паттерн State
Переписывание модулей из-за расширения функционала, параметризация вещей, которые раньше были привязаны к конкретным числам.
Итак, вы никогда не пользовались ООП на Python, вы не знаете, как поведет себя незнакомая библиотека pygame, но вы усложнили себе задачу, решив сделать игру, которая будет переключать несколько сцен, внутри которых игра будет заново инициализировать все, в том числе и игровой экран?
Поздравляю вас, если у вас есть опыт разработки на других технологиях.
Я действительно не знал, как из одного .py файла, создающего игровое окно и инициализирующего pygame, передавать данные в другое, делающего тоже самое. Возможно ли это, какие грабли я встречу и стоит ли оно того... И мне очень хотелось потренировать ООП в Python. Поэтому я прибегнул к реализации одного из своих первых и надежных паттернов проектирования, паттерна State.
Вот моя попытка проиллюстрировать его суть на этой замечательной клинописи:
В чем суть? Игровое окно, гейм луп и все самое важное инициализируются только ОДИН РАЗ в классе Context. Дальше, каждый кадр в классе Context обрабатывается игровая логика текущего мода игры. Текущий мод игры может в любой момент обратиться к классу Context и запросить смену мода. Экран очистится и проинициализируется другой мод. И так моды игры могут меняться до бесконечности.
Ниже пример того, как меняются моды главного меню (class MainMenuState) и одиночной игры (class SinglePlayerState). Они имеют схожего родителя (class State), и внутри класса Context для компактности кода они хранятся в переменной класса StateList. При создании StateList они проходят первую инициализацию, подготавливаясь к тому, что класс Context будет ими постоянно жонглировать.
Казалось бы, просто меняются окна, запускается окно меню, выключается после нажатия кнопки, далее запускается окно режима одиночной игры, и обратно, когда змейка погибает. Но на самом деле, это иллюзия, запускается только одно окно во время старта игры, а дальше меняется его содержимое.
  1. Еще режимы и бесконечные усложнения ТЗ.
Далее я решил для удобства глаз выделить часть черного экрана под текст, чтобы видеть, сколько очков набрал игрок, а сама игра происходила в сером участке карты, выходя за пределы которого змея погибала. И все резко поменялось, так как прежняя логика отрисовки и перемещения змейки была не разделена и опиралась на одни и те же координаты, которые внезапно получили смещение.
  • Переписывание класса змейки с разделением игровой логики от визуализации...
Теперь в игровой логике змейка двигалась по координатам от (1,1) до (40,40), например. И на этой системе счисления проверялись коллизии с объектами, типа яблока или границ игровой зоны. А отрисовка уже на их основе использовала свои коэффициенты для смещения и отрисовки игровых объектов. Поздравляю, почти весь код змейки был переписан.
Этого мне тоже показалось мало, я захотел, чтобы был режим, где игрок играет против бота... Поэтому пришлось добавлять змейке поддержку коллизий с чем-то кроме яблока, а это вылилось в отдельный вспомогательный класс Collisions, который знает обо всех змейках на сцене и имеет функционал проверки, не столкнулась ли одна змея с другой... И еще раз класс змейки был переписан.
Также змейка перемещалась с помощью нажатия клавиатуры, а змейка-бот должна это игнорировать и перемещаться на основании своего ИИ. Еще раз класс змейки был переписан, а вместе с ним и другие классы, которые входят в её зависимость...
Так или иначе, заветная змейка-бот стала жить, пусть она делала странные вещи, но по крайней мере вся база была налажена.
  1. Улучшение ИИ и прекращение усложнения ТЗ.
В начале была мысль сделать алгоритм поиска пути A* или Дейкстры, создать такую матрицу, которая каждый кадр обновляла бы свое состояние, считывая с экрана всех змеек как препятствия и вычисляя путь, на основе которого будет сделан следующий шаг конкретной змеи. Фантазии занесли меня даже в машинное обучение, чтобы змейка обучалась этому с каждой новой игрой, становясь умнее и лучше... Но, подождите... Что я вообще собирался сделать?
Я хотел сделать примитивную змейку без всяких этих ваших меню, странных решений, простую змейку, которая бегает по экрану и кушает яблоки, а где я теперь?
Змейке был добавлен простой алгоритм ИИ, который каждый кадр на основе ее координат и координат яблока предпринимал какой-то логичный шаг, будь то поворот в сторону яблока или разворот, если она приближается к границам игровой зоны. Сенсоры на других змеек и прочее... Это все лишнее, изначальное ТЗ уже перевыполнено многократно. А если я захочу расширять проект, то в любой момент могу вернуться, ведь он задизайнен не так, что это одна сплошная простыня, это более-менее разбитое по модулям приложение, в которое при необходимости можно добавить новый ИИ, не перекапывая весь код, а занимаясь только классом Snake.
  1. Победа над перфекционизмом.
Итак, после добавления респавна змейкам вместо смерти и выхода в главное меню, а также небольших правок для режима бот против бота, я решил закончить проект.. Несколько минут я провел с GitHub, особенно с его файлом.gitignore, который должен помогать игнорировать ненужные папки типа pycache при загрузке на сервер. И проект готов.
Безусловно, можно его полировать до бесконечности, переписывать сто раз, рефакторить и анализировать до посинения и состаривания. Но создавать что-то несовершенное - это абсолютно нормально.
ТЗ было перевыполнено в 10 раз, и я могу развивать свой скилл на следующих проектах, которые уже мечтаю реализовать. И получать удовольствие от исследования новых, ранее недостижимых вершин.
Через некоторое время будет интересно взглянуть на этот проект и испытать стыд от того, какой бред я написал в коде.
  1. Что я получил создавая этот проект?
  • Я прыгнул выше своей головы, выбравшись из бесконечных "hello world" и решения элементарных алгоритмических задач на 5-10 строк, солидно пощупав ООП на Python и пройдя не один вечер на граблях. Теперь я увереннее жонглирую классами и их функционалом, создавая и изменяя их на автоматическом уровне.
  • Я получил моральное удовлетворение от преодоления барьеров.
  • Я научился собирать Python-проект в exe-файл с помощью нескольких команд в терминале.
  • Этот проект стал моим первым полноценным опытом работы с GitHub, и я изучил множество команд в терминале, которые раньше казались мне сложными. Я также впервые загрузил свой первый полноценный проект на GitHub.
  • И, конечно же, я горжусь тем, что создал "Змейку на Питоне"!
Ссылка на исходники проекта:
Поиграть в змейку (должно работать даже если у вас не установлен Python, по идее):
`
ОЖИДАНИЕ РЕКЛАМЫ...
31
Лучшее - враг хорошего (с)
Спасибо за пост, всегда интересно почитать "инсайды": зачастую такие вот рассказы о том, как делается проект интереснее самого проекта.
4
Лучшее - враг хорошего (с)
Спасибо за пост, всегда интересно почитать "инсайды": зачастую такие вот рассказы о том, как делается проект интереснее самого проекта.
Спасибо за тёплый отзыв. Сам проект "змейка" достаточно заезжен и что-то новое о нем вряд-ли получится рассказать в техническом плане. Хотя велосипедов нагородил немало. А вот в моральном аспекте, мне показалось полезным поделиться именно психологическим аспектом, прыжком в неизвестность, в которой начинаешь адаптироваться, балансируя на грани вылета. И бесконечным желанием сделать "лучше".
Чтобы оставить комментарий, пожалуйста, войдите на сайт.