295. 手动 main pygame 背景显示
为 main_only_background.py 添加手动 pygame 背景显示。
第 295 / 356 · tests/chapter_08_manual_main/test_295_manual_main_pygame_background_display.py
在根目录创建/更新的文件
main_only_background.py为什么需要这一步
core_validator.py 证明了模拟器可以在不使用 pygame 的情况下启动本地 ROM 并逐帧执行。main_only_background.py 是历史版本的仅背景可视化手动运行器:它应使用 pygame 在每帧之后显示模拟器生成的背景 Framebuffer。
推荐的工作流程
先复制 core_validator.py 中可用的结构,然后仅添加缺少的 pygame/显示部分:
- 导入 pygame
- 从 tools.show_framebuffer 导入 draw_framebuffer
- 定义 SCALE
- 创建初始帧缓冲区以确定窗口尺寸
- 打开 pygame 窗口
- 处理 pygame.QUIT 事件
- 每帧执行后渲染背景帧缓冲区
- 绘制帧缓冲区并刷新显示
- 在 finally 中调用 pygame.quit()
重要边界
main_only_background.py 中允许使用 pygame,因为它是手动/前端入口点。模拟器核心模块不得导入 pygame。
重要的法律/测试规则
教程仓库不得包含商业 ROM 文件。自动化测试不得要求 MarioBros.nes 或打开真实的 pygame 窗口。
教程开发期间使用的参考哈希 [Mario Bros. (World).nes]:
MD5 5d7bcc400a2fb5fa27346da345d3bb62 MarioBros.nes
SHA1 314b6e46e814f955b52ac954f67dab849582fe77此哈希仅供手动参考。测试不得要求此文件或此确切哈希,因为用户可能拥有不同的合法转储/版本。
建议的实现示例
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("Stopped by user.")
finally:
pygame.quit()
if __name__ == "__main__":
main()手动命令
uv run python main_only_background.py预期的手动行为
main_only_background.py 会打开一个 pygame 窗口并显示当前背景帧缓冲区。由于此历史运行器尚未实现精灵渲染,窗口可能看起来不完整。关闭窗口或按 Ctrl+C 停止。
大致预期的视觉效果
+------------------------------+
| |
| MARIO BROS. |
| |
| 1 PLAYER GAME A |
| 1 PLAYER GAME B |
| 2 PLAYER GAME A |
| 2 PLAYER GAME B |
| |
| background is shown |
| sprites are missing |
| |
+------------------------------+约 30 秒到 1 分钟后,你还应看到类似经典 Mario Bros. 1983 关卡的背景/布局。精灵仍然缺失,但背景场景应使模拟器看起来已活跃运行:
+------------------------------+
| I-0000 TOP-0000 II-0000 |
| |
| ==== ==== |
|== ==|
| |
| ────────────── |
|───── ─────|
| |
| |
| ─────────── POW ─────────── |
|==== ====|
|------------------------------|
+------------------------------+这只是一个近似的 ASCII 示意图。重要的手动信号是背景/标题/关卡图块出现并随时间变化。在精灵渲染实现之前,缺少移动的角色/敌人是预期行为。
性能说明
手动 pygame 运行器目前可能感觉较慢。这在当前阶段是预期的。当前的 draw_framebuffer 辅助函数故意保持简单,从 Python 绘制大量缩放的矩形。未来的优化可以用更快的帧缓冲区上传路径替换它,但本步骤聚焦于预期视觉输出和架构边界,而非速度。
为什么本测试不调用 main()
main_only_background.py 会打开真实的 pygame 窗口并运行手动循环。自动化测试必须保持有限执行,且仅应检查结构。
超出本步骤范围
- 快速帧缓冲区上传优化
- pygame 键盘/控制器映射
- 精灵渲染
- 验证商业 ROM 的精确视觉像素
- 从 pytest 调用 main()
运行本课
uv run pytest tests/chapter_08_manual_main/test_295_manual_main_pygame_background_display.py -v