295. Visualización manual de fondo en pygame desde main

Añadir la visualización manual de fondo en pygame a main_only_background.py.

Lección 295 de 356 · tests/chapter_08_manual_main/test_295_manual_main_pygame_background_display.py

Archivo a crear/actualizar en la carpeta raíz

main_only_background.py

Por qué existe este paso

core_validator.py demuestra que el emulador puede arrancar una ROM local y avanzar fotogramas sin pygame. main_only_background.py es el ejecutor visual manual histórico solo de fondo: debe usar pygame para mostrar el Framebuffer de fondo producido por el emulador tras cada fotograma.

Flujo de trabajo recomendado

Empieza copiando la estructura funcional de core_validator.py y añade solo las piezas de pygame/visualización que falten:

  • importar pygame
  • importar draw_framebuffer desde tools.show_framebuffer
  • definir SCALE
  • crear un framebuffer inicial para las dimensiones de la ventana
  • abrir una ventana de pygame
  • procesar eventos pygame.QUIT
  • tras cada paso de fotograma, renderizar el framebuffer de fondo
  • dibujar el framebuffer y volcar la pantalla
  • llamar a pygame.quit() en finally

Límite importante

pygame está permitido en main_only_background.py porque es un punto de entrada manual/frontend. pygame no debe importarse en los módulos del núcleo del emulador.

Regla legal/de pruebas importante

El repositorio del tutorial no debe incluir archivos de ROM comerciales. Las pruebas automatizadas no deben requerir MarioBros.nes ni abrir una ventana real de pygame.

Hash de referencia usado durante el desarrollo del tutorial [Mario Bros. (World).nes]:

MD5 5d7bcc400a2fb5fa27346da345d3bb62  MarioBros.nes
SHA1 314b6e46e814f955b52ac954f67dab849582fe77

Este hash es solo una referencia manual. Las pruebas no deben requerir este archivo ni este hash exacto, porque los usuarios pueden tener volcados/revisiones legales distintos.

Ejemplo de implementación sugerido

from pathlib import Path

import pygame

from emulator.bus.cpu_bus import CpuBus
from emulator.cartridge.cartridge import Cartridge
from emulator.console import Console
from emulator.cpu.cpu import CPU
from tools.show_framebuffer import draw_framebuffer


ROM_PATH = Path("MarioBros.nes")
debug_mode = False
SCALE = 3


def main() -> None:
    if not ROM_PATH.exists():
        raise FileNotFoundError(
            "MarioBros.nes not found. Provide your own legal local copy. "
            "This file is intentionally not included in the tutorial repository."
        )

    cartridge = Cartridge.from_ines_bytes(ROM_PATH.read_bytes())

    cpu_bus = CpuBus(cartridge=cartridge)
    cpu = CPU(cpu_bus)
    console = Console(cpu=cpu, ppu=cpu_bus.ppu)

    cpu.reset()
    framebuffer = console.render_background_framebuffer()

    print(f"Loaded {ROM_PATH}")
    print(f"CPU reset PC = ${cpu.pc:04X}")
    print("Starting frame loop. Close the window or press Ctrl+C to stop.")

    pygame.init()
    try:
        window = pygame.display.set_mode(
            (framebuffer.width * SCALE, framebuffer.height * SCALE)
        )
        pygame.display.set_caption("NES Background")

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

            executed = console.step_until_next_frame()

            framebuffer = console.render_background_framebuffer()
            draw_framebuffer(window, framebuffer, SCALE)
            pygame.display.flip()

            if debug_mode:
                print(
                    f"frame={console.ppu.frame} "
                    f"pc=${cpu.pc:04X} "
                    f"instructions={executed}"
                )
    except KeyboardInterrupt:
        print("

Detenido por el usuario.")

    finally:
        pygame.quit()


if __name__ == "__main__":
    main()

Comando manual

uv run python main_only_background.py

Comportamiento manual esperado

main_only_background.py abre una ventana de pygame y muestra el framebuffer de fondo actual. La ventana puede verse incompleta porque los sprites no están implementados en este ejecutor histórico. Cierra la ventana o pulsa Ctrl+C para detenerlo.

Ejemplo aproximado de la expectativa visual

+------------------------------+
|                              |
|          MARIO BROS.         |
|                              |
|        1 PLAYER GAME A       |
|        1 PLAYER GAME B       |
|        2 PLAYER GAME A       |
|        2 PLAYER GAME B       |
|                              |
|   background is shown        |
|   sprites are  missing       |
|                              |
+------------------------------+

Tras 30 segundos - 1 minuto, también deberías ver un fondo/composición similar al escenario clásico de Mario Bros. de 1983. Los sprites siguen faltando, pero la escena de fondo debería hacer que el emulador se sienta vivo:

+------------------------------+
|  I-0000   TOP-0000  II-0000  |
|                              |
|  ====                  ====  |
|==                          ==|
|                              |
|        ──────────────        |
|─────                    ─────|
|                              |
|                              |
| ─────────── POW  ─────────── |
|====                      ====|
|------------------------------|
+------------------------------+

Esto es solo un boceto ASCII aproximado. La señal manual importante es que los tiles de fondo/título/escenario aparezcan y cambien con el tiempo. Es esperable que falten los personajes/enemigos en movimiento hasta que se implemente el renderizado de sprites.

Nota de rendimiento

El ejecutor manual de pygame puede sentirse lento en este momento. Eso es esperable en esta etapa. El ayudante draw_framebuffer actual es deliberadamente simple y dibuja muchos rectángulos escalados desde Python. Una futura optimización podrá sustituirlo por una vía de subida de framebuffer más rápida, pero este paso se centra en la salida visual esperada y en los límites de arquitectura, no en la velocidad.

Por qué esta prueba no llama a main()

main_only_background.py abre una ventana real de pygame y ejecuta un bucle manual. Las pruebas automatizadas deben permanecer finitas y deben limitarse a inspeccionar la estructura.

Fuera de alcance

  • Optimización de subida rápida de framebuffer
  • Mapeo de teclado/mando de pygame
  • Renderizado de sprites
  • Verificar píxeles visuales exactos de una ROM comercial
  • Llamar a main() desde pytest

Ejecutar esta lección

uv run pytest tests/chapter_08_manual_main/test_295_manual_main_pygame_background_display.py -v