cge_nes 0.1.2

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! Canonical front-end bootstrap example.
//!
//! Loads an iNES ROM from disk, runs one complete frame (262 scanlines), and
//! prints the rendered frame buffer's size plus the count of non-background
//! pixels so a user can see the emulator actually produced output.
//!
//! Run with:
//!
//! ```text
//! cargo run --example run_frame -- path/to/game.nes
//! ```
//!
//! A real front-end would replace the `NullHostInput` below with an adapter
//! that reads the host keyboard / gamepad each frame, and would render
//! `console.screen_colors()` to a window via SDL2 / wgpu / Bevy / etc.

use cge_nes::{
    load_rom, ConnectedSocket, GamepadButtonsPressedFlags, GamepadState, HostInput,
    InputDeviceState, NesConsole,
};
use std::{env, fs, io::Read, process};

/// Adapter that reports no controller input. Replace with a real
/// implementation that polls the host keyboard / gamepad.
struct NullHostInput;

impl HostInput for NullHostInput {
    fn input_device_state(&mut self, _player: ConnectedSocket) -> InputDeviceState {
        InputDeviceState::Disconnected
    }
}

fn main() {
    let rom_path = match env::args().nth(1) {
        Some(p) => p,
        None => {
            eprintln!("usage: cargo run --example run_frame -- <rom_path>");
            process::exit(2);
        }
    };

    let mut rom_data = Vec::new();
    if let Err(e) = fs::File::open(&rom_path).and_then(|mut f| f.read_to_end(&mut rom_data)) {
        eprintln!("failed to read {rom_path}: {e}");
        process::exit(1);
    }

    let cartridge = match load_rom(&mut rom_data.as_slice()) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("failed to load ROM: {e}");
            process::exit(1);
        }
    };

    let mut console = NesConsole::new();
    console.insert_cartridge(cartridge);
    console.switch_on();

    let mut host = NullHostInput;
    console.run_frame(&mut host);

    let frame = console.screen_colors();
    let non_background = frame.iter().filter(|c| c.value() != 0).count();
    println!("frame OK");
    println!("  screen size : {} pixels ({}x{})", frame.len(), 256, 240);
    println!(
        "  non-background pixels: {} ({:.1}%)",
        non_background,
        100.0 * non_background as f64 / frame.len() as f64
    );
}

// Keep `GamepadState` reachable so this example stays in sync with the
// public API surface even when the host input is hardcoded as
// `InputDeviceState::Disconnected`.
#[allow(dead_code)]
fn _example_gamepad() -> GamepadState {
    let mut buttons = GamepadButtonsPressedFlags::empty();
    buttons |= GamepadButtonsPressedFlags::A;
    buttons |= GamepadButtonsPressedFlags::START;
    GamepadState::new(buttons)
}