Глава 19 · Модель игры

Настоящий игровой цикл

screen.ontimer() планирует следующий тик и сразу возвращает управление — вместо того чтобы блокировать программу в while.

while — не единственный способ повторять тики

Первый прототип запускает игру через while igrovoj_shag(): ... с time.sleep() в теле — простой и понятный блокирующий цикл. Здесь легко сделать неверный вывод, так что стоит проговорить его отдельно: screen.update() внутри цикла ДЕЙСТВИТЕЛЬНО обрабатывает накопившиеся события Tkinter, включая нажатия клавиш — значит, обработчик, привязанный через onkeypress(), технически может сработать прямо во время этого вызова. Проблема такого цикла не в том, что клавиатура «не успевает» — а в том, что он владеет управлением от начала и до конца партии и ни у кого не спрашивает разрешения. Скорость в нём — одна константа ZADERZHKA_SEK, которую по ходу игры не поменять; а паузу и перезапуск пришлось бы вручную вплетать в тело цикла отдельными флагами и проверками на каждой итерации. И всё это время программа не может заниматься ничем другим: она сидит внутри sleep().

ontimer_cikl.py
def game_tick(self):
    if self.state.status is not GameStatus.RUNNING:
        return
    # ...применить направление, подвинуть голову, проверить еду и столкновения...
    self.render()
    self.screen.ontimer(self.game_tick, self.state.delay_ms)
screen.ontimer() планирует следующий тик, а не блокирует программу
screen.ontimer(callback, delay_ms) просит цикл событий Tkinter (тот же самый, что и в главе 17) вызвать callback примерно через delay_ms — и сразу возвращает управление. Между тиками программа полностью свободна: клавиатура, пауза, изменение скорости обрабатываются как обычные события, а не ждут своей очереди внутри цикла.
time.sleep() в игровом цикле на Tkinter/Turtle — плохая идея
time.sleep() останавливает весь процесс, включая обработку событий — окно перестанет реагировать на клавиатуру и закрытие ровно на время сна. screen.ontimer() ничего не блокирует: он передаёт часы циклу событий, который в это время продолжает обслуживать всё остальное.

Каждый вызов game_tick() сам планирует следующий через ontimer() — получается цепочка, а не цикл в привычном смысле. Здесь есть тонкость: ontimer() только планирует будущий вызов — он не удаляется сам по себе, если статус игры поменяется. Раздел 19.22 показывает, почему поэтому пауза не может полагаться на одну лишь проверку status внутри тика — и что происходит, если положиться только на неё.

Практика: игровой цикл на ontimer()
Модуль turtle открывает нативное окно Python — выполните локально в VS Code, PyCharm или Jupyter
Практика выполняется локально
Открыть практику →