Игра, созданная с ИИ с одного промта. Тест 2: игра «ASHEN DEPTHS» - пошаговый рогалик: BSP-подземелья, теневое поле зрения, A*-монстры, предметы, уровни и permadeath. Python + pygame, ноль ассетов.
Пошаговый рогалик про спуск в засыпанный пеплом город. Восемь этажей вниз, по дороге крысы, гули, скелеты и что-то похуже, а на самом дне лежит Пепельная корона — забрал её, значит выиграл. Умер по дороге — значит начинай заново, сохранение стирается. Как положено жанру.
Это мой второй заход в геймдев на питоне, и делал я его ради алгоритмов. В шутере, который я писал до этого, весь интерес был в рейкастинге. Тут интересного больше: генерация подземелья, поле зрения, поиск пути, очередь ходов. По началу хотел просто накидать комнат случайно, но получались то слипшиеся кучи, то куски карты, куда вообще не дойти. Пришлось разбираться, как это делают взрослые.
Ассетов в проекте нет вообще: каждая плитка стены, пола и колонны рисуется кодом при запуске. Символы монстров — обычный шрифт, но подсвеченный под уровень факела.
Нужен Python 3.8 или новее и pygame. На Windows ставить руками ничего не надо.
Windows, в один клик
- Скачайте папку с проектом и распакуйте куда угодно. Именно распакуйте — из архива запускать бесполезно.
- Зайдите в папку и дважды кликните
PLAY.bat. - Откроется консоль. Первый запуск идёт около полуминуты: скрипт ищет Python, если надо ставит его через winget, потом докачивает pygame (примерно 10 МБ). Всё это он пишет на экран.
- Когда появится «Запускаю», откроется окно игры. Консоль не закрывайте, она нужна пока игра работает.
- В меню нажмите
n— новый спуск.
Вместо PLAY.bat можно кликнуть правой кнопкой по run.ps1 и выбрать
«Выполнить с помощью PowerShell», это то же самое.
Важно: скрипты нужно запускать файлом, а не копировать их текст в открытое окно PowerShell. При копипасте скрипт не понимает, где он лежит, и начнёт работать в той папке, которая открыта в консоли. Я на это уже наступал.
Linux и macOS
python3 -m pip install -r requirements.txt
python3 main.py
Под Linux pygame иногда просит системный SDL2:
sudo apt install python3-pygame # Debian, Ubuntu
Если что-то пошло не так
Консоль моргнула и закрылась — запустите run.ps1 правой кнопкой, тогда текст
ошибки останется на экране, скрипт специально ждёт Enter перед выходом.
«Выполнение сценариев отключено в этой системе» — значит run.ps1 запущен
напрямую из PowerShell. Либо через PLAY.bat, либо разрешите скрипты на одну
сессию: Set-ExecutionPolicy -Scope Process Bypass.
«python не является внутренней или внешней командой» — Python есть, но не в PATH. Проще переустановить его с сайта и на первом экране установщика поставить галочку «Add Python to PATH».
стрелки, wasd идти; шаг в монстра = удар
y u b n, нумпад идти по диагонали
пробел или . пропустить ход
g подобрать то, что под ногами
i рюкзак: enter применить или надеть, d выбросить
c лист персонажа
m вся история сообщений
Shift + . ( > ) спуститься по лестнице
мышь навести — посмотреть, что это; клик — цель для свитка
F5 / F9 сохранить / загрузить
Esc назад, из игры — в меню (с автосохранением)
Про бой стоит сказать сразу: он честно арифметический, урон = сила атакующего минус защита цели, и никакого уклонения нет. Поэтому побеждает не тот, кто быстрее жмёт, а тот кто вовремя отступил в коридор, где до него дотянется только один противник вместо четырёх. Свитки лучше беречь до момента, когда станет по-настоящему плохо.
Комнаты. Этаж строится через BSP — бинарное разбиение пространства. Прямо- угольник карты рекурсивно режется пополам, в каждом получившемся листе вырезается комната, а потом на обратном ходе рекурсии соседние половины соединяются коридором. Прелесть в том, что связность получается сама собой: соседи по дереву — это всегда соседи на карте, поэтому проверять карту на «отрезанные куски» не нужно вообще. Тест всё равно проверяет, на сорока разных этажах.
Зрение. Recursive shadowcasting: карта обходится по восьми октантам, и каждое препятствие сужает диапазон углов, который виден за ним. Считает он только реально видимые клетки, а не «лучи на каждый градус», и не оставляет дырок в тенях за углами, чем грешит наивный вариант. Заодно из квадрата расстояния сразу считается уровень освещённости, поэтому дальний край факела темнее ближнего.
Память карты. Всё, что вы уже видели, остаётся нарисованным, но холодным синевато-серым — сразу понятно, где вы были, а где сейчас светло.
Путь. Монстры ходят по A* с восьмью направлениями и октильной эвристикой. Другие монстры для поиска — не стена, а дорогая клетка, поэтому толпа в коридоре не встаёт намертво, а перетекает друг вокруг друга.
Одинаковый мир. Генератору можно передать зерно, и тогда этаж воспроизводится один в один — удобно, когда поймал баг и хочешь показать его ещё раз. Скриншот выше как раз с фиксированным зерном, поэтому тест рисует его одинаково при каждом прогоне.
Ходы. Не «все ходят по разу», а очередь энергии: каждый ход существо получает энергии по своей скорости и тратит по 100 на действие. Крыса со скоростью 130 иногда успевает ударить дважды, огр со скоростью 70 — иногда пропускает.
Существа. Никакой иерархии классов, вместо неё компоненты: боец, ИИ, рюкзак, экипировка, уровень. «Запутавшийся скелет с мечом» не требует нового класса — просто другой набор деталей.
Сохранение. Обычный JSON, который можно открыть блокнотом. Существо пишется как ключ шаблона плюс то, что успело измениться, карта — по одной цифре на клетку. Никакого pickle: файл сохранения не должен уметь выполнять код при загрузке.
Файлы:
main.py цикл, состояния, связка всего
settings.py все константы и баланс
engine.py состояние партии и очередь ходов
procgen.py генерация этажа (BSP)
gamemap.py карта, видимость, запросы к ней
fov.py recursive shadowcasting
pathfinding.py A*
entity.py существа и таблица шаблонов
components.py бойцы, ИИ, рюкзак, свитки, экипировка
actions.py действия одним классом на действие
input_handlers.py экраны и их клавиши
renderer.py карта, интерфейс, окна
graphics.py вся графика кодом
message_log.py журнал
savegame.py JSON-сохранения
tests/ headless-тест
Баланс целиком в settings.py: количество этажей, радиус факела, сколько
монстров рождается на глубине. Кто на какой глубине появляется — в таблицах
MONSTER_CHANCES и ITEM_CHANCES в procgen.py, там же веса.
Тест гоняет игру без окна и проверяет всё подряд — связность сорока этажей, что стены реально загораживают обзор, что A* строит непрерывный путь и сдаётся на недостижимой цели, бой, повышение уровня, рюкзак, прицельный свиток, спуск до самого низа с короной, смерть, и сохранение с загрузкой туда-обратно:
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy python tests/smoke_test.py
Он же перерисовывает скриншот в начале файла.
Спуск односторонний, наверх вернуться нельзя — так проще и с памятью, и с сохранением, но лестниц вверх мне немного не хватает. Нет дверей, ловушек, голода и магазинов. Монстры не умеют разбегаться, когда им плохо, и это, пожалуй, первое что я хочу починить. Звука нет совсем.
Не смотря на то что этажей всего восемь, до конца дойти не так уж просто — я сам проходил раза с четвёртого.
MIT. Берите, ломайте, переделывайте. Папка проекта называется ashen-depths,
если будете искать её на диске.
RustamChu, 2026
