├── .gitignore ├── Cargo.toml ├── README.md ├── screenshot.png └── src ├── chip8.rs ├── display.rs ├── instruction.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | games 4 | art 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-8" 3 | version = "0.0.1" 4 | authors = ["Ryan Levick "] 5 | 6 | [dependencies] 7 | piston_window = "0.32.0" 8 | rand = "0.3.0" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust-8 2 | 3 | ![screenshot](screenshot.png) 4 | 5 | Rust-8 is an implementation of the CHIP-8 virtual machine written in Rust. 6 | 7 | ## What is CHIP-8? 8 | 9 | CHIP-8 is a virtual machine (along with a supporting programming language). 10 | Since the CHIP-8 VM does not expose the fact that it's running on a host CPU, 11 | in theory the VM could be translated to physical hardware. 12 | 13 | The CHIP-8 VM was used in the late '70s on some computers such as the [Telmac 14 | 1800](https://en.wikipedia.org/wiki/Telmac_1800) and on some calculators in the 15 | 1980's. 16 | 17 | CHIP-8 was mainly used as a gaming platform, and today you can play lots of 18 | games like Pong and Breakout on it. 19 | 20 | You can find a list of games [here](http://chip8.com/?page=84). 21 | 22 | You can find more information on the [CHIP-8 wikipedia page](https://en.wikipedia.org/wiki/CHIP-8) or on the [CHIP-8 website](http://chip8.com). 23 | 24 | ## Running it 25 | 26 | You can find lots of game ROMs online. The best source of games I've found is 27 | the CHIP-8 website listed above. 28 | 29 | Once you have a game ROM you can run it with: 30 | 31 | `cargo run -- $GAME` 32 | 33 | ## Why a CHIP-8 implementation? 34 | 35 | I was inspired by Jake Taylor's live streaming project of building an [N64 36 | emulator in Rust](https://github.com/yupferris/rustendo64). Having never built 37 | an emulator myself, I searched online for easy ways to get started. The CHIP-8 38 | is many people's first emulator because it's relatively easy and quick to finish. 39 | I got the emulator working in less than a week, for example. 40 | 41 | ## Why Rust? 42 | 43 | Rust is awesome. If you start a project where you think you need either C or 44 | C++, [reach for Rust next time](https://www.rust-lang.org/). 45 | 46 | ## References 47 | 48 | A big thank you to all the people who wrote references about the Chip-8 and 49 | posted them online. One reason that the Chip-8 is such a great learning 50 | VM/emulator is because of the awesome references. 51 | 52 | * Matthew Mikolay's [Mastering 53 | Chip-8](http://mattmik.com/files/chip8/mastering/chip8.html) 54 | * Cowgod's [Chip-8 Reference](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM) 55 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rylev/Rust-8/686a80269b696fc1ca38b192848e0ced93d88bdb/screenshot.png -------------------------------------------------------------------------------- /src/chip8.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | extern crate rand; 4 | 5 | use instruction::{Instruction, RawInstruction}; 6 | use display::{Display,SPRITES}; 7 | use rand::distributions::{IndependentSample, Range}; 8 | 9 | const NUM_GENERAL_PURPOSE_REGS: usize = 16; 10 | const MEMORY_SIZE: usize = 4 * 1024; 11 | const NUM_STACK_FRAMES: usize = 16; 12 | const PROGRAM_CODE_OFFSET: usize = 0x200; 13 | const CLOCK_RATE: f64 = 600.0; 14 | const NUM_KEYS: usize = 16; 15 | 16 | pub struct Chip8 { 17 | regs: [u8; NUM_GENERAL_PURPOSE_REGS], 18 | i_reg: u16, 19 | delay_timer_reg: u8, 20 | sound_timer_reg: u8, 21 | stack_pointer_reg: u8, 22 | program_counter_reg: u16, 23 | memory: [u8; MEMORY_SIZE], 24 | stack: [u16; NUM_STACK_FRAMES], 25 | key_to_wait_for: Option, 26 | keyboard: [bool; NUM_KEYS], 27 | pub display: Box, 28 | } 29 | 30 | impl Chip8 { 31 | pub fn new(program: Vec) -> Chip8 { 32 | let mut memory = [0; MEMORY_SIZE]; 33 | // TODO: do this more efficiently 34 | for (i, byte) in program.iter().enumerate() { 35 | memory[PROGRAM_CODE_OFFSET + i] = byte.clone(); 36 | } 37 | for (i, byte) in SPRITES.iter().enumerate() { 38 | memory[i] = byte.clone(); 39 | } 40 | 41 | Chip8 { 42 | regs: [0; NUM_GENERAL_PURPOSE_REGS], 43 | i_reg: 0, 44 | delay_timer_reg: 0, 45 | sound_timer_reg: 0, 46 | stack_pointer_reg: 0, 47 | program_counter_reg: PROGRAM_CODE_OFFSET as u16, 48 | memory: memory, 49 | stack: [0; NUM_STACK_FRAMES], 50 | key_to_wait_for: None, 51 | keyboard: [false; NUM_KEYS], 52 | display: Box::new(Display::new()), 53 | } 54 | } 55 | 56 | pub fn cycle(&mut self, seconds_since_last_cycle: f64) { 57 | let num_instructions = (seconds_since_last_cycle * CLOCK_RATE).round() as u64; 58 | 59 | for _ in 1..num_instructions { 60 | if self.delay_timer_reg > 0 { 61 | self.delay_timer_reg -= 1; 62 | } 63 | 64 | if self.key_to_wait_for == None { 65 | let instruction = self.instruction(); 66 | self.program_counter_reg = self.run_instruction(&instruction); 67 | } 68 | } 69 | } 70 | 71 | fn run_instruction(&mut self, instruction: &Instruction) -> u16 { 72 | match *instruction { 73 | Instruction::ClearDisplay => { 74 | self.display.clear(); 75 | self.program_counter_reg + 2 76 | } 77 | Instruction::Return => { 78 | let addr = self.stack[(self.stack_pointer_reg - 1) as usize]; 79 | self.stack_pointer_reg -= 1; 80 | addr + 2 81 | } 82 | Instruction::Jump(addr) => addr, 83 | Instruction::Call(addr) => { 84 | self.stack_pointer_reg += 1; 85 | self.stack[(self.stack_pointer_reg - 1) as usize] = self.program_counter_reg; 86 | addr 87 | } 88 | Instruction::SkipIfEqualsByte(reg, value) => { 89 | if self.read_reg(reg) == value { 90 | self.program_counter_reg + 4 91 | } else { 92 | self.program_counter_reg + 2 93 | } 94 | } 95 | Instruction::SkipIfNotEqualsByte(reg, value) => { 96 | if self.read_reg(reg) != value { 97 | self.program_counter_reg + 4 98 | } else { 99 | self.program_counter_reg + 2 100 | } 101 | } 102 | Instruction::SkipIfEqual(reg1, reg2) => { 103 | if self.read_reg(reg1) == self.read_reg(reg2) { 104 | self.program_counter_reg + 4 105 | } else { 106 | self.program_counter_reg + 2 107 | } 108 | } 109 | Instruction::LoadByte(reg, value) => { 110 | self.load_reg(reg, value); 111 | self.program_counter_reg + 2 112 | } 113 | Instruction::AddByte(reg_number, value) => { 114 | let reg_value = self.read_reg(reg_number); 115 | self.load_reg(reg_number, value.wrapping_add(reg_value)); 116 | self.program_counter_reg + 2 117 | } 118 | Instruction::Move(reg1, reg2) => { 119 | let value = self.read_reg(reg2); 120 | self.load_reg(reg1, value); 121 | self.program_counter_reg + 2 122 | } 123 | Instruction::Or(_, _) => { 124 | panic!("Not yet implemeneted: {:?}", instruction); 125 | } 126 | Instruction::And(reg1, reg2) => { 127 | let first = self.read_reg(reg1); 128 | let second = self.read_reg(reg2); 129 | self.load_reg(reg1, first & second); 130 | self.program_counter_reg + 2 131 | } 132 | Instruction::Xor(reg1, reg2) => { 133 | let first = self.read_reg(reg1); 134 | let second = self.read_reg(reg2); 135 | self.load_reg(reg1, first ^ second); 136 | self.program_counter_reg + 2 137 | } 138 | Instruction::Add(reg1, reg2) => { 139 | let first = self.read_reg(reg1) as u16; 140 | let second = self.read_reg(reg2) as u16; 141 | let answer = first + second; 142 | self.load_reg(0xF, (answer > 255) as u8); 143 | self.load_reg(reg1, answer as u8); 144 | self.program_counter_reg + 2 145 | } 146 | Instruction::Sub(reg1, reg2) => { 147 | let first = self.read_reg(reg1); 148 | let second = self.read_reg(reg2); 149 | self.load_reg(0xF, (first > second) as u8); 150 | self.load_reg(reg1, first.wrapping_sub(second)); 151 | self.program_counter_reg + 2 152 | } 153 | Instruction::ShiftRight(reg) => { 154 | let value = self.read_reg(reg); 155 | self.load_reg(0xF, value & 0b1); 156 | self.load_reg(reg, value >> 1); 157 | self.program_counter_reg + 2 158 | } 159 | Instruction::ReverseSub(_, _) => { 160 | panic!("Not yet implemeneted: {:?}", instruction); 161 | } 162 | Instruction::ShiftLeft(reg) => { 163 | let value = self.read_reg(reg); 164 | self.load_reg(0xF, value >> 7); 165 | self.load_reg(reg, value << 1); 166 | self.program_counter_reg + 2 167 | } 168 | Instruction::SkipIfNotEqual(reg1, reg2) => { 169 | let first = self.read_reg(reg1); 170 | let second = self.read_reg(reg2); 171 | if first != second { 172 | self.program_counter_reg + 4 173 | } else { 174 | self.program_counter_reg + 2 175 | } 176 | } 177 | Instruction::LoadI(value) => { 178 | self.i_reg = value; 179 | self.program_counter_reg + 2 180 | } 181 | Instruction::JumpPlusZero(_) => { 182 | panic!("Not yet implemeneted: {:?}", instruction); 183 | } 184 | Instruction::Random(reg, value) => { 185 | let rng = &mut rand::thread_rng(); 186 | let rand_number = Range::new(0, 255).ind_sample(rng); 187 | 188 | self.load_reg(reg, rand_number & value); 189 | self.program_counter_reg + 2 190 | } 191 | Instruction::Draw(reg1, reg2, n) => { 192 | let x = self.read_reg(reg1); 193 | let y = self.read_reg(reg2); 194 | let from = self.i_reg as usize; 195 | let to = from + (n as usize); 196 | 197 | self.regs[0xF] = self.display.draw(x, y, &self.memory[from..to]) as u8; 198 | self.program_counter_reg + 2 199 | } 200 | Instruction::SkipIfPressed(reg) => { 201 | let value = self.read_reg(reg); 202 | let pressed = self.keyboard[value as usize]; 203 | if pressed { 204 | self.program_counter_reg + 4 205 | } else { 206 | self.program_counter_reg + 2 207 | } 208 | } 209 | Instruction::SkipIfNotPressed(reg) => { 210 | let value = self.read_reg(reg); 211 | let pressed = self.keyboard[value as usize]; 212 | if !pressed { 213 | self.program_counter_reg + 4 214 | } else { 215 | self.program_counter_reg + 2 216 | } 217 | } 218 | Instruction::LoadDelayTimer(reg) => { 219 | let delay_value = self.delay_timer_reg; 220 | self.load_reg(reg, delay_value); 221 | self.program_counter_reg + 2 222 | } 223 | Instruction::WaitForKeyPress(reg) => { 224 | // TODO rename key_to_wait_for 225 | self.key_to_wait_for = Some(reg); 226 | self.program_counter_reg + 2 227 | } 228 | Instruction::SetDelayTimer(reg) => { 229 | let value = self.read_reg(reg); 230 | self.delay_timer_reg = value; 231 | self.program_counter_reg + 2 232 | } 233 | Instruction::SetSoundTimer(_) => { 234 | // TODO: set sound timer 235 | self.program_counter_reg + 2 236 | } 237 | Instruction::AddToI(reg) => { 238 | let value = self.read_reg(reg) as u16; 239 | self.i_reg = self.i_reg + value; 240 | self.program_counter_reg + 2 241 | } 242 | Instruction::LoadSprite(reg) => { 243 | let digit = self.read_reg(reg); 244 | self.i_reg = (digit * 5) as u16; 245 | self.program_counter_reg + 2 246 | } 247 | Instruction::BCDRepresentation(reg) => { 248 | let value = self.read_reg(reg); 249 | self.memory[self.i_reg as usize] = (value / 100) % 10; 250 | self.memory[(self.i_reg + 1) as usize] = (value / 10) % 10; 251 | self.memory[(self.i_reg + 2) as usize] = value % 10; 252 | self.program_counter_reg + 2 253 | } 254 | Instruction::StoreRegisters(highest_reg) => { 255 | let i = self.i_reg; 256 | for reg_number in 0..(highest_reg + 1) { 257 | self.memory[(i + reg_number as u16) as usize] = self.read_reg(reg_number); 258 | } 259 | self.program_counter_reg + 2 260 | } 261 | Instruction::LoadRegisters(highest_reg) => { 262 | let i = self.i_reg; 263 | for reg_number in 0..(highest_reg + 1) { 264 | let value = self.memory[(i + reg_number as u16) as usize]; 265 | self.load_reg(reg_number, value); 266 | } 267 | self.program_counter_reg + 2 268 | } 269 | } 270 | } 271 | 272 | pub fn handle_key_press(&mut self, key: u8) { 273 | self.keyboard[key as usize] = true; 274 | if let Some(reg) = self.key_to_wait_for { 275 | self.load_reg(reg, key); 276 | self.key_to_wait_for = None; 277 | } 278 | } 279 | 280 | pub fn handle_key_release(&mut self, key: u8) { 281 | self.keyboard[key as usize] = false; 282 | } 283 | 284 | fn instruction(&self) -> Instruction { 285 | let pc = self.program_counter_reg; 286 | let higher_order = (self.memory[pc as usize] as u16) << 8; 287 | let lower_order = self.memory[(pc + 1) as usize] as u16; 288 | RawInstruction::new(higher_order + lower_order) 289 | .to_instruction() 290 | .expect("Unrecognized instruction") 291 | } 292 | 293 | fn read_reg(&self, reg_number: u8) -> u8 { 294 | self.regs[(reg_number as usize)] 295 | } 296 | 297 | fn load_reg(&mut self, reg_number: u8, value: u8) { 298 | self.regs[(reg_number as usize)] = value; 299 | } 300 | } 301 | 302 | impl<'a> fmt::Debug for Chip8 { 303 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 304 | write!(f, 305 | "CPU {{ regs: {:?}, i_reg: {}, program_counter_reg: {} }}", 306 | self.regs, 307 | self.i_reg, 308 | self.program_counter_reg) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | pub const WIDTH: usize = 64; 2 | pub const HEIGHT: usize = 32; 3 | pub const SPRITES: [u8; 80] = [0xF0, 0x90, 0x90, 0x90, 0xF0 /* 0 */, 0x20, 0x60, 0x20, 0x20, 4 | 0x70 /* 1 */, 0xF0, 0x10, 0xF0, 0x80, 0xF0 /* 2 */, 0xF0, 5 | 0x10, 0xF0, 0x10, 0xF0 /* 3 */, 0x90, 0x90, 0xF0, 0x10, 6 | 0x10 /* 4 */, 0xF0, 0x80, 0xF0, 0x10, 0xF0 /* 5 */, 0xF0, 7 | 0x80, 0xF0, 0x90, 0xF0 /* 6 */, 0xF0, 0x10, 0x20, 0x40, 8 | 0x40 /* 7 */, 0xF0, 0x90, 0xF0, 0x90, 0xF0 /* 8 */, 0xF0, 9 | 0x90, 0xF0, 0x10, 0xF0 /* 9 */, 0xF0, 0x90, 0xF0, 0x90, 10 | 0x90 /* a */, 0xE0, 0x90, 0xE0, 0x90, 0xE0 /* b */, 0xF0, 11 | 0x80, 0x80, 0x80, 0xF0 /* c */, 0xE0, 0x90, 0x90, 0x90, 12 | 0xE0 /* d */, 0xF0, 0x80, 0xF0, 0x80, 0xF0 /* e */, 0xF0, 13 | 0x80, 0xF0, 0x80, 0x80];// f 14 | 15 | pub type Buffer = [[bool; WIDTH]; HEIGHT]; 16 | 17 | pub struct Display { 18 | buffer: Buffer, 19 | } 20 | 21 | impl Display { 22 | pub fn new() -> Display { 23 | Display { buffer: [[false; WIDTH]; HEIGHT] } 24 | } 25 | 26 | pub fn draw(&mut self, starting_x: u8, starting_y: u8, memory: &[u8]) -> bool { 27 | let mut pixel_turned_off = false; 28 | 29 | for (byte_number, block) in memory.iter().enumerate() { 30 | let y = (starting_y as usize + byte_number) % HEIGHT; 31 | 32 | for bit_number in 0..8 { 33 | let x = (starting_x as usize + bit_number) % WIDTH; 34 | let current_pixel = self.buffer[y][x] as u8; 35 | 36 | let current_bit = (block >> (7 - bit_number)) & 1; 37 | let new_pixel = current_bit ^ current_pixel; 38 | 39 | self.buffer[y][x] = new_pixel != 0; 40 | 41 | if current_pixel == 1 && new_pixel == 0 { 42 | pixel_turned_off = true; 43 | } 44 | } 45 | } 46 | pixel_turned_off 47 | } 48 | 49 | pub fn get_buffer(&self) -> Buffer { 50 | self.buffer 51 | } 52 | 53 | pub fn clear(&mut self) { 54 | self.buffer = [[false; WIDTH]; HEIGHT]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/instruction.rs: -------------------------------------------------------------------------------- 1 | pub type Address = u16; 2 | pub type Register = u8; 3 | 4 | #[derive(Debug)] 5 | pub enum Instruction { 6 | ClearDisplay, 7 | Return, 8 | Jump(Address), 9 | Call(Address), 10 | SkipIfEqualsByte(Register, u8), 11 | SkipIfNotEqualsByte(Register, u8), 12 | SkipIfEqual(Register, Register), 13 | LoadByte(Register, u8), 14 | AddByte(Register, u8), 15 | Move(Register, Register), 16 | Or(Register, Register), 17 | And(Register, Register), 18 | Xor(Register, Register), 19 | Add(Register, Register), 20 | Sub(Register, Register), 21 | ShiftRight(Register), 22 | ReverseSub(Register, Register), 23 | ShiftLeft(Register), 24 | SkipIfNotEqual(Register, Register), 25 | LoadI(u16), 26 | JumpPlusZero(Address), 27 | Random(Register, u8), 28 | Draw(Register, Register, u8), 29 | SkipIfPressed(Register), 30 | SkipIfNotPressed(Register), 31 | LoadDelayTimer(Register), 32 | WaitForKeyPress(Register), 33 | SetDelayTimer(Register), 34 | SetSoundTimer(Register), 35 | AddToI(Register), 36 | LoadSprite(Register), 37 | BCDRepresentation(Register), 38 | StoreRegisters(Register), 39 | LoadRegisters(Register), 40 | } 41 | 42 | pub struct RawInstruction { 43 | value: u16, 44 | } 45 | 46 | impl RawInstruction { 47 | pub fn new(value: u16) -> RawInstruction { 48 | RawInstruction { value: value } 49 | } 50 | 51 | pub fn to_instruction(&self) -> Option { 52 | match self.xooo() { 53 | 0x0 => { 54 | match self.ooxx() { 55 | 0xE0 => Some(Instruction::ClearDisplay), 56 | 0xEE => Some(Instruction::Return), 57 | _ => None, 58 | } 59 | } 60 | 0x1 => Some(Instruction::Jump(self.oxxx())), 61 | 0x2 => Some(Instruction::Call(self.oxxx())), 62 | 0x3 => Some(Instruction::SkipIfEqualsByte(self.oxoo(), self.ooxx())), 63 | 0x4 => Some(Instruction::SkipIfNotEqualsByte(self.oxoo(), self.ooxx())), 64 | 0x5 => Some(Instruction::SkipIfEqual(self.oxoo(), self.ooxo())), 65 | 0x6 => Some(Instruction::LoadByte(self.oxoo(), self.ooxx())), 66 | 0x7 => Some(Instruction::AddByte(self.oxoo(), self.ooxx())), 67 | 0x8 => { 68 | match self.ooox() { 69 | 0x0 => Some(Instruction::Move(self.oxoo(), self.ooxo())), 70 | 0x1 => Some(Instruction::Or(self.oxoo(), self.ooxo())), 71 | 0x2 => Some(Instruction::And(self.oxoo(), self.ooxo())), 72 | 0x3 => Some(Instruction::Xor(self.oxoo(), self.ooxo())), 73 | 0x4 => Some(Instruction::Add(self.oxoo(), self.ooxo())), 74 | 0x5 => Some(Instruction::Sub(self.oxoo(), self.ooxo())), 75 | 0x6 => Some(Instruction::ShiftRight(self.oxoo())), 76 | 0x7 => Some(Instruction::ReverseSub(self.oxoo(), self.ooxo())), 77 | 0xE => Some(Instruction::ShiftLeft(self.oxoo())), 78 | _ => None, 79 | } 80 | } 81 | 0x9 => Some(Instruction::SkipIfNotEqual(self.oxoo(), self.ooxo())), 82 | 0xA => Some(Instruction::LoadI(self.oxxx())), 83 | 0xB => Some(Instruction::JumpPlusZero(self.oxxx())), 84 | 0xC => Some(Instruction::Random(self.oxoo(), self.ooxx())), 85 | 0xD => Some(Instruction::Draw(self.oxoo(), self.ooxo(), self.ooox())), 86 | 0xE => { 87 | match self.ooxx() { 88 | 0x9E => Some(Instruction::SkipIfPressed(self.oxoo())), 89 | 0xA1 => Some(Instruction::SkipIfNotPressed(self.oxoo())), 90 | _ => None, 91 | } 92 | } 93 | 0xF => { 94 | match self.ooxx() { 95 | 0x07 => Some(Instruction::LoadDelayTimer(self.oxoo())), 96 | 0x0A => Some(Instruction::WaitForKeyPress(self.oxoo())), 97 | 0x15 => Some(Instruction::SetDelayTimer(self.oxoo())), 98 | 0x18 => Some(Instruction::SetSoundTimer(self.oxoo())), 99 | 0x1E => Some(Instruction::AddToI(self.oxoo())), 100 | 0x29 => Some(Instruction::LoadSprite(self.oxoo())), 101 | 0x33 => Some(Instruction::BCDRepresentation(self.oxoo())), 102 | 0x55 => Some(Instruction::StoreRegisters(self.oxoo())), 103 | 0x65 => Some(Instruction::LoadRegisters(self.oxoo())), 104 | _ => None, 105 | } 106 | } 107 | _ => None, 108 | } 109 | } 110 | 111 | #[inline(always)] 112 | fn xooo(&self) -> u8 { 113 | ((self.value >> 12) & 0xF) as u8 114 | } 115 | 116 | #[inline(always)] 117 | fn oxoo(&self) -> u8 { 118 | ((self.value >> 8) & 0xF) as u8 119 | } 120 | 121 | #[inline(always)] 122 | fn ooxo(&self) -> u8 { 123 | ((self.value >> 4) & 0xF) as u8 124 | } 125 | 126 | #[inline(always)] 127 | fn ooox(&self) -> u8 { 128 | (self.value as u8) & 0xF 129 | } 130 | 131 | #[inline(always)] 132 | fn ooxx(&self) -> u8 { 133 | (self.value & 0xFF) as u8 134 | } 135 | 136 | #[inline(always)] 137 | fn oxxx(&self) -> u16 { 138 | self.value & 0xFFF 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate piston_window; 2 | extern crate rand; 3 | 4 | mod display; 5 | mod instruction; 6 | mod chip8; 7 | use std::env; 8 | use std::fs::File; 9 | use std::io::Read; 10 | 11 | use piston_window::*; 12 | 13 | const ENLARGEMENT_FACTOR: usize = 20; 14 | const WINDOW_DIMENSIONS: [u32; 2] = [(display::WIDTH * ENLARGEMENT_FACTOR) as u32, 15 | (display::HEIGHT * ENLARGEMENT_FACTOR) as u32]; 16 | 17 | fn main() { 18 | let file_name = env::args().nth(1).expect("Must give game name as first file"); 19 | let mut file = File::open(file_name).expect("There was an issue opening the file"); 20 | let mut game_data = Vec::new(); 21 | file.read_to_end(&mut game_data).expect("Failure to read file"); 22 | 23 | let window: PistonWindow = WindowSettings::new("Rust-8 Emulator", WINDOW_DIMENSIONS) 24 | .exit_on_esc(true) 25 | .build() 26 | .unwrap(); 27 | let mut computer = chip8::Chip8::new(game_data); 28 | 29 | for e in window { 30 | if let Some(_) = e.render_args() { 31 | draw_screen(&computer.display.get_buffer(), &e); 32 | } 33 | 34 | if let Some(u) = e.update_args() { 35 | computer.cycle(u.dt); 36 | } 37 | 38 | if let Some(Button::Keyboard(key)) = e.release_args() { 39 | if let Some(key_value) = key_value(&key) { 40 | computer.handle_key_release(key_value); 41 | } 42 | } 43 | 44 | if let Some(Button::Keyboard(key)) = e.press_args() { 45 | if let Some(key_value) = key_value(&key) { 46 | computer.handle_key_press(key_value); 47 | } 48 | } 49 | } 50 | } 51 | 52 | fn key_value(key: &Key) -> Option { 53 | if key.code() >= 48 && key.code() <= 57 { 54 | Some((key.code() - 48) as u8) 55 | } else if key.code() >= 97 && key.code() <= 102 { 56 | Some((key.code() - 97 + 10) as u8) 57 | } else { 58 | None 59 | } 60 | } 61 | 62 | fn draw_screen(display_buffer: &display::Buffer, window: &PistonWindow) { 63 | window.draw_2d(|context, graphics| { 64 | piston_window::clear(color::BLACK, graphics); 65 | 66 | for (i, row) in display_buffer.iter().enumerate() { 67 | for (j, val) in row.iter().enumerate() { 68 | if *val { 69 | let dimensions = [(j * ENLARGEMENT_FACTOR) as f64, 70 | (i * ENLARGEMENT_FACTOR) as f64, 71 | ENLARGEMENT_FACTOR as f64, 72 | ENLARGEMENT_FACTOR as f64]; 73 | Rectangle::new(color::WHITE) 74 | .draw(dimensions, &context.draw_state, context.transform, graphics); 75 | } 76 | } 77 | } 78 | }) 79 | } 80 | 81 | #[allow(dead_code)] 82 | fn debug(display_buffer: &display::Buffer) { 83 | for row in display_buffer.iter() { 84 | print!("|"); 85 | for val in row.iter() { 86 | if *val { 87 | print!("*") 88 | } else { 89 | print!(".") 90 | } 91 | } 92 | println!("|") 93 | } 94 | } 95 | --------------------------------------------------------------------------------