Игра, созданная с ИИ с одного промта. Тест 4: игра «ОРБИТА-9» - космический мини-гольф с настоящей гравитацией. Каждая лунка доказуемо проходима: генератор сам забивает её тем же физическим движком. Python + pygame.
Космический мини-гольф. Девять лунок, настоящая гравитация: оттягиваете шар мышью как рогатку, отпускаете — и он летит между планетами, огибая их по дугам, отскакивая от поверхности или мягко прилипая к ней, чтобы следующий удар был уже с чужого горизонта. Пульсары отталкивают, чёрные дыры глотают шар со штрафом, червоточины перебрасывают его через полкарты. Пар на каждой лунке — 3, но любая из них честно проходится в один удар, и это не обещание, а доказанный факт: см. ниже про генератор.
Это четвёртая моя игра на питоне и первая, где главный герой — численное интегрирование. После шутера, рогалика и tower defense хотелось чего-то тихого и красивого, где вся сложность сидит не в противниках, а в физике.
Ассетов по традиции ноль: звёздное небо с туманностями, планеты с тенью и полосами, свечение — всё рисуется кодом при запуске, звуки синтезируются математикой в буфер.
Нужен Python 3.8+ и pygame. На Windows ставить руками ничего не надо.
Windows, в один клик
- Скачайте папку с проектом и распакуйте куда угодно. Именно распакуйте, из архива запускать бесполезно.
- Дважды кликните
PLAY.bat. - Откроется консоль. Первый запуск идёт около полуминуты: скрипт найдёт Python (или поставит его через winget) и докачает pygame, примерно 10 МБ.
- Когда появится «Запускаю», откроется окно игры. Консоль не закрывайте.
- В меню нажмите
n— новая трасса.
Можно и без bat-файла: правый клик по run.ps1 → «Выполнить с помощью
PowerShell». Копировать текст скрипта в консоль не надо — при копипасте он
не знает, где лежит; от этого он теперь защищён, но всё равно не надо.
Linux и macOS
python3 -m pip install -r requirements.txt
python3 main.py
Если что-то пошло не так — консоль моргнула и закрылась → запустите
run.ps1 правой кнопкой, текст ошибки останется на экране; «выполнение
сценариев отключено» → запускайте через PLAY.bat; «python не является
внутренней или внешней командой» → переставьте Python с галочкой
«Add Python to PATH».
мышь зажмите возле шара, оттяните, отпустите — рогатка
пунктир прогноз траектории: та же физика, тот же шаг времени
R переиграть лунку
H подсказка — удар, которым лунку прошёл сам генератор
TAB карточка счёта
Esc в меню, партия сохраняется сама
Правила гольфовые: каждый запуск — удар. Улетел за экран или попал в чёрную дыру — штрафной удар и возврат на место, откуда бил. Медленное касание планеты — шар сел, быстрое — отскок с потерей энергии. Через двенадцать секунд полёта шар замирает где летел: вечно висеть на орбите красиво, но некогда.
Горячие клавиши по физическим клавишам, так что работают в любой раскладке.
Один step() на всё. Настоящий шар, пунктир прицела и солвер генератора гоняют одну и ту же функцию шага физики с одним и тем же фиксированным шагом времени 1/120. Из этого одного решения следуют сразу три гарантии: прогноз траектории никогда не врёт, найденное генератором решение точно воспроизводится в игре, и вся симуляция детерминирована для тестов.
Интегратор. Полуявный Эйлер: сначала скорость, потом позиция. Разница с наивным вариантом не косметическая — наивный на орбитальном движении тихо накачивает энергию, и шар по спирали уходит прочь. Полуявный держит орбиту: в тесте шар двадцать секунд крутится вокруг планеты и не разлетается. Гравитация — честный обратный квадрат по всем телам, у пульсаров масса отрицательная.
Генератор с доказательством. Самое интересное. Раскидать планеты случайно легко, но получится ли попасть в лунку? Генератор не надеется, а проверяет: сетка из 270 запусков (90 углов × 3 силы) прогоняется через ту же физику, и если ни один не попал — раскладка в мусор. А если попал, но траектория победного удара почти прямая, раскладка тоже в мусор: лунка, где гравитация не понадобилась, скучная. Мерой служит максимальное отклонение пути от прямой старт-лунка. Что остаётся — всегда проходимо и всегда изогнуто, а победный удар сохраняется и показывается по клавише H как подсказка.
Трасса из сида. Девять лунок раскрываются из одного числа, сложность нарастает по ходу: сначала голые планеты, потом пульсар, червоточины, чёрная дыра, пояс астероидов, а на девятой всё сразу.
Сохранение. JSON на пару строк: сид, номер лунки, счёт. Сама лунка воспроизводится из сида один в один.
Файлы:
main.py окно, состояния, рогатка мышью
settings.py вся физика и баланс числами
physics.py гравитация, интегратор, столкновения — один step()
level.py генератор лунок и его солвер
engine.py партия: удары, штрафы, счёт
graphics.py небо, планеты, свечение — всё кодом
renderer.py поле, интерфейс, экраны
sounds.py синтезатор эффектов
savegame.py JSON-сохранение
tests/ headless-тест
Тест проверяет 129 вещей: что гравитация тянет куда надо, что орбита двадцать секунд не разваливается, отскок и прилипание, червоточину с сохранением скорости, чёрную дыру, штрафы, что три полные трассы из разных сидов доказуемо проходимы и каждое решение реально топит шар через живой движок (девять лунок подряд в один удар — тест буквально играет в гольф), фильтр скучных прямых, рогатку, счёт в гольфовой нотации, сохранение туда-обратно и все экраны:
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy python tests/smoke_test.py
Он же рисует скриншот выше.
Ветра... шучу, в космосе с ним туго. Нет движущихся планет — лунка со спутником на орбите была бы прекрасна, и солвер бы её осилил, но прицел пришлось бы рисовать с учётом времени. Нет таблицы рекордов по сидам, хотя сид на то и сид, чтобы соревноваться на одной трассе.
Мой личный рекорд на случайной трассе — минус четыре. Подсказкой не пользовался, чем и горжусь.
MIT. Берите, ломайте, переделывайте. Папка проекта называется orbit-nine.
RustamChu, 2026
