NES TDD

286. Ручной главный цикл pygame

Добавить ручной главный цикл pygame для запуска проверки framebuffer.

Урок 286 из 356 · tests/chapter_05_rendering_pipeline/test_286_manual_pygame_main_loop.py

Файлы для обновления/создания

tools/show_framebuffer.py

Зачем нужен этот шаг

На предыдущем шаге были добавлены помощники, способные отрисовать Framebuffer на pygame Surface. На этом шаге добавляется ручной цикл окна, чтобы разработчик мог визуально убедиться, что чистые данные Framebuffer можно отобразить.

Важно

Это ручной инструмент для проверки работоспособности, а не автоматизированный тест рендеринга. Автоматизированные тесты не должны открывать настоящее окно pygame.

Запускайте инструмент как модуль Python

uv run python -m tools.show_framebuffer

Флаг -m в Python ожидает имя модуля, а не путь к файлу. Имя модуля использует точки:

tools.show_framebuffer

а не путь к файлу:

tools/show_framebuffer.py

Современный Python поддерживает такую структуру проекта как пакет пространства имён, поэтому в этом уроке файл __init__.py не требуется.

Ручные команды

Correct:
    uv run python -m tools.show_framebuffer

Incorrect:
    uv run python -m tools/show_framebuffer.py
    uv run python -m tools.show_framebuffer.py

Что такое главный цикл pygame?

Главный цикл pygame поддерживает окно активным, многократно выполняя

1. reading events
2. drawing to the window surface
3. presenting the drawn image

Минимальная структура

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    draw_framebuffer(window, framebuffer, SCALE)
    pygame.display.flip()

Что делает pygame.display.flip()? Она выводит текущее содержимое поверхности окна на экран. Проще говоря:

draw pixels -> flip -> user sees pixels

Ожидаемый визуальный результат при ручной проверке

Framebuffer в виде шахматной доски должен показывать чёрно-белый или тёмно-светлый узор, например:

+----------------+
| ██  ██  ██  ██ |
| ██  ██  ██  ██ |
|   ██  ██  ██   |
|   ██  ██  ██   |
| ██  ██  ██  ██ |
| ██  ██  ██  ██ |
+----------------+

Точный размер блоков зависит от размера framebuffer и SCALE, но важна следующая визуальная проверка:

alternating light and dark squares are visible
closing the window exits cleanly

Пример предлагаемой реализации

SCALE = 3


def main() -> None:
    framebuffer = make_checkerboard_framebuffer()

    pygame.init()
    try:
        window = pygame.display.set_mode(
            (framebuffer.width * SCALE, framebuffer.height * SCALE)
        )
        pygame.display.set_caption("Framebuffer Smoke Test")

        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

            draw_framebuffer(window, framebuffer, SCALE)
            pygame.display.flip()
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()

Архитектурное правило

pygame остаётся вне ядра эмулятора. Этот файл может импортировать pygame, поскольку находится в tools/. Не импортируйте pygame из emulator/rendering, emulator/ppu или emulator/console.

Вне рамок этого шага

  • тестирование настоящего окна pygame в pytest
  • загрузка ROM
  • рендеринг живого вывода Console
  • ввод с контроллера
  • спрайты

Запустить этот урок

uv run pytest tests/chapter_05_rendering_pipeline/test_286_manual_pygame_main_loop.py -v