initial commit
This commit is contained in:
commit
e8417279bb
7 changed files with 602 additions and 0 deletions
74
.gitignore
vendored
Normal file
74
.gitignore
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Rust ###
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos
|
||||
roms
|
||||
.fleet
|
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "chip8"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pixels = "0.12.1"
|
||||
pretty-hex = "0.3.0"
|
||||
rand = "0.8.5"
|
||||
winit = "0.28.3"
|
||||
winit_input_helper = "0.14.1"
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# CHIP-8 Emulator
|
||||
|
||||
A kinda janky CHIP-8 emulator I've written using Rust. This should theoretically be cross platform with the libraries I have used.
|
349
src/chip8/chip8.rs
Normal file
349
src/chip8/chip8.rs
Normal file
|
@ -0,0 +1,349 @@
|
|||
use pretty_hex::*;
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
const FONT_OFFSET: u16 = 0x050;
|
||||
|
||||
struct Options {
|
||||
modern_shift: bool,
|
||||
modern_jump_with_offset: bool,
|
||||
modern_store_and_load: bool,
|
||||
modern_add_to_index: bool
|
||||
}
|
||||
|
||||
pub struct Emulator<'a> {
|
||||
// 4k RAM
|
||||
memory: [u8; 4096],
|
||||
|
||||
// 16-bit program counter
|
||||
pc: u16,
|
||||
|
||||
// 16-bit index register
|
||||
i: u16,
|
||||
|
||||
// stack of 16-bit addresses
|
||||
stack: Vec<u16>,
|
||||
|
||||
// 8-bit delay timer
|
||||
delay_timer: u8,
|
||||
|
||||
// 8-bit sound timer
|
||||
sound_timer: u8,
|
||||
|
||||
// 16 x 8-bit variable registers V0-VF
|
||||
vr: [u8; 16],
|
||||
|
||||
cpu_speed: Duration,
|
||||
timer_speed: Duration,
|
||||
last_cpu_cycle: Instant,
|
||||
last_timer_decrement: Instant,
|
||||
options: Options,
|
||||
peripherals: &'a dyn Peripherals,
|
||||
}
|
||||
|
||||
impl<'a> Emulator<'a> {
|
||||
pub fn new<'b>(peripherals: &'b dyn Peripherals, cpu_clock_speed: u32) -> Emulator<'b> {
|
||||
let cpu_duration = Duration::from_secs(1) / cpu_clock_speed;
|
||||
let timer_duration = Duration::from_secs(1) / 60;
|
||||
|
||||
let mut emulator = Emulator {
|
||||
memory: [0; 4096],
|
||||
pc: 0x200,
|
||||
i: 0,
|
||||
stack: Vec::new(),
|
||||
delay_timer: 0,
|
||||
sound_timer: 0,
|
||||
vr: [0; 16],
|
||||
cpu_speed: cpu_duration,
|
||||
timer_speed: timer_duration,
|
||||
last_cpu_cycle: Instant::now(),
|
||||
last_timer_decrement: Instant::now(),
|
||||
options: Options { modern_shift: true, modern_jump_with_offset: false, modern_store_and_load: true, modern_add_to_index: true },
|
||||
peripherals
|
||||
};
|
||||
emulator.load_memory(super::font::FONT.to_vec(), FONT_OFFSET);
|
||||
|
||||
emulator
|
||||
}
|
||||
|
||||
pub fn load_memory(&mut self, input: Vec<u8>, offset: u16) {
|
||||
for i in 0..input.len() {
|
||||
self.memory[offset as usize + i] = *input.get(i).expect("Input should have address");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpu_cycle_due(&self) -> bool {
|
||||
let delta = Instant::now() - self.last_cpu_cycle;
|
||||
delta >= self.cpu_speed
|
||||
}
|
||||
|
||||
pub fn timer_decrement_due(&self) -> bool {
|
||||
let delta = Instant::now() - self.last_timer_decrement;
|
||||
delta >= self.timer_speed
|
||||
}
|
||||
|
||||
pub fn decrement_timers(&mut self) {
|
||||
if let Some(new_delay_timer) = self.delay_timer.checked_sub(1) {
|
||||
self.delay_timer = new_delay_timer;
|
||||
}
|
||||
if let Some(new_sound_timer) = self.sound_timer.checked_sub(1) {
|
||||
self.sound_timer = new_sound_timer;
|
||||
}
|
||||
self.last_timer_decrement = Instant::now();
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self, screen: &mut [u8], keys: [bool; 16]) {
|
||||
// fetch
|
||||
let instruction: u16 = {
|
||||
let instruction1 = self
|
||||
.memory
|
||||
.get(self.pc as usize)
|
||||
.expect("Should be able to read memory instruction1");
|
||||
let instruction2 = self
|
||||
.memory
|
||||
.get((self.pc + 1) as usize)
|
||||
.expect("Should be able to read memory instruction2");
|
||||
((*instruction1 as u16) << 8) | *instruction2 as u16
|
||||
};
|
||||
|
||||
// increment program counter
|
||||
self.pc += 2;
|
||||
|
||||
// decode & execute
|
||||
let o: u8 = (instruction >> 12) as u8;
|
||||
let x: u8 = ((instruction & 0x0F00) >> 8) as u8; // u4
|
||||
let y: u8 = ((instruction & 0x00F0) >> 4) as u8; // u4
|
||||
let n: u8 = (instruction & 0x000F) as u8; // u4
|
||||
let nn: u8 = (instruction & 0x00FF) as u8; // u8
|
||||
let nnn: u16 = instruction & 0x0FFF; // u12
|
||||
|
||||
let vx = self.vr[x as usize];
|
||||
let vy = self.vr[y as usize];
|
||||
|
||||
// println!("Execute: 0x{:X}", instruction);
|
||||
|
||||
match (o, x, y, n) {
|
||||
// 00E0 - clear display
|
||||
(0x0, 0x0, 0xE, 0x0) => self.peripherals.clear_display(screen),
|
||||
// 00EE - subroutine
|
||||
(0x0, 0x0, 0xE, 0xE) => {
|
||||
self.pc = self.stack.pop().expect("Should have address on stack to return from");
|
||||
},
|
||||
// 0NNN - execute machine language routine
|
||||
(0x0, _, _, _) => (),
|
||||
// 1NNN - jump
|
||||
(0x1, _, _, _) => self.pc = nnn,
|
||||
// 2NNN - subroutine
|
||||
(0x2, _, _, _) => {
|
||||
// put pc in stack
|
||||
self.stack.push(self.pc);
|
||||
// set pc to address
|
||||
self.pc = nnn;
|
||||
},
|
||||
// 3XNN - skip if vx == nn
|
||||
(0x3, _, _, _) => {
|
||||
if vx == nn {
|
||||
self.pc += 2;
|
||||
}
|
||||
},
|
||||
// 4XNN - skip if vx != nn
|
||||
(0x4, _, _, _) => {
|
||||
if vx != nn {
|
||||
self.pc += 2;
|
||||
}
|
||||
},
|
||||
// 5XY0 - skip if vx == vy
|
||||
(0x5, _, _, 0x0) => {
|
||||
if vx == vy {
|
||||
self.pc += 2;
|
||||
}
|
||||
},
|
||||
// 6XNN - set register vx
|
||||
(0x6, _, _, _) => self.vr[x as usize] = nn,
|
||||
// 7XNN - add value to register vx
|
||||
(0x7, _, _, _) => self.vr[x as usize] = vx.wrapping_add(nn),
|
||||
// 8XY0 - set (vx = vy)
|
||||
(0x8, _, _, 0x0) => self.vr[x as usize] = vy,
|
||||
// 8XY1 - or (vx or vy)
|
||||
(0x8, _, _, 0x1) => self.vr[x as usize] = vx | vy,
|
||||
// 8XY2 - and (vx and vy)
|
||||
(0x8, _, _, 0x2) => self.vr[x as usize] = vx & vy,
|
||||
// 8XY3 - xor (vx xor vy)
|
||||
(0x8, _, _, 0x3) => self.vr[x as usize] = vx ^ vy,
|
||||
// 8XY4 - add (vx + vy)
|
||||
(0x8, _, _, 0x4) => {
|
||||
let (value, of) = vx.overflowing_add(vy);
|
||||
self.vr[x as usize] = value;
|
||||
self.vr[0xF] = if of { 1 } else { 0 };
|
||||
},
|
||||
// 8XY5 - subtract (vx - vy)
|
||||
(0x8, _, _, 0x5) => {
|
||||
let (value, uf) = vx.overflowing_sub(vy);
|
||||
self.vr[x as usize] = value;
|
||||
if vx > vy {
|
||||
self.vr[0xF] = 1;
|
||||
} else if vy > vx && uf {
|
||||
self.vr[0xF] = 0;
|
||||
}
|
||||
},
|
||||
// 8XY7 - subtract (vy - vx)
|
||||
(0x8, _, _, 0x7) => {
|
||||
let (value, uf) = vy.overflowing_sub(vx);
|
||||
self.vr[x as usize] = value;
|
||||
if vy > vx {
|
||||
self.vr[0xF] = 1;
|
||||
} else if vx > vy && uf {
|
||||
self.vr[0xF] = 0;
|
||||
}
|
||||
},
|
||||
// 8XY6 - shift right
|
||||
(0x8, _, _, 0x6) => {
|
||||
if !self.options.modern_shift {
|
||||
self.vr[x as usize] = vy;
|
||||
}
|
||||
let vf = vx & 1;
|
||||
self.vr[x as usize] = vx >> 1;
|
||||
self.vr[0xF] = vf;
|
||||
},
|
||||
// 8XYE - shift left
|
||||
(0x8, _, _, 0xE) => {
|
||||
if !self.options.modern_shift {
|
||||
self.vr[x as usize] = vy;
|
||||
}
|
||||
let vf = vx >> 7;
|
||||
self.vr[x as usize] = vx << 1;
|
||||
self.vr[0xF] = vf;
|
||||
},
|
||||
// 9XY0 - skip if vx != vy
|
||||
(0x9, _, _, 0) => {
|
||||
if vx != vy {
|
||||
self.pc += 2;
|
||||
}
|
||||
}
|
||||
// ANNN - set i register
|
||||
(0xA, _, _, _) => self.i = nnn,
|
||||
// BNNN - jump with offset
|
||||
(0xB, _, _, _) => {
|
||||
let offset = {
|
||||
if self.options.modern_jump_with_offset {
|
||||
vx
|
||||
} else {
|
||||
self.vr[0]
|
||||
}
|
||||
};
|
||||
self.pc = nnn + offset as u16;
|
||||
},
|
||||
// CXNN - random
|
||||
(0xC, _, _, _) => {
|
||||
let random: u8 = rand::random();
|
||||
self.vr[x as usize] = random & nn;
|
||||
},
|
||||
// DXYN - display
|
||||
(0xD, _, _, _) => {
|
||||
let x_coordinate: usize = (vx % (self.peripherals.display_width() as u8)).into();
|
||||
let y_coordinate: usize = (vy % (self.peripherals.display_height() as u8)).into();
|
||||
|
||||
// set vf to 0
|
||||
self.vr[0xF] = 0;
|
||||
|
||||
for height in 0..(n as usize) {
|
||||
if y_coordinate + height >= self.peripherals.display_height() {
|
||||
break;
|
||||
}
|
||||
|
||||
let sprite_line = self.memory[self.i as usize + height];
|
||||
|
||||
for i in 0..8 {
|
||||
if x_coordinate + i >= self.peripherals.display_width() {
|
||||
break;
|
||||
}
|
||||
|
||||
let state: bool = (sprite_line >> (7-i)) % 2 == 1;
|
||||
if state {
|
||||
let turned_off = self.peripherals.draw_pixel(screen, x_coordinate + i, y_coordinate + height);
|
||||
if turned_off {
|
||||
self.vr[0xF] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// EX9E - skip if key
|
||||
(0xE, _, 0x9, 0xE) => {
|
||||
if keys[vx as usize] {
|
||||
self.pc += 2;
|
||||
}
|
||||
},
|
||||
// EXA1 - skip if key
|
||||
(0xE, _, 0xA, 0x1) => {
|
||||
if !keys[vx as usize] {
|
||||
self.pc += 2;
|
||||
}
|
||||
},
|
||||
// FX07 - timers (set vx to delay timer)
|
||||
(0xF, _, 0x0, 0x7) => self.vr[x as usize] = self.delay_timer,
|
||||
// FX15 - timers (set delay timer to vx)
|
||||
(0xF, _, 0x1, 0x5) => self.delay_timer = vx,
|
||||
// FX18 - timers (set sound timer to vx)
|
||||
(0xF, _, 0x1, 0x8) => self.sound_timer = vx,
|
||||
// FX1E - add to index
|
||||
(0xF, _, 0x1, 0xE) => {
|
||||
self.i += vx as u16;
|
||||
if self.options.modern_add_to_index && self.i > 0x1000 {
|
||||
self.vr[0xF] = 1;
|
||||
}
|
||||
},
|
||||
// FX0A - get key
|
||||
(0xF, _, 0x0, 0xA) => {
|
||||
if !keys.iter().any(|k| *k) {
|
||||
self.pc -= 2;
|
||||
}
|
||||
},
|
||||
// FX29 - font character
|
||||
(0xF, _, 0x2, 0x9) => self.i = FONT_OFFSET + ((vx & 0xF) as u16 * 20),
|
||||
// FX33 - binary-coded decimal conversion
|
||||
(0xF, _, 0x3, 0x3) => {
|
||||
let digits: Vec<_> = vx.to_string().chars().map(|d| d.to_digit(10).unwrap() as u8).collect();
|
||||
self.memory[self.i as usize] = digits.get(0).unwrap_or(&0).to_owned();
|
||||
self.memory[self.i as usize + 1] = digits.get(1).unwrap_or(&0).to_owned();
|
||||
self.memory[self.i as usize + 2] = digits.get(2).unwrap_or(&0).to_owned();
|
||||
},
|
||||
// FX55 - store memory
|
||||
(0xF, _, 0x5, 0x5) => {
|
||||
for i in 0..=x as usize {
|
||||
self.memory[self.i as usize + i] = self.vr[i];
|
||||
}
|
||||
if !self.options.modern_store_and_load {
|
||||
self.i += x as u16;
|
||||
}
|
||||
},
|
||||
// FX65 - load memory
|
||||
(0xF, _, 0x6, 0x5) => {
|
||||
for i in 0..=x as usize {
|
||||
self.vr[i] = self.memory[self.i as usize + i];
|
||||
}
|
||||
if !self.options.modern_store_and_load {
|
||||
self.i += x as u16;
|
||||
}
|
||||
},
|
||||
_ => panic!("Unimplemented instruction: 0x{:X}", instruction)
|
||||
}
|
||||
|
||||
self.last_cpu_cycle = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Emulator<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let registers: String = self.vr.iter().enumerate().map(|(i, r)| format!("V{:X}: 0x{:x}", i, r)).collect::<Vec<String>>().join(", ");
|
||||
let stack: String = self.stack.iter().map(|a| format!("0x{:x}", a)).collect::<Vec<String>>().join(", ");
|
||||
let dump = pretty_hex(&self.memory);
|
||||
write!(f, "PC: 0x{:x}, I: 0x{:x}, DT: 0x{:x}, ST: 0x{:x}\n{}\nStack: {}\n\n{}", self.pc, self.i, self.delay_timer, self.sound_timer, registers, stack, dump)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Peripherals {
|
||||
fn display_width(&self) -> usize;
|
||||
fn display_height(&self) -> usize;
|
||||
fn draw_pixel(&self, screen: &mut [u8], x: usize, y: usize) -> bool;
|
||||
fn clear_display(&self, screen: &mut[u8]);
|
||||
}
|
18
src/chip8/font.rs
Normal file
18
src/chip8/font.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
pub const FONT: [u8; 80] = [
|
||||
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
||||
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
||||
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
||||
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
||||
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
||||
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
||||
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
||||
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
||||
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
||||
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
||||
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
||||
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
||||
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
||||
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
||||
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
||||
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
|
||||
];
|
2
src/chip8/mod.rs
Normal file
2
src/chip8/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod chip8;
|
||||
mod font;
|
143
src/main.rs
Normal file
143
src/main.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
mod chip8;
|
||||
|
||||
use std::error::Error;
|
||||
use pixels::{Pixels, SurfaceTexture};
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, VirtualKeyCode},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
use chip8::chip8::{Emulator, Peripherals};
|
||||
use std::fs;
|
||||
use std::env;
|
||||
|
||||
const WIDTH: u32 = 64;
|
||||
const HEIGHT: u32 = 32;
|
||||
const DEFAULT_SCALE: f64 = 6.0;
|
||||
const COLOR_WHITE: [u8; 3] = [0xFF, 0xFF, 0xFF];
|
||||
const COLOR_BLACK: [u8; 3] = [0x00, 0x00, 0x00];
|
||||
const KEY_MAP: [VirtualKeyCode; 16] = [
|
||||
VirtualKeyCode::X, VirtualKeyCode::Key1, VirtualKeyCode::Key2, VirtualKeyCode::Key3,
|
||||
VirtualKeyCode::Q, VirtualKeyCode::W, VirtualKeyCode::E, VirtualKeyCode::A,
|
||||
VirtualKeyCode::S, VirtualKeyCode::D, VirtualKeyCode::Z, VirtualKeyCode::C,
|
||||
VirtualKeyCode::Key4, VirtualKeyCode::R, VirtualKeyCode::F, VirtualKeyCode::V
|
||||
];
|
||||
|
||||
struct EmulatorPeripherals {
|
||||
width: usize,
|
||||
height: usize
|
||||
}
|
||||
|
||||
impl EmulatorPeripherals {
|
||||
fn set_pixel(&self, screen: &mut [u8], x: usize, y: usize, state: bool) {
|
||||
let i = ((y * self.width) + x) * 4;
|
||||
let color = {
|
||||
match state {
|
||||
true => COLOR_WHITE,
|
||||
false => COLOR_BLACK
|
||||
}
|
||||
};
|
||||
screen[i] = color[0];
|
||||
screen[i+1] = color[1];
|
||||
screen[i+2] = color[2];
|
||||
screen[i+3] = 0xFF;
|
||||
}
|
||||
|
||||
fn get_pixel(&self, screen: &[u8], x: usize, y: usize) -> bool {
|
||||
let i = ((y * self.width) + x) * 4;
|
||||
return screen[i] == 255;
|
||||
}
|
||||
}
|
||||
|
||||
impl Peripherals for EmulatorPeripherals {
|
||||
fn display_width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn display_height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn draw_pixel(&self, screen: &mut [u8], x: usize, y: usize) -> bool {
|
||||
let current = self.get_pixel(screen, x, y);
|
||||
self.set_pixel(screen, x, y, !current);
|
||||
current
|
||||
}
|
||||
|
||||
fn clear_display(&self, screen: &mut [u8]) {
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
self.set_pixel(screen, x, y, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let event_loop = EventLoop::new();
|
||||
let mut input = WinitInputHelper::new();
|
||||
|
||||
let window = {
|
||||
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
|
||||
let scaled_size = LogicalSize::new(WIDTH as f64 * DEFAULT_SCALE, HEIGHT as f64 * DEFAULT_SCALE);
|
||||
WindowBuilder::new()
|
||||
.with_title("CHIP-8")
|
||||
.with_inner_size(scaled_size)
|
||||
.with_min_inner_size(size)
|
||||
.build(&event_loop)?
|
||||
};
|
||||
|
||||
let mut pixels = {
|
||||
let window_size = window.inner_size();
|
||||
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||
Pixels::new(WIDTH, HEIGHT, surface_texture)?
|
||||
};
|
||||
|
||||
let mut emulator = Emulator::new(&EmulatorPeripherals{width: WIDTH as usize, height: HEIGHT as usize}, 600);
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let rom = fs::read(args.get(1).expect("ROM path should be passed as argument")).expect("Should be able to read ROM");
|
||||
emulator.load_memory(rom, 0x200);
|
||||
|
||||
let mut keys = [false; 16];
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
if emulator.cpu_cycle_due() {
|
||||
emulator.cycle(pixels.frame_mut(), keys);
|
||||
}
|
||||
|
||||
if emulator.timer_decrement_due() {
|
||||
emulator.decrement_timers();
|
||||
}
|
||||
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
pixels.render().expect("Should be able to render");
|
||||
}
|
||||
|
||||
if input.update(&event) {
|
||||
if input.key_pressed(VirtualKeyCode::Escape) || input.close_requested() || input.destroyed() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
keys = [false; 16];
|
||||
for (i, key) in KEY_MAP.iter().enumerate() {
|
||||
if input.key_held(*key) {
|
||||
keys[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(size) = input.window_resized() {
|
||||
if let Err(err) = pixels.resize_surface(size.width, size.height) {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window.request_redraw();
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue