Настоящий игровой цикл
screen.ontimer() планирует следующий тик и сразу возвращает управление — вместо того чтобы блокировать программу в while.
while — не единственный способ повторять тики
Первый прототип запускает игру через while igrovoj_shag(): ...
с time.sleep() в теле — простой и понятный блокирующий цикл.
Здесь легко сделать неверный вывод, так что стоит проговорить его отдельно:
screen.update() внутри цикла ДЕЙСТВИТЕЛЬНО обрабатывает
накопившиеся события Tkinter, включая нажатия клавиш — значит, обработчик, привязанный через
onkeypress(), технически может сработать прямо во время этого
вызова. Проблема такого цикла не в том, что клавиатура «не успевает» — а в том, что он
владеет управлением от начала и до конца партии и ни у кого не спрашивает
разрешения. Скорость в нём — одна константа ZADERZHKA_SEK,
которую по ходу игры не поменять; а паузу и перезапуск пришлось бы вручную вплетать в тело
цикла отдельными флагами и проверками на каждой итерации. И всё это время программа не может
заниматься ничем другим: она сидит внутри sleep().
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(callback, delay_ms) просит цикл событий Tkinter (тот же самый, что и в главе 17) вызвать callback примерно через delay_ms — и сразу возвращает управление. Между тиками программа полностью свободна: клавиатура, пауза, изменение скорости обрабатываются как обычные события, а не ждут своей очереди внутри цикла.time.sleep() останавливает весь процесс, включая обработку событий — окно перестанет реагировать на клавиатуру и закрытие ровно на время сна. screen.ontimer() ничего не блокирует: он передаёт часы циклу событий, который в это время продолжает обслуживать всё остальное.Каждый вызов game_tick() сам планирует следующий через
ontimer() — получается цепочка, а не цикл в привычном смысле.
Здесь есть тонкость: ontimer() только планирует будущий вызов —
он не удаляется сам по себе, если статус игры поменяется. Раздел 19.22 показывает, почему
поэтому пауза не может полагаться на одну лишь проверку status
внутри тика — и что происходит, если положиться только на неё.