diff --git a/Cargo.lock b/Cargo.lock index cb5347f..e0413e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.8.0" @@ -875,7 +881,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags", + "bitflags 2.8.0", ] [[package]] @@ -911,11 +917,13 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" name = "rom-emulator" version = "0.1.0" dependencies = [ - "bitflags", + "bitflags 2.8.0", "env_logger", "futures", "lazy_static", "log", + "rand", + "sdl2", "serde", "serde_json", "tokio", @@ -949,6 +957,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdl2" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b498da7d14d1ad6c839729bd4ad6fc11d90a57583605f3b4df2cd709a9cd380" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951deab27af08ed9c6068b7b0d05a93c91f0a8eb16b6b816a5e73452a43521d3" +dependencies = [ + "cfg-if", + "libc", + "version-compare", +] + [[package]] name = "serde" version = "1.0.217" @@ -1327,6 +1358,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 95ecda5..7a95cb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ env_logger = "0.11.6" futures = "0.3.31" lazy_static = "1.5.0" log = "0.4.25" +rand = "0.8.5" +sdl2 = "0.37.0" serde = "1.0.217" serde_json = "1.0.135" tokio = { version = "1.43.0", features = ["full"] } diff --git a/cpu.rs b/cpu.rs index f5df986..5c9f24d 100644 --- a/cpu.rs +++ b/cpu.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; // This is all new to me so it's heavily commented so we can understand what is happening. -use bitflags::{bitflags, Flags}; +use bitflags::bitflags; use crate::opcodes; @@ -91,6 +91,9 @@ pub struct CPU { // X register has one special function. It can be used to copy a stack pointer or change it's // value. pub register_x: u8, + // used to assist in memory addressing and certain operations. Most commonly used as an offset + // for addressing memory. Can also be used as temporary storage space or to manipulate data for + // computations pub register_y: u8, // this creates an array with size of 0xFFFF and initializes all elements to 0 (see below in t // he new function). 0xFFFF is hexadecimal for 65535 in decimal. This defines the size of the @@ -99,7 +102,7 @@ pub struct CPU { pub stack_pointer: u8, } -trait Mem { +pub trait Mem { fn mem_read(&self, addr: u16) -> u8; fn mem_write(&mut self, addr: u16, data: u8); @@ -259,7 +262,7 @@ impl CPU { /// Road the program, reset the state of the cpu and then run the program. pub fn load_and_run(&mut self, program: Vec) { self.load(program); - self.reset(); + self.program_counter = self.mem_read_u16(0xFFFC); self.run(); } @@ -275,17 +278,25 @@ impl CPU { /// Load the program code into memroy starting at address 0x8000. 0x8000 .. 0xFFFF is reserved /// from program ROM and we can assume that the instructions should start somewhere here. pub fn load(&mut self, program: Vec) { - self.memory[0x8000..(0x8000 + program.len())].copy_from_slice(&program[..]); - self.mem_write_u16(0xFFFC, 0x8000); + self.memory[0x0600..(0x0600 + program.len())].copy_from_slice(&program[..]); + self.mem_write_u16(0xFFFC, 0x0600); } + /// Load a value from memory into the Y register fn ldy(&mut self, mode: &AddressingMode) { + // compute the memory address based on the given addressing mode let addr = self.get_operand_address(mode); + // fetch the data from memory let data = self.mem_read(addr); + // store data in register y self.register_y = data; + // Update the CpuFlags based on the new value of register y self.update_zero_and_negative_flags(self.register_y); } + /// LDA stands for Load X Register + /// Load a value from memory into the x register and updates the CPU status flags based on the + /// new value. fn ldx(&mut self, mode: &AddressingMode) { let addr = self.get_operand_address(mode); let data = self.mem_read(addr); @@ -299,8 +310,7 @@ impl CPU { let addr = self.get_operand_address(&mode); let value = self.mem_read(addr); - self.register_a = value; - self.update_zero_and_negative_flags(self.register_a); + self.set_register_a(value); } /// TAX instruction copies the value from teh accumulator register (register_a) into the x @@ -339,12 +349,18 @@ impl CPU { self.set_register_a(data & self.register_a); } + /// EOR stands for Exclusive OR. + /// Performs a bitwise exclosive OR operation between a vlaue from memory and the Accumulator + /// register (register_a), then stores the value back in the accumulator fn eor(&mut self, mode: &AddressingMode) { let addr = self.get_operand_address(mode); let data = self.mem_read(addr); self.set_register_a(data ^ self.register_a); } + /// ORA stands for OR Accumulator. + /// Perform a bitwise inclusive OR operation between a vlaue fetched from emmory and the + /// Accumulator (register_a), then stores the value back in the Accumulator fn ora(&mut self, mode: &AddressingMode) { let addr = self.get_operand_address(mode); let data = self.mem_read(addr); @@ -363,13 +379,25 @@ impl CPU { self.status.remove(CpuFlags::ZERO); } - if result & 0b1000_000 != 0 { + if result >> 7 == 1 { self.status.insert(CpuFlags::NEGATIVE) } else { self.status.remove(CpuFlags::NEGATIVE); } } + /// Update the Negative Flag in the status register based on the most significant bit of the + /// result + fn update_negative_flags(&mut self, result: u8) { + // shifting right by 7 places moves the 7th bit to the least significant position (thus + // isolating it) + if result >> 7 == 1 { + self.status.insert(CpuFlags::NEGATIVE); + } else { + self.status.remove(CpuFlags::NEGATIVE); + } + } + fn set_carry_flag(&mut self) { self.status.insert(CpuFlags::CARRY); } @@ -445,6 +473,8 @@ impl CPU { } /// ADC stands for Add with Carry + /// Perform addition between the Accumulator and a value fetched from memory, taking into + /// account the carry flag fn adc(&mut self, mode: &AddressingMode) { let addr = self.get_operand_address(mode); let value = self.mem_read(addr); @@ -475,9 +505,15 @@ impl CPU { hi << 8 | lo } + /// ASL stands for Arithmetic Shift Left + /// Shift all bits in the Accumulator one position to the left, which effectively multiplies + /// the value by two. It also updates the carry flag and handles the overflow of the leftmost + /// bit. fn asl_accumulator(&mut self) { let mut data = self.register_a; + // shift all the bits of data to the right by 6 positions if data >> 6 == 1 { + // set the carry flag to indicate overflow self.set_carry_flag(); } else { self.clear_carry_flag(); @@ -512,18 +548,6 @@ impl CPU { data } - fn lsr_accumulator(&mut self) { - let mut data = self.register_a; - if data & 1 == 1 { - self.set_carry_flag(); - } else { - self.clear_carry_flag(); - } - - data = data >> 1; - self.set_register_a(data); - } - fn lsr(&mut self, mode: &AddressingMode) -> u8 { let addr = self.get_operand_address(mode); let mut data = self.mem_read(addr); @@ -542,7 +566,7 @@ impl CPU { fn rol(&mut self, mode: &AddressingMode) -> u8 { let addr = self.get_operand_address(mode); let mut data = self.mem_read(addr); - let old_carry = self.status.contains(CpuFlags::CARRY); + let _old_carry = self.status.contains(CpuFlags::CARRY); if data >> 7 == 1 { self.set_carry_flag(); @@ -551,11 +575,11 @@ impl CPU { } data = data << 1; - if old_carry { + if _old_carry { data = data | 1; } self.mem_write(addr, data); - self.update_zero_and_negative_flags(data); + self.update_negative_flags(data); data } @@ -579,8 +603,7 @@ impl CPU { fn ror(&mut self, mode: &AddressingMode) -> u8 { let addr = self.get_operand_address(mode); - let mut data = self.mem_read(addr); - let old_carry = self.status.contains(CpuFlags::CARRY); + let data = self.mem_read(addr); if data & 1 == 1 { self.set_carry_flag(); @@ -589,7 +612,7 @@ impl CPU { } self.mem_write(addr, data); - self.update_zero_and_negative_flags(data); + self.update_negative_flags(data); data } @@ -693,7 +716,7 @@ impl CPU { .wrapping_add(1) .wrapping_add(jump as u16); - self.program_counter = jump_addr + self.program_counter = jump_addr; } } @@ -708,7 +731,7 @@ impl CPU { self.register_y = self.register_y.wrapping_add(1); self.update_zero_and_negative_flags(self.register_y); } - + pub fn run(&mut self) { self.run_with_callback(|_| {}); } @@ -720,7 +743,7 @@ impl CPU { // - Decode instruction // - Execute the instruction // - Repeat - pub fn run_with_callback(&mut self, mut callback: F) + pub fn run_with_callback(&mut self, mut callback: F) where F: FnMut(&mut CPU), { @@ -729,15 +752,25 @@ impl CPU { loop { let code = self.mem_read(self.program_counter); self.program_counter += 1; + let program_counter_state = self.program_counter; + let opcode = opcodes.get(&code).unwrap(); match code { - 0xA9 | 0xa5 | 0xb4 | 0xad | 0xbd | 0xb9 | 0xa1 | 0xb1 => { - self.lda(&AddressingMode::Immediate); - self.program_counter += 1; + 0xA9 | 0xA5 | 0xB5 | 0xAD | 0xBD | 0xB9 | 0xA1 | 0xB1 => { + self.lda(&opcode.mode); + } + 0xA0 | 0xA4 | 0xB4 | 0xAC | 0xBC => { + self.ldy(&opcode.mode); + } + 0xA2 | 0xA6 | 0xB6 | 0xAE | 0xBE => { + self.ldx(&opcode.mode); + } + 0xEA => { + // do nothing } 0xAA => self.tax(), - 0xe8 => self.inx(), + 0xE8 => self.inx(), 0x00 => return, 0xd8 => self.status.remove(CpuFlags::DECIMAL_MODE), 0x58 => self.status.remove(CpuFlags::INTERRUPT_DISABLE), @@ -757,10 +790,10 @@ impl CPU { self.plp(); } 0x69 | 0x65 | 0x75 | 0x6d | 0x7d | 0x79 | 0x61 | 0x71 => { - self.adc(&opcode.mod); + self.adc(&opcode.mode); } 0xe9 | 0xe5 | 0xf5 | 0xed | 0xfd | 0xf9 | 0xe1 | 0xf1 => { - self.adc(&opcode.mode); + self.sbc(&opcode.mode); } 0x29 | 0x25 | 0x35 | 0x2d | 0x3d | 0x39 | 0x21 | 0x31 => { self.and(&opcode.mode); @@ -773,7 +806,7 @@ impl CPU { } 0x4a => self.lsr_accumulator(), 0x46 | 0x56 | 0x4e | 0x5e => { - self.lsr(&opcode.mode) + self.lsr(&opcode.mode); } 0x0a => self.asl_accumulator(), 0x06 | 0x16 | 0x0e | 0x1e => { @@ -787,7 +820,7 @@ impl CPU { 0x66 | 0x76 | 0x6e | 0x7e => { self.ror(&opcode.mode); } - 0xe6 | 0xf6 | 0xee | 0xfe => { + 0xe6 | 0xf6 | 0xee | 0xfe => { self.inc(&opcode.mode); } 0xc8 => self.iny(), @@ -796,14 +829,16 @@ impl CPU { self.dec(&opcode.mode); } 0xca => self.dex(), - 0x88 => self.dey, + 0x88 => { + self.dey(); + } 0xc9 | 0xc5 | 0xd5 | 0xcd | 0xdd | 0xd9 | 0xc1 | 0xd1 => { self.compare(&opcode.mode, self.register_a); } 0xc0 | 0xc4 | 0xcc => { self.compare(&opcode.mode, self.register_y); } - 0xe0 | 0xe4 | 0xec => self.compare(&opcode, self.register_x), + 0xe0 | 0xe4 | 0xec => self.compare(&opcode.mode, self.register_x), 0x4c => { let mem_address = self.mem_read_u16(self.program_counter); self.program_counter = mem_address; @@ -813,8 +848,9 @@ impl CPU { let indirect_ref = if mem_address & 0x00FF == 0x00FF { let lo = self.mem_read(mem_address); let hi = self.mem_read(mem_address & 0xFF00); + (hi as u16) << 8 | lo as u16 } else { - self.mem_read_u16(mem_address) + self.mem_read_u16(mem_address) }; self.program_counter = indirect_ref; @@ -828,37 +864,136 @@ impl CPU { self.program_counter = self.stack_pop_u16() + 1; } 0x40 => { - self.status.bits = self.stack_pop(); + let raw_bits = self.stack_pop(); + self.status = CpuFlags::from_bits_truncate(raw_bits); self.status.remove(CpuFlags::BREAK); self.status.insert(CpuFlags::BREAK2); self.program_counter = self.stack_pop_u16(); } - // come back here - 0xA5 => { - self.lda(&AddressingMode::ZeroPage); - self.program_counter += 1; + 0xd0 => { + self.branch(!self.status.contains(CpuFlags::ZERO)); } - 0xAD => { - self.lda(&AddressingMode::Absolute); - self.program_counter += 2; + 0x70 => { + self.branch(self.status.contains(CpuFlags::OVERFLOW)); } - 0x85 => { - self.sta(&AddressingMode::ZeroPage); - self.program_counter += 1; + 0x50 => { + self.branch(!self.status.contains(CpuFlags::OVERFLOW)); } - 0x95 => { - self.sta(&AddressingMode::ZeroPage); - self.program_counter += 1; + 0x10 => { + self.branch(!self.status.contains(CpuFlags::NEGATIVE)); } - // implement the 0xAA opcode which corresponds to the TAX (transfer acculumater to - // X register) - 0xAA => self.tax(), - // INX stands for Increment Index Register X - increase the value of the X register - // by one and update specific processor flags based on the result. - 0xe8 => self.inx(), - _ => todo!(), + 0x30 => { + self.branch(self.status.contains(CpuFlags::NEGATIVE)); + } + 0xf0 => { + self.branch(self.status.contains(CpuFlags::ZERO)); + } + 0xb0 => { + self.branch(self.status.contains(CpuFlags::CARRY)); + } + 0x90 => { + self.branch(!self.status.contains(CpuFlags::CARRY)); + } + 0x24 | 0x2c => { + self.bit(&opcode.mode); + } + 0x85 | 0x95 | 0x8d | 0x9d | 0x99 | 0x81 | 0x91 => { + self.sta(&opcode.mode); + } + 0x86 | 0x96 | 0x8e => { + let addr = self.get_operand_address(&opcode.mode); + self.mem_write(addr, self.register_x); + } + 0x84 | 0x94 | 0x8c => { + let addr = self.get_operand_address(&opcode.mode); + self.mem_write(addr, self.register_y); + } + 0xa8 => { + self.register_y = self.register_a; + self.update_zero_and_negative_flags(self.register_y); + } + 0xba => { + self.register_x = self.stack_pointer; + self.update_zero_and_negative_flags(self.register_x); + } + 0x8a => { + self.register_a = self.register_x; + self.update_zero_and_negative_flags(self.register_a); + } + 0x9a => { + self.stack_pointer = self.register_x; + } + 0x98 => { + self.register_a = self.register_y; + self.update_zero_and_negative_flags(self.register_a); + } + + _ => { + eprintln!("Unimplemented opcode: {:#X}", code); + } + } + + if program_counter_state == self.program_counter { + self.program_counter += (opcode.len - 1) as u16; } + + callback(self) } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_0xa9_lda_immediate_load_data() { + let mut cpu = CPU::new(); + + cpu.load_and_run(vec![0xa9, 0x05, 0x00]); + + assert_eq!(cpu.register_a, 5); + assert!(cpu.status.bits() & 0b0000_0010 == 0b00); + assert!(cpu.status.bits() & 0b1000_0000 == 0); + } + + #[test] + fn test_0xaa_tax_move_a_to_x() { + let mut cpu = CPU::new(); + cpu.register_a = 10; + + cpu.load_and_run(vec![0xaa, 0x00]); + + assert_eq!(cpu.register_x, 10) + } + + #[test] + fn test_5_ops_working_together() { + let mut cpu = CPU::new(); + + cpu.load_and_run(vec![0xa9, 0xc0, 0xaa, 0xe8, 0x00]); + + assert_eq!(cpu.register_x, 0xc1) + } + + #[test] + fn test_inx_overflow() { + let mut cpu = CPU::new(); + cpu.register_x = 0xff; + + cpu.load_and_run(vec![0xe8, 0xe8, 0x00]); + + assert_eq!(cpu.register_x, 1) + } + + #[test] + fn test_lda_from_memory() { + let mut cpu = CPU::new(); + cpu.mem_write(0x10, 0x55); + + cpu.load_and_run(vec![0xa5, 0x10, 0x00]); + + assert_eq!(cpu.register_a, 0x55); + } +} diff --git a/main.rs b/main.rs index 2beb823..b7fcaef 100644 --- a/main.rs +++ b/main.rs @@ -1,90 +1,157 @@ -use futures::{SinkExt, StreamExt}; -use log::{error, info}; -use std::env; -use std::net::SocketAddr; -use tokio::net::{TcpListener, TcpStream}; -use tokio_tungstenite::{accept_async, tungstenite::protocol::Message}; -mod cpu; -mod opcodes; -// this file is overly commented because I'm learning Rust while I program. I want just want to -// thnk aloud. -#[tokio::main] -async fn main() { - // initialize logger - env_logger::init(); - - // retrieve the iterator from command line args passed in, extract second item, if nth returns - // a value, extra it or else if it returns nothing, use the given ip address - let addr = env::args() - .nth(1) - .unwrap_or_else(|| "127.0.0.1:8080".to_string()); - - // SocketAddr is used to represent an IP address and port combo. .parse attempts to convert the - // string to a SocketAddr. If successful, returns a Result. expect() will - // terminate the program and print a message if parse returns an error. If parse is successful, - // expect() will unwrap the SockAddr from the Result and assign it to addr. - let addr: SocketAddr = addr.parse().expect("Invalid Address"); - - // attempt to bind the tcplistner to an address. await indicates that this is async. - // TCPListener::bind will not block the current thread but instead yield control until the - // binding operation completes. If bind fails, it will return an Err. - // - // The await keyword here tells the runtime that the function will wait for TCPListener::bind - // to complete and will not block the thread. Instead, the runtime can schedule other tasks to - // run on the same thread while original operation waits for it's completion. - let listener = TcpListener::bind(&addr).await.expect("Failed to bind"); - - // info! is a macro that logs a message at the info level. - info!("Listening on: {}", addr); - - // This is a pattern matching loop. It will continuausly process connections as long as the - // await returns Ok. If an error occurs, the loop will stop. - // - // The accept method waits asynchronously for an incoming tCP connection. - // When a client connects, it returns a Result containing TcpStream and SocketAddr. TcpStream - // is a handle for the client connection, allow for read and writes. - while let Ok((stream, _)) = listener.accept().await { - // tokio::spawn will spawn a new async task to handle the connection. This allows for - // multiple clients to connect and to be processed concurrently. Each task runs - // independently and does not block server loop. - tokio::spawn(handle_connection(stream)); +pub mod cpu; +pub mod opcodes; +use cpu::Mem; +use cpu::CPU; +use rand::Rng; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::pixels::PixelFormatEnum; +use sdl2::EventPump; +mod assembly; + +extern crate lazy_static; + +extern crate bitflags; + +/// Translate byte values to colors +fn color(byte: u8) -> Color { + match byte { + 0 => sdl2::pixels::Color::BLACK, + 1 => sdl2::pixels::Color::WHITE, + 2 | 9 => sdl2::pixels::Color::GREY, + 3 | 10 => sdl2::pixels::Color::RED, + 4 | 11 => sdl2::pixels::Color::GREEN, + 5 | 12 => sdl2::pixels::Color::BLUE, + 6 | 13 => sdl2::pixels::Color::MAGENTA, + 7 | 14 => sdl2::pixels::Color::YELLOW, + _ => sdl2::pixels::Color::CYAN, } } -async fn handle_connection(stream: TcpStream) { - // perform the handshake on the given TCP stream - // The match expression is used to handle the result of the accept_async function - // accept_async will return a Result that is either an Ok(ws) which will hand us the websocket - // connection or an error message. - let ws_stream = match accept_async(stream).await { - Ok(ws) => ws, - Err(e) => { - error!("Error during the websocket handshake: {}", e); - return; +/// Read a block of memory and update frame buffer with the corresponding RGB value +/// If returns true, then there were changes to the frame +fn read_screen_state(cpu: &CPU, frame: &mut [u8; 32 * 3 * 32]) -> bool { + let mut frame_idx = 0; + let mut update = false; + for i in 0x0200..0x600 { + let color_idx = cpu.mem_read(i as u16); + let (b1, b2, b3) = color(color_idx).rgb(); + if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 { + frame[frame_idx] = b1; + frame[frame_idx + 1] = b2; + frame[frame_idx + 2] = b3; + update = true; } - }; - - // split websocket stream into sender and receiver - let (mut sender, mut receiver) = ws_stream.split(); - - // handle incoming websocket messages and reverse the string that comes in and then send it - // back. Why? Because. - while let Some(msg) = receiver.next().await { - match msg { - Ok(Message::Text(text)) => { - let reversed = text.chars().rev().collect::(); - // .into converts the types when possible. The Message::Text variant expects String - // but reversed might not match that exact implementation. Libraries like - // tokio-tungstenite may require into. - if let Err(e) = sender.send(Message::Text(reversed.into())).await { - error!("Error sending message: {}", e); - } + frame_idx += 3; + } + update +} + +fn handle_user_input(cpu: &mut CPU, event_pump: &mut EventPump) { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => std::process::exit(0), + Event::KeyDown { + keycode: Some(Keycode::W), + .. + } => { + cpu.mem_write(0xff, 0x77); } - Ok(Message::Close(_)) => break, - Ok(_) => (), - Err(e) => { - error!("Error processing message: {}", e); + Event::KeyDown { + keycode: Some(Keycode::S), + .. + } => { + cpu.mem_write(0xff, 0x73); } + Event::KeyDown { + keycode: Some(Keycode::A), + .. + } => { + cpu.mem_write(0xff, 0x61); + } + Event::KeyDown { + keycode: Some(Keycode::D), + .. + } => { + cpu.mem_write(0xff, 0x64); + } + _ => { /* do nothing */ } } } } + +fn main() { + // init sdl2 + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem + .window("Snake game", (32.0 * 10.0) as u32, (32.0 * 10.0) as u32) + .position_centered() + .build() + .unwrap(); + + let mut canvas = window.into_canvas().present_vsync().build().unwrap(); + let mut event_pump = sdl_context.event_pump().unwrap(); + canvas.set_scale(20.0, 20.0).unwrap(); + + let creator = canvas.texture_creator(); + let mut texture = creator + .create_texture_target(PixelFormatEnum::RGB24, 32, 32) + .unwrap(); + + let game_code = vec![ + 0x20, 0x06, 0x06, 0x20, 0x38, 0x06, 0x20, 0x0d, 0x06, 0x20, 0x2a, 0x06, 0x60, 0xa9, 0x02, + 0x85, 0x02, 0xa9, 0x04, 0x85, 0x03, 0xa9, 0x11, 0x85, 0x10, 0xa9, 0x10, 0x85, 0x12, 0xa9, + 0x0f, 0x85, 0x14, 0xa9, 0x04, 0x85, 0x11, 0x85, 0x13, 0x85, 0x15, 0x60, 0xa5, 0xfe, 0x85, + 0x00, 0xa5, 0xfe, 0x29, 0x03, 0x18, 0x69, 0x02, 0x85, 0x01, 0x60, 0x20, 0x4d, 0x06, 0x20, + 0x8d, 0x06, 0x20, 0xc3, 0x06, 0x20, 0x19, 0x07, 0x20, 0x20, 0x07, 0x20, 0x2d, 0x07, 0x4c, + 0x38, 0x06, 0xa5, 0xff, 0xc9, 0x77, 0xf0, 0x0d, 0xc9, 0x64, 0xf0, 0x14, 0xc9, 0x73, 0xf0, + 0x1b, 0xc9, 0x61, 0xf0, 0x22, 0x60, 0xa9, 0x04, 0x24, 0x02, 0xd0, 0x26, 0xa9, 0x01, 0x85, + 0x02, 0x60, 0xa9, 0x08, 0x24, 0x02, 0xd0, 0x1b, 0xa9, 0x02, 0x85, 0x02, 0x60, 0xa9, 0x01, + 0x24, 0x02, 0xd0, 0x10, 0xa9, 0x04, 0x85, 0x02, 0x60, 0xa9, 0x02, 0x24, 0x02, 0xd0, 0x05, + 0xa9, 0x08, 0x85, 0x02, 0x60, 0x60, 0x20, 0x94, 0x06, 0x20, 0xa8, 0x06, 0x60, 0xa5, 0x00, + 0xc5, 0x10, 0xd0, 0x0d, 0xa5, 0x01, 0xc5, 0x11, 0xd0, 0x07, 0xe6, 0x03, 0xe6, 0x03, 0x20, + 0x2a, 0x06, 0x60, 0xa2, 0x02, 0xb5, 0x10, 0xc5, 0x10, 0xd0, 0x06, 0xb5, 0x11, 0xc5, 0x11, + 0xf0, 0x09, 0xe8, 0xe8, 0xe4, 0x03, 0xf0, 0x06, 0x4c, 0xaa, 0x06, 0x4c, 0x35, 0x07, 0x60, + 0xa6, 0x03, 0xca, 0x8a, 0xb5, 0x10, 0x95, 0x12, 0xca, 0x10, 0xf9, 0xa5, 0x02, 0x4a, 0xb0, + 0x09, 0x4a, 0xb0, 0x19, 0x4a, 0xb0, 0x1f, 0x4a, 0xb0, 0x2f, 0xa5, 0x10, 0x38, 0xe9, 0x20, + 0x85, 0x10, 0x90, 0x01, 0x60, 0xc6, 0x11, 0xa9, 0x01, 0xc5, 0x11, 0xf0, 0x28, 0x60, 0xe6, + 0x10, 0xa9, 0x1f, 0x24, 0x10, 0xf0, 0x1f, 0x60, 0xa5, 0x10, 0x18, 0x69, 0x20, 0x85, 0x10, + 0xb0, 0x01, 0x60, 0xe6, 0x11, 0xa9, 0x06, 0xc5, 0x11, 0xf0, 0x0c, 0x60, 0xc6, 0x10, 0xa5, + 0x10, 0x29, 0x1f, 0xc9, 0x1f, 0xf0, 0x01, 0x60, 0x4c, 0x35, 0x07, 0xa0, 0x00, 0xa5, 0xfe, + 0x91, 0x00, 0x60, 0xa6, 0x03, 0xa9, 0x00, 0x81, 0x10, 0xa2, 0x00, 0xa9, 0x01, 0x81, 0x10, + 0x60, 0xa6, 0xff, 0xea, 0xea, 0xca, 0xd0, 0xfb, 0x60, + ]; + + //load the game + let mut cpu = CPU::new(); + cpu.load(game_code); + cpu.reset(); + + println!("Program has started!"); + + let mut screen_state = [0 as u8; 32 * 3 * 32]; + let mut rng = rand::thread_rng(); + + // run the game cycle + cpu.run_with_callback(move |cpu| { + handle_user_input(cpu, &mut event_pump); + + cpu.mem_write(0xfe, rng.gen_range(1..16)); + + if read_screen_state(cpu, &mut screen_state) { + texture.update(None, &screen_state, 32 * 3).unwrap(); + + canvas.copy(&texture, None, None).unwrap(); + + canvas.present(); + } + + std::thread::sleep(std::time::Duration::new(0, 70_000)); + }); +} diff --git a/websocket.rs b/websocket.rs new file mode 100644 index 0000000..2beb823 --- /dev/null +++ b/websocket.rs @@ -0,0 +1,90 @@ +use futures::{SinkExt, StreamExt}; +use log::{error, info}; +use std::env; +use std::net::SocketAddr; +use tokio::net::{TcpListener, TcpStream}; +use tokio_tungstenite::{accept_async, tungstenite::protocol::Message}; +mod cpu; +mod opcodes; +// this file is overly commented because I'm learning Rust while I program. I want just want to +// thnk aloud. +#[tokio::main] +async fn main() { + // initialize logger + env_logger::init(); + + // retrieve the iterator from command line args passed in, extract second item, if nth returns + // a value, extra it or else if it returns nothing, use the given ip address + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "127.0.0.1:8080".to_string()); + + // SocketAddr is used to represent an IP address and port combo. .parse attempts to convert the + // string to a SocketAddr. If successful, returns a Result. expect() will + // terminate the program and print a message if parse returns an error. If parse is successful, + // expect() will unwrap the SockAddr from the Result and assign it to addr. + let addr: SocketAddr = addr.parse().expect("Invalid Address"); + + // attempt to bind the tcplistner to an address. await indicates that this is async. + // TCPListener::bind will not block the current thread but instead yield control until the + // binding operation completes. If bind fails, it will return an Err. + // + // The await keyword here tells the runtime that the function will wait for TCPListener::bind + // to complete and will not block the thread. Instead, the runtime can schedule other tasks to + // run on the same thread while original operation waits for it's completion. + let listener = TcpListener::bind(&addr).await.expect("Failed to bind"); + + // info! is a macro that logs a message at the info level. + info!("Listening on: {}", addr); + + // This is a pattern matching loop. It will continuausly process connections as long as the + // await returns Ok. If an error occurs, the loop will stop. + // + // The accept method waits asynchronously for an incoming tCP connection. + // When a client connects, it returns a Result containing TcpStream and SocketAddr. TcpStream + // is a handle for the client connection, allow for read and writes. + while let Ok((stream, _)) = listener.accept().await { + // tokio::spawn will spawn a new async task to handle the connection. This allows for + // multiple clients to connect and to be processed concurrently. Each task runs + // independently and does not block server loop. + tokio::spawn(handle_connection(stream)); + } +} + +async fn handle_connection(stream: TcpStream) { + // perform the handshake on the given TCP stream + // The match expression is used to handle the result of the accept_async function + // accept_async will return a Result that is either an Ok(ws) which will hand us the websocket + // connection or an error message. + let ws_stream = match accept_async(stream).await { + Ok(ws) => ws, + Err(e) => { + error!("Error during the websocket handshake: {}", e); + return; + } + }; + + // split websocket stream into sender and receiver + let (mut sender, mut receiver) = ws_stream.split(); + + // handle incoming websocket messages and reverse the string that comes in and then send it + // back. Why? Because. + while let Some(msg) = receiver.next().await { + match msg { + Ok(Message::Text(text)) => { + let reversed = text.chars().rev().collect::(); + // .into converts the types when possible. The Message::Text variant expects String + // but reversed might not match that exact implementation. Libraries like + // tokio-tungstenite may require into. + if let Err(e) = sender.send(Message::Text(reversed.into())).await { + error!("Error sending message: {}", e); + } + } + Ok(Message::Close(_)) => break, + Ok(_) => (), + Err(e) => { + error!("Error processing message: {}", e); + } + } + } +}