Реализация алгоритма игры «Жизнь» – это хорошее упражнение с множеством интересных задач, которые вам предстоит решить. В частности, вам нужно будет создать сетку (игровое поле) жизни и найти способ применить правила игры ко всем клеткам на решетке, чтобы они эволюционировали в течение нескольких поколений.
В этом руководстве вы научитесь:
- Реализовывать алгоритм игры «Жизнь» Конвея на Python.
- Использовать библиотеку curses для работы с сеткой игры «Жизнь».
- Создавать интерфейс командной строки argparse для игры.
- Настраивать приложение игры для установки и запуска.
Чтобы извлечь максимальную пользу из этого руководства, вы должны знать основы написания объектно-ориентированного кода на Python, создания приложений с интерфейсом командной строки (CLI) с помощью argparse
и разработки проекта на Python.
Получите свой код: Нажмите здесь, чтобы загрузить исходный код (.zip) для создания игры «Жизнь» Конвея на Python.
Описание проекта
Игра «Жизнь» британского математика Джона Хортона Конвея не является игрой в традиционном смысле этого слова. С технической точки зрения, это клеточный автомат, но вы можете рассматривать игру «Жизнь» как симуляцию, развитие которой зависит от ее начального состояния и не требует дальнейшего участия игроков.
Игровое поле представляет собой бесконечную двумерную сетку клеток. Каждая клетка может находиться в одном из двух возможных состояний:
- Живая.
- Мертвая.
Каждая клетка эволюционирует в следующее поколение в зависимости от состояния ее самой и соседних клеток. Вот краткое описание правил эволюции:
- Живые клетки погибают, если у них меньше двух («малонаселенность») или больше трех живых соседей («перенаселение»).
- Живые клетки остаются жизнеспособными, если у них есть два или три живых соседа.
- Мертвые клетки, имеющие ровно три живых соседа, становятся живыми (размножение).
Начальное состояние игры – это стартовый образец, или начальная модель жизни. В данной реализации модель жизни будет представлять собой набор живых клеток. Первая генерация возникает в результате применения описанных выше правил к каждой клетке в начальном состоянии. Вторая генерация возникает в результате применения правил к первому поколению и так далее. Таким образом, каждая последующая генерация является чистой функцией предыдущей.
Задача этого проекта – запрограммировать алгоритм эволюции на Python, а затем разработать интерфейс командной строки (CLI) для запуска игры с различными моделями жизни.
Предварительные условия
Проект, который вы будете создавать в этом уроке, потребует от вас знакомства с общим программированием на Python и особенно с объектно-ориентированным программированием. Поэтому вы должны обладать базовыми знаниями по следующим темам:
- Работа с условными операторами.
- Написание циклов и генераторов.
- Работа со строками, кортежами, списками и множествами в Python.
- Создание регулярных классов и классов данных.
- Создание интерфейсов командной строки с помощью
argparse
. - Работа с файлами TOML в Python.
Однако если у вас еще нет всех этих знаний – не беда! Вы можете узнать больше, начав работу над проектом.
После этого краткого обзора игры «Жизнь» и его предпосылок вы готовы приступить к работе. Получайте удовольствие от кодинга!
🎓 Статьи по теме
- 🐍 Самоучитель по Python для начинающих. Часть 5. Методы работы со списками и списковыми включениями
- 🐍 Самоучитель по Python для начинающих. Часть 7: Методы работы с кортежами
- 🐍 Самоучитель по Python для начинающих. Часть 8: Методы работы со множествами
- 🐍 Самоучитель по Python для начинающих. Часть 9: Особенности цикла for
- 🐍 Самоучитель по Python для начинающих. Часть 10: Условный цикл while
Шаг 1: Настройка проекта игры «Жизнь»
Каждый раз, когда вы начинаете новый проект на Python, вы должны потратить некоторое время на то, чтобы подумать об организации самого проекта. Вам нужно создать макет проекта, который представляет собой структуру каталогов вашей работы.
Для проекта на Python, реализующего игру «Жизнь» Конвея, у вас может быть много разных макетов. Поэтому лучше сначала подумать о том, что вы хотите или должны сделать. Вот краткое содержание:
- Реализовать алгоритм игры «Жизнь», включая сетку жизни и семена или образцы
- Обеспечить возможность визуализации сетки и ее эволюции
- Дать пользователю возможность выбрать шаблон и запустить игру на определенное количество поколений.
Следуя этим идеям, вы создадите следующую структуру каталогов для своей игры «Жизнь»:
В этом руководстве вы можете назвать проект rplife
, что является комбинацией Real Python (rp
) и life
. Файл README.md
будет содержать описание проекта и инструкции по установке и запуску приложения.
Файл pyproject.toml
– это TOML-файл, который определяет процесс создания проекта и многие другие конфигурации. В современном Python этот файл заменяет скрипт setup.py
, который вы могли использовать раньше. Таким образом, в этом проекте вы будете использовать pyproject.toml
вместо setup.py
.
Внутри каталога rplife/
находятся следующие файлы:
__init__.py
включает rplife/ как пакет Python.__main__.py
работает как скрипт входа в игру.cli.py
содержит интерфейс командной строки для игры.patterns.py
иpatterns.toml
обрабатывают шаблоны игры.views.py
реализует способ отображения сетки и ее эволюции.
grid.py
обеспечивает реализацию сетки.
Теперь создайте все эти файлы без какого-либо содержимого. Вы можете сделать это в вашем любимом редакторе кода или IDE. Как только вы закончите создание макета проекта, можно приступать к реализации правил игры «Жизнь» на Python.
Windows:
Linux + macOS:
С помощью этих двух команд вы создадите и активируете виртуальную среду Python под названием venv в своем рабочем каталоге.
Шаг 2: Создание сетки игры «Жизнь»
Как вы уже знаете, основным компонентом игры «Жизнь» является бесконечная двухмерная сетка из ячеек. Этот элемент может показаться сложным для реализации, потому что он бесконечен. Поэтому вам нужен способ абстрагироваться от этого требования. Для этого вы сосредоточитесь на живых клетках, а не на всех клетках сетки.
Чтобы отобразить начальный набор живых клеток, вы используете класс Pattern
, который представляет собой начало игры. Затем вы создадите класс LifeGrid
, который возьмет шаблон и разовьет его до следующего поколения живых клеток, применяя правила игры. Этот класс также предоставит строковое представление сетки, чтобы вы могли отобразить ее на экране.
Для загрузки кода этого шага перейдите по следующей ссылке и загляните в папку source_code_step_2/
:
Набросок классов Pattern и LifeGrid
Чтобы приступить к реализации алгоритма игры, откройте редактор кода и перейдите к файлу patterns.py
. Там создайте класс Pattern
, используя декоратор @dataclass
из модуля dataclasses
:
На данный момент Pattern требуется хранить только наименование шаблона и живые клетки. Атрибут .alive_cells
представляет собой набор кортежей с двумя значениями. Каждый кортеж соответствует координатам живой ячейки в сетке. Использование такого элемента для хранения живых ячеек позволяет использовать операции с множествами для определения ячеек, которые будут живыми в следующем поколении.
Теперь вам нужен класс LifeGrid
, который будет решать две конкретные задачи:
- Эволюцию сетки до следующего поколения
- Предоставление строкового отображения сетки
Итак, ваш класс будет иметь следующие атрибуты и методы. Не забывайте, что этот класс будет располагаться в модуле grid.py
:
Для создания базы LifeGrid
вы использовали pass
. Инициализатор класса принимает в качестве аргумента шаблон. Этим аргументом будет служить Pattern
. Затем есть .evolve()
, проверяющая текущие живые клетки и их соседей, для определения следующего поколения живых клеток.
Наконец, в .as_string()
вы обеспечите способ представления сетки в виде строки, которую можно отобразить в терминале. Обратите внимание, что этот метод принимает аргумент, который предоставляет ограничительную рамку для сетки. Эта область будет определять, какую часть сетки вы отобразите в вашем терминале.
Переход от одного поколения сетки к другому
Теперь пришло время написать метод .evolve()
, который должен определить ячейки, которые перейдут в следующее поколение в качестве живых клеток. Для этого нужно проверить живые клетки и их соседей, чтобы определить количество активных членов сетки и решить, какая клетка останется в живых, какая умрет, а какая возродится. Вспомните правила эволюции клеток:
- Живые клетки погибают, если у них меньше двух («малонаселенность») или больше трех живых соседей («перенаселенность»).
- Живые клетки остаются живыми, если у них есть два или три живых соседа.
- Мертвые клетки, имеющие ровно три живых соседа, становятся живыми (размножение).
Подождите, у вас есть только координаты живых клеток. Как вы можете проверить соседей определенной живой клетки? Рассмотрим следующую диаграмму, которая представляет собой небольшую часть сетки:
Теперь предположим, что вы проверяете соседей клетки в точке (1, 1), а они все зеленого цвета. Как вы можете определить их координаты в сетке? К примеру, чтобы вычислить координату первого ряда ячеек, вы можете сделать следующее:
- Для (0, 0) прибавьте (-1, -1) к (1, 1) значение за значением.
- Для (0, 1) добавьте (-1, 0) к (1, 1) по значению.
- Для (0, 2) прибавьте (-1, 1) к (1, 1) по значению.
Из предложенных примеров можно сделать вывод, что кортежи (-1, -1), (-1, 0), (-1, 1) представляют собой разницу между целевой ячейкой и ее соседями. Другими словами, это дельты, которые вы можете добавить к координатам целевой ячейки, чтобы определить ее соседей. Вы можете распространить эту схему на остальных соседей и найти соответствующие кортежи дельт.
С учетом этих идей вы готовы к реализации метода .evolve()
:
Вот разбор того, что делает этот код (построчно):
- Строки с 6 по 15 определяют дельта-координаты для соседей целевой ячейки.
- В строке 16 используется словарь для подсчета количества живых соседей. В этой строке вы задействуете класс
defaultdict
из модуляcollections
для создания счетчика с классомint
в качестве основного объекта. - В строке 17 выполняется цикл по живым ячейкам, которые хранятся в объекте
.pattern
. Этот цикл позволяет проверить соседей каждой живой клетки, чтобы определить следующее поколение. - Строка 18 запускает цикл по смещениям соседних клеток. Этот внутренний цикл подсчитывает, со сколькими клетками соседствует текущая ячейка. Этот подсчет позволяет узнать количество соседей как для живых, так и для мертвых клеток.
- В строках 21-23 создается список, содержащий клетки, которые останутся в живых. Для этого сначала создается совокупность соседей, которые сами имеют двух или трех живых соседей. Затем находятся клетки, которые являются общими как для этого множества, так и для
.alive_cells
. - В строках 24-26 создается совокупность клеток, которые будут возрождаться. В этом случае вы создаете группу клеток, у которых ровно три живых соседа. Затем вы определяете воскресшие клетки, исключая те, что уже есть в
.alive_cells
. - Строка 28 обновляет
.alive_cells
множеством, которое получается в результате объединения клеток, которые остаются в живых, и клеток, которые возрождаются.
Чтобы проверить, работает ли ваш код так, как нужно, необходимо определить количество живых клеток в каждом поколении. Добавьте следующий метод в LifeGrid
:
Специальный метод .__str__()
позволяет представить содержимое объекта в удобном для пользователя виде. С помощью этого метода, когда вы используете встроенную функцию print()
для вывода на экран элемента LifeGrid
, вы получаете имя соответствующего шаблона и множество живых ячеек в следующей строке. Эта информация дает вам представление о текущем состоянии сетки.
Теперь вы готовы протестировать свой код. Откройте новый терминал в корневом каталоге проекта. Затем начните сессию Python REPL и запустите следующий код:
В этом фрагменте кода вы сначала импортируете модули grid
и patterns
из пакета rplife
. Затем вы создаете образец Pattern
. В ближайшее время вы познакомитесь с разнообразием паттернов. А пока в качестве примера вы используете шаблон Blinker.
Далее вы создаете объект LifeGrid
. Обратите внимание, что при выводе на экран этого объекта вы получите имя паттерна и живые ячейки. У вас есть рабочая сетка с подходящим исходным материалом. Теперь вы можете развивать сетку, вызвав .evolve()
. На этот раз вы получите другой набор живых клеток.
Если вы снова модифицируете сетку, то получите тот же набор живых клеток, который вы использовали в качестве начального материала для игры. Это происходит потому, что паттерн Blinker представляет собой осциллятор, который эволюционирует подобным образом:
Шаблон Blinker отображает три горизонтальные живые клетки в одном поколении и три вертикальные живые клетки в следующем поколении. Ваш код делает то же самое, так что все работает, как и предполагалось.
Представление сетки в виде строки
Теперь, когда вы реализовали .evolve()
, чтобы перевести игру в следующее поколение, вам нужно реализовать .as_string()
. Как вы уже узнали, этот метод предусматривает процесс создания представления сетки в виде строки, чтобы вы могли отобразить ее на экране.
Ниже приведен фрагмент кода, в котором вы используете этот метод:
В этом коде вы сначала определяете две константы, ALIVE и DEAD. Эти константы содержат символы, которые вы будете использовать для обозначения живых и мертвых клеток на сетке.
Внутри .as_strings()
вы разворачиваете координаты ограничительной рамки в четыре переменные. Эти параметры определяют, какую часть бесконечной сетки ваша программа будет отображать на экране. Затем вы создаете переменную display
в качестве списка, содержащего имя шаблона. Обратите внимание, что вы используете .center()
для центрирования названия по ширине сетки.
В цикле for
выполняется итерация по диапазону строк внутри окна. В цикле создается новый список, содержащий живые и мертвые ячейки в текущем ряду. Чтобы выяснить, жива ли данная ячейка, проверяется, есть ли ее координаты в наборе живых ячеек.
Затем вы добавляете строку в виде последовательности в список отображения. В конце цикла вы объединяете каждую строку с помощью символа новой строки (\n
), чтобы создать сетку жизни в виде строки.
Для проверки ваших изменений выполните следующий код в интерактивной среде:
При выводе на экран сетки вы получаете прямоугольную область, содержащую точки и сердечки. Если вы вызовете .evolve()
и снова отобразите на экране сетку, то получите изображение следующего поколения. Здорово, не правда ли?
Шаг 3: Настройка и загрузка шаблонов для игры «Жизнь»
До этого момента вы использовали класс LifeGrid
и первую половину класса данных Pattern
. Ваш код успешно выполняется. Однако создание исходных данных вручную кажется слишком сложной задачей. Было бы неплохо иметь несколько предустановленных шаблонов и загружать их в процессе выполнения игры.
В следующих разделах вы создадите несколько типовых паттернов в файле TOML и напишете код, необходимый для загрузки шаблонов в объекты Pattern
.
Щелкните по ссылке ниже для загрузки кода для этого этапа, чтобы вы могли следовать за проектом. Вы найдете все необходимое в папке source_code_step_3/
:
Настройка шаблонов жизни в файле TOML
Для создания шаблона для игры «Жизнь» вам понадобится название паттерна и набор координат для живых клеток. К примеру, используя формат файла TOML, вы можете представить шаблон Blinker следующим образом:
В этом файле TOML содержится таблица с именем целевого шаблона. Кроме того, в ней присутствует пара ключевых значений, содержащая множество множеств. Внутренние массивы представляют собой координаты живых клеток в шаблоне Blinker. Обратите внимание, что формат TOML не поддерживает наборы или кортежи, поэтому вместо них вы используете массивы.
Следуя этой же конструкции, вы можете задать столько шаблонов, сколько захотите. Паттерны, которые вы будете использовать в этом уроке в файле patterns.toml:
В приведенном выше файле patterns.toml
содержится восемь различных шаблонов. При желании вы можете добавить еще несколько, но для целей данного урока достаточно и этого.
Загрузка паттернов жизни из TOML
У вас есть файл TOML с кучей паттернов для вашей игры «Жизнь». Теперь вам нужен способ для загрузки этих шаблонов в ваш код. Для начала включите альтернативный конструктор в класс Pattern
, который позволит вам создавать шаблоны из данных TOML:
Метод .from_toml()
является классовым, поскольку вы используете декоратор @classmethod
. Классовые методы отлично справляются с задачей, когда вам нужно добавить альтернативный конструктор в состав класса. Методы этого типа получают текущий класс в качестве первого аргумента (cls
).
Затем в качестве аргументов вы принимаете имя шаблона и данные TOML. Внутри метода вы создаете и возвращаете экземпляр класса, используя аргумент cls
. Чтобы задать аргумент .alive_cells
, вы используете генератор.
В нем вы создаете набор кортежей из перечня списков, полученных из файла TOML. Каждый кортеж будет содержать координаты живой клетки на сетке. Обратите внимание, что для доступа к живым клеткам в данных TOML можно использовать поиск по словарю с использованием имени целевого ключа в квадратных скобках.
Далее необходимо добавить две функции. Первая функция позволит загрузить все паттерны из файла TOML. Вторая функция будет подгружать по одной детали за раз.
Чтобы разобрать файл TOML и преобразовать его содержимое в объекты Python, вы можете использовать стандартный библиотечный модуль tomllib
, если вы используете Python 3.11 или более позднюю версию. В противном случае вам следует использовать стороннюю библиотеку tomli
, которая совместима с tomllib
.
Чтобы ваш код работал с каждым из этих инструментов, вы можете включить операторы импорта библиотек TOML в блок try ... except
:
Импорт в строке try
нацелен на стандартный библиотечный модуль tomllib
. Если этот импорт вызывает исключение, потому что вы используете Python версии ниже 3.11, то оператор except
импортирует стороннюю библиотеку tomli
, которую необходимо установить в качестве внешней зависимости вашего проекта.
Когда библиотека TOML будет установлена, настанет время для написания необходимых функций. Добавьте get_pattern()
в файл patterns.py
:
Эта функция принимает в качестве аргументов название целевого паттерна и имя файла TOML и возвращает объект Pattern
, представляющий шаблон, название которого совпадает с аргументом name
.
В первой строке функции get_pattern()
вы загружаете содержимое файла patterns.toml
, используя выбранную вами библиотеку TOML. Метод .loads()
возвращает словарь. Затем вы создаете объект Pattern
с помощью конструктора .from_toml()
и возвращаете полученный результат.
Обратите внимание, что аргумент filename
имеет значение по умолчанию, которое вы задаете с помощью константы. Вот как можно определить эту константу после завершения процесса импорта:
Эта константа содержит элемент pathlib.Path
, который указывает на файл patterns.toml
. Не забывайте, что этот файл находится в вашем каталоге rplife/
. Чтобы получить адрес этого каталога, вы используете атрибут __file__
, который содержит путь к файлу, из которого был загружен модуль patterns.py
. Затем вы используете атрибут .parent
в Path
, чтобы получить необходимую директорию.
Функция get_pattern()
извлекает один паттерн из файла TOML, используя имя шаблона. Эта функция будет полезна, когда вы хотите запустить свою игру «Жизнь», используя один паттерн. А если вы хотите запустить несколько шаблонов подряд? В этом случае вам понадобится функция, которая получит все образцы из файла TOML.
Вот как реализуется эта функция:
Эта функция принимает в качестве аргумента путь к файлу TOML. Первая строка аналогична строке get_pattern()
. Затем вы создаете список объектов Pattern
с помощью генератора. При этом используется метод .items()
для словаря, который возвращает .loads()
.
После того как эти две функции будут введены, вы можете испытать их в действии:
Великолепно! Обе функции работают как положено. В этом примере вы сначала получаете паттерн Blinker с помощью функции get_pattern()
. Затем вы выводите полный список доступных шаблонов с помощью функции get_all_patterns()
.
Шаг 4: Пишем визуальное представление игры
Вы реализовали большую часть внутреннего кода для своей игры «Жизнь». Теперь вам нужен способ отобразить развитие игры на экране. В этом уроке вы будете использовать пакет curses
из стандартной библиотеки для демонстрации развития игры. Этот пакет содержит в себе информацию о библиотеке curses
, позволяющую создать текстовый пользовательский интерфейс (TUI) с возможностью использования расширенных возможностей терминала.
Чтобы загрузить код для этого шага, перейдите по ссылке ниже, а затем откройте папку source_code_step_4/
:
Чтобы начать работу, следует задать класс CursesView
:
Классовый инициализатор принимает несколько аргументов. Вот их описание и значения:
pattern
представляет собой модель жизни, которую вы хотите отобразить на экране. Это должен быть объектPattern
.gen
– количество поколений, через которые будет проходить игра. По умолчанию задано 10 поколений.frame_rate
представляет собой количество кадров в секунду, которое является показателем времени между отображением одного поколения и следующим. По умолчанию установлено значение 7 кадров в секунду.bbox
– ограничительная рамка для сетки. Это кортеж, который определяет, какая часть сетки будет отображаться. Это должен быть кортеж определенного вида (start_col
,start_row
,end_col
,end_row
).
Этот класс будет иметь только один метод в своем общедоступном интерфейсе. Метод .show()
будет отвечать за отображение сетки на экране:
Метод .show()
довольно краток. Он включает в себя только вызов функции wrapper()
из curses
. Эта функция инициализирует curses
и вызывает другой объект. В данном случае вызываемым объектом является закрытый метод ._draw()
, который отвечает за отображение последовательных генераций ячеек.
Вот возможная реализация метода ._draw()
:
В этом фрагменте кода происходит много всего. Ниже приведен построчный разбор:
- В строке 2 импортируется функция
sleep()
из модуляtime
. Вы будете использовать эту функцию для управления количеством кадров в секунду вашего изображения. - Строка 4 импортирует класс
LifeGrid
из модуляgrid
в пакетеrplife
. - В строке 8 определена функция
._draw()
, которая принимает в качестве аргумента объектscreen
илиcurses window
. Этот объект автоматически создается при вызовеcurses.wrapper()
с._draw()
в качестве аргумента. - В строке 9 определяется сетка путем внедрения класса
LifeGrid
с текущим шаблоном в качестве аргумента. - В строке 10 вызывается
.curs_set()
для установки видимости курсора. В данном случае в качестве аргумента используется 0, что означает, что курсор будет невидимым. - В строках с 13 по 18 определен блок
try ... except
, который вызывает исключениеValueError
, если в текущем терминале недостаточно места для отображения сетки. Обратите внимание, что эту проверку нужно выполнить только один раз, поэтому не нужно включать ее в цикл в строке 20. - Строка 20 запускает цикл, который будет выполняться столько раз, сколько существует поколений.
- Строка 21 вызывает
.evolve()
на сетке для развития игры до следующего поколения. - Строка 22 вызывает
.addstr()
на текущем объекте экрана. Первые два аргумента определяют строку и столбец, в которых вы собираетесь нарисовать сетку жизни. В этом уроке вы начнете рисовать с точки (0, 0), которая является верхним левым углом терминала. - Строка 23 обновляет экран, вызывая
.refresh()
. Этот вызов немедленно обновляет экран, чтобы отразить изменения, произошедшие после предыдущего вызова.addstr()
. - В строке 24 вызывается функция
sleep()
, чтобы установить частоту кадров, которую вы будете использовать для отображения последовательных поколений в сетке.
После того как визуализация завершена, вы можете опробовать свою игру «Жизнь», выполнив следующий код:
В этом фрагменте вы импортируете класс CursesView
из views
и функцию get_pattern()
из patterns
. Затем вы создаете новый объект CursesView
, используя шаблон Glider Gun и сотню поколений. И наконец, вы вызываете метод .show()
для отображения результата. Этот код запустит игру и отобразит ее эволюцию через сотню поколений жизни.
Ух ты! Здорово! Ваша игра «Жизнь» набирает обороты. В следующих двух шагах вы разработаете интерфейс командной строки (CLI) для взаимодействия пользователей с игрой, и, наконец, соберете все вместе в скрипте стартовой точки игры, __main__.py
.
Шаг 5: Реализация игрового CLI
В этом разделе вы создадите интерфейс командной строки (CLI) для вашей игры «Жизнь». Этот интерфейс позволит вашим пользователям взаимодействовать с игрой и запускать ее с различными моделями жизни. Для создания CLI вы будете использовать модуль argparse
из стандартной библиотеки. Он содержит следующие параметры командной строки:
--version
покажет номер версии программы и завершит работу.-p, --pattern
будет задан шаблон для игры «Жизнь», по умолчанию – Blinker.-a, --all
покажет все доступные шаблоны в определенной последовательности.-v, --view
отобразит сетку жизни в заданном режиме, по умолчаниюCursesView
.-g, --gen
выводит количество поколений, по умолчанию 10.-f, --fps
– количество кадров в секунду, по умолчанию 7.
Чтобы загрузить код для этого шага, перейдите по следующей ссылке и найдите папку source_code_step_5/
:
Чтобы начать писать CLI, добавьте следующий код в cli.py
:
В этом фрагменте кода вы сначала импортируете модуль argparse
. Затем вы импортируете некоторые необходимые объекты из пакета rplife
.
До этого этапа вы не обозначали __version__
, поэтому откройте файл __init__.py
. Затем добавьте строку __version__ = "1.0.0"
в начало файла. Этот атрибут активирует опцию командной строки --version
, которая часто встречается в CLI-приложениях и позволяет отображать текущую версию приложения.
Далее вы задаете функцию get_command_line_args()
, чтобы завершить определение CLI. Внутри функции get_command_line_args()
вы создаете парсер аргументов, используя ArgumentParser
. В этом примере в качестве аргументов в процессе создания класса вы указываете только имя и описание программы.
После создания этого кода можно приступать к добавлению параметров командной строки. Ниже представлен код, необходимый для реализации перечисленных опций:
В этом фрагменте кода вы добавляете все необходимые опции в CLI вашей игры, вызывая .add_argument()
на парсерном объекте. Каждая опция имеет свои аргументы в зависимости от желаемого функционала.
Важно отметить, что опция -p, --pattern
является параметром выбора, что означает, что входное значение должно точно соответствовать имени доступного шаблона. Чтобы получить названия всех имеющихся паттернов, можно воспользоваться списком и функцией get_all_patterns()
.
Функция get_command_line_args()
включает в себя диапазон имен, содержащий аргументы командной строки и соответствующие им значения. Вы можете получить доступ к аргументам и их значениям, используя точечную нумерацию в диапазоне имен. К примеру, если вам нужно получить доступ к значению аргумента --view
, то вы можете сделать так: get_command_line_args().view
.
Опция -v, --view
также является параметром выбора. В этом случае вы получаете доступные варианты из атрибута __all__
, заданного в модуле views
. Конечно, вы еще не определили __all__
, поэтому вам нужно сделать это прямо сейчас. Откройте файл views.py
и добавьте следующий оператор присваивания сразу после вашего импорта:
Специальный атрибут __all__
позволяет указать список имен, которые базовый модуль будет экспортировать как часть своего общедоступного кода. В этом примере __all__
содержит только один объект, потому что это пока все, что у вас есть. Вы можете реализовать собственные объекты в рамках вашего проекта и добавить их в этот список, чтобы пользователь мог использовать их при запуске игры.
Отлично! Теперь у вашей игры есть удобный пользовательский интерфейс командной строки. Однако нет возможности опробовать его. Для начала необходимо написать сценарий входа в игру. Именно этим вы и займетесь в следующем разделе.
Шаг 6: Написание скрипта начала игры
В Python выполняемые программы имеют скрипт или файл запуска. Как следует из названия, скрипт входа – это сценарий, содержащий код, который запускает выполнение программы. В этот файл обычно помещается программная функция main()
.
Код для этого шага вы можете скачать, перейдя по ссылке ниже и заглянув в папку source_code_step_6/
:
В современном Python, как правило, файл __main__.py
является оптимальным местом для исходного кода. Поэтому откройте файл __main__.py
в редакторе кода. Затем добавьте в него следующее:
Вот построчное объяснение приведенного выше кода:
- В строке 1 импортируется модуль
sys
из стандартной библиотеки. Вы будете использовать этот модуль для доступа к файлуsys.stderr
, в который вы будете записывать все ошибки, возникающие во время выполнения программы. - В строке 3 импортируются модули
patterns
иviews
изrplife
. С их помощью вы определите, какой шаблон жизни и какой режим работы использовать. - Строка 4 импортирует
get_command_line_args()
из модуляcli
. Вы будете использовать ее для разбора аргументов и опций командной строки. - В строке 6 определяется функция
main()
. - Строка 7 вызывает
get_command_line_args()
и сохраняет полученный объект диапазона имен в args. - В строке 8 используется аргумент командной строки
args.view
для доступа к нужному изображению на модулеviews
. Чтобы получить доступ к данным, вы используете встроенную функциюgetattr()
. Таким образом, вы обеспечиваете масштабируемость кода, позволяя добавлять новые типы представлений без необходимости модифицироватьmain()
. Обратите внимание, что поскольку вызовgetattr()
в данном случае использует класс, вы использовали заглавную букву в словеView
, чтобы обозначить этот факт. - В строке 9 определяется условный оператор, проверяющий, выбрал ли пользователь запуск всех доступных шаблонов подряд. Если это так, то строки 10 и 11 выполняют цикл по всем шаблонам и выводят их на экран с помощью вспомогательной функции
_show_pattern()
. Вы сможете задать эту функцию в ближайшее время. - Строки с 13 по 17 выполняются каждый раз, когда пользователь выбирает определенный шаблон для запуска игры.
Вспомогательная функция _show_pattern()
является важной частью main()
. Вот ее формулировка:
В этой функции вы используете текущий вид, шаблон и аргументы командной строки в качестве входных данных. Затем с помощью блока try ... except
вы создаете объект view
и запускаете его метод .show()
. Этот блок перехватит и обработает любое исключение, которое может возникнуть в процессе развития игры, и выведет сообщение об ошибке в стандартный блок с ошибками. Таким образом, вы показываете удобное для пользователя сообщение об ошибке вместо сложной истории исключений.
Отлично! Скрипт входа почти готов. Осталось добавить одну маленькую деталь. Нужно вызвать main()
, чтобы программа заработала:
Способ запуска функции main()
в исполняемом скрипте – это использование фразы name-main
, как это сделано в приведенном выше фрагменте кода. Эта идиома гарантирует, что функция main()
будет запущена только тогда, когда вы запустите файл как исполняемую программу.
Создав скрипт входа, вы можете опробовать свою игру «Жизнь». Выполните следующую команду, чтобы запустить игру со всеми доступными моделями жизни:
Выглядит просто потрясающе, не правда ли? Вы можете изучить, как работают остальные опции командной строки. Попробуйте! Опция --help
позволит вам узнать, как использовать CLI-приложения.
Шаг 7: Настройка игры для установки и запуска
До этого момента вы выполнили все необходимые действия, чтобы получить полнофункциональную реализацию игры «Жизнь» Конвея. Теперь у игры есть удобный интерфейс командной строки, который позволяет запускать ее с различными опциями. Вы можете запустить игру с одним шаблоном, со всеми доступными шаблонами и так далее.
Несмотря на то что игра «Жизнь» прекрасно работает, для запуска игры все равно нужно использовать команду python. Это немного раздражает и может создать ощущение, что игра не является настоящим приложением CLI.
В следующих разделах вы узнаете, как настроить игру «Жизнь» для установки с помощью файла pyproject.toml
. Вы также узнаете, как установить игру в виртуальную среду Python, чтобы запустить ее как отдельное CLI-приложение.
Чтобы загрузить код для этого заключительного шага, перейдите по следующей ссылке и найдите папку source_code_step_7/
:
Написание файла pyproject.toml
В последние годы сообщество Python переходит к использованию файлов pyproject.toml
в качестве основного файла конфигурации для упаковки и дистрибуции проектов Python. В этом разделе вы узнаете, как написать стандартный файл pyproject.toml
для вашей игры «Жизнь».
Как следует из расширения, pyproject.toml
использует формат TOML. Полную информацию о написании файлов pyproject.toml
вы можете найти в PEP 621. Следуя этой инструкции, ниже приведен файл pyproject.toml
для вашей игры «Жизнь»:
Первая строка в этом файле определяет систему построения, которую вы хотите использовать при создании проекта. В этом руководстве для вашего проекта будет использоваться пакет setuptools
.
В поле «Проект» вы указываете имя проекта – rplife
. Затем вы задаете версию проекта как динамическую переменную, которая позже будет загружена в строку tool.setuptools.dynamic
в конце файла.
Далее необходимо описать проект, предоставить путь к файлу README
и указать авторов. Ключ dependencies содержит список внешних зависимостей для этого проекта. В данном случае необходимо установить библиотеку tomli
для обработки файла TOML, если версия Python меньше 3.11.
В строке project.scripts
вы определяете стартовую команду приложения, которой является функция main()
из __main__.py
пакета rplife
.
Наконец, вы загружаете номер версии приложения из константы __version__ dunder
, которую вы определили в файле __init__.py
вашего пакета rplife
.
Вот и все! У вас есть минимальный жизнеспособный файл pyproject.toml
для вашей игры «Жизнь». Теперь вы и ваши пользователи можете установить программу и использовать ее как обычное приложение командной строки.
Установка и запуск игры «Жизнь»
После того как вы создали необходимый файл pyproject.toml
для вашей игры «Жизнь», можно приступать к установке проекта в специальную виртуальную среду Python. Выполните следующие команды в терминале, если вы не сделали этого в первом шаге этого руководства:
Windows:
Linux + macOS:
С помощью этих команд вы создаете и активируете новую виртуальную среду. После того как среда будет активирована, вы можете установить проект в редактируемом режиме с помощью опции -e
в pip install
.
Режим редактирования очень удобен, когда вы работаете над проектом на Python. Он позволяет использовать проект как отдельное приложение и испытывать его, как это делается в производстве. В этом режиме вы можете продолжать добавлять функции и модифицировать код, тестируя его в режиме реального времени.
Теперь вы можете запустить игру «Жизнь» как обычное приложение в командной строке. Выполните следующую команду:
В данном случае вы используете команду rplife
напрямую, чтобы запустить игру с шаблоном Glider Gun на сто поколений. Обратите внимание, что вам больше не нужно использовать команду python
. Теперь ваш проект работает как обычное приложение командной строки. Разве это не здорово?
Заключение
Вы разработали игру «Жизнь» Конвея с помощью Python и объектно-ориентированного программирования. Чтобы сделать игру более удобной для пользователей, вы позаботились о создании понятного интерфейса командной строки с помощью argparse
. В процессе работы вы научились структурировать и оформлять CLI-приложение, а также настраивать его для дистрибуции и установки. Это отличный опыт для разработчика на Python.
В этом руководстве вы узнали, как:
- Реализовывать алгоритм игры «Жизнь» с помощью ООП
- Использовать
curses
для отображения сетки игры «Жизнь» - Обеспечивать игру интерфейсом командной строки
argparse
- Настраивать игру для установки и запуска
Получив все эти знания и навыки, вы готовы приступить к более сложным проектам и задачам.
Опять же, вы можете загрузить полный исходный код и другие ресурсы для этого проекта по ссылке ниже:
Получите свой код: Нажмите здесь, чтобы загрузить исходный код (.zip) для создания игры «Жизнь» Конвея на Python.
Следующие шаги
Теперь, когда вы закончили работу над своей игрой «Жизнь», вы можете сделать еще один шаг вперед, реализовав несколько дополнительных функций. Самостоятельное добавление новых элементов поможет вам узнать о потрясающих вещах.
Вот несколько идей для новых возможностей:
- Внедрить другие типы представлений: Наличие других вариантов, помимо основанного на
curses
, будет отличным дополнением к вашему проекту. К примеру, вы можете использовать Tkinter, в котором вы будете отображать сетку жизни в окне графического интерфейса. - Добавьте новые интересные шаблоны жизни: Добавление новых шаблонов жизни в файл
patterns.toml
позволит вам исследовать другие варианты развития игры. - Измените правила: До сих пор вы работали с традиционными правилами, согласно которым мертвые клетки с тремя живыми соседями перерождаются, а живые клетки с двумя или тремя живыми соседями выживают. Сокращенно это называется B3/S23, но существует несколько вариаций, использующих другие правила для эволюции нового поколения. Меняя правила, вы можете познакомиться с другими вселенными, похожими на человеческую жизнь.
Комментарии