Browse Source

a bunch of stuff

master
Stephanie Gredell 12 months ago
parent
commit
0a9796b9fb
  1. 41
      Cargo.lock
  2. 2
      Cargo.toml
  3. 249
      cpu.rs
  4. 227
      main.rs
  5. 90
      websocket.rs

41
Cargo.lock generated

@ -103,6 +103,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.8.0"
@ -875,7 +881,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.8.0",
] ]
[[package]] [[package]]
@ -911,11 +917,13 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
name = "rom-emulator" name = "rom-emulator"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.8.0",
"env_logger", "env_logger",
"futures", "futures",
"lazy_static", "lazy_static",
"log", "log",
"rand",
"sdl2",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@ -949,6 +957,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 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]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.217"
@ -1327,6 +1358,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"

2
Cargo.toml

@ -9,6 +9,8 @@ env_logger = "0.11.6"
futures = "0.3.31" futures = "0.3.31"
lazy_static = "1.5.0" lazy_static = "1.5.0"
log = "0.4.25" log = "0.4.25"
rand = "0.8.5"
sdl2 = "0.37.0"
serde = "1.0.217" serde = "1.0.217"
serde_json = "1.0.135" serde_json = "1.0.135"
tokio = { version = "1.43.0", features = ["full"] } tokio = { version = "1.43.0", features = ["full"] }

249
cpu.rs

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
// This is all new to me so it's heavily commented so we can understand what is happening. // 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; 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 // X register has one special function. It can be used to copy a stack pointer or change it's
// value. // value.
pub register_x: u8, 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, pub register_y: u8,
// this creates an array with size of 0xFFFF and initializes all elements to 0 (see below in t // 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 // 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, pub stack_pointer: u8,
} }
trait Mem { pub trait Mem {
fn mem_read(&self, addr: u16) -> u8; fn mem_read(&self, addr: u16) -> u8;
fn mem_write(&mut self, addr: u16, data: 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. /// Road the program, reset the state of the cpu and then run the program.
pub fn load_and_run(&mut self, program: Vec<u8>) { pub fn load_and_run(&mut self, program: Vec<u8>) {
self.load(program); self.load(program);
self.reset(); self.program_counter = self.mem_read_u16(0xFFFC);
self.run(); self.run();
} }
@ -275,17 +278,25 @@ impl CPU {
/// Load the program code into memroy starting at address 0x8000. 0x8000 .. 0xFFFF is reserved /// 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. /// from program ROM and we can assume that the instructions should start somewhere here.
pub fn load(&mut self, program: Vec<u8>) { pub fn load(&mut self, program: Vec<u8>) {
self.memory[0x8000..(0x8000 + program.len())].copy_from_slice(&program[..]); self.memory[0x0600..(0x0600 + program.len())].copy_from_slice(&program[..]);
self.mem_write_u16(0xFFFC, 0x8000); self.mem_write_u16(0xFFFC, 0x0600);
} }
/// Load a value from memory into the Y register
fn ldy(&mut self, mode: &AddressingMode) { fn ldy(&mut self, mode: &AddressingMode) {
// compute the memory address based on the given addressing mode
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
// fetch the data from memory
let data = self.mem_read(addr); let data = self.mem_read(addr);
// store data in register y
self.register_y = data; self.register_y = data;
// Update the CpuFlags based on the new value of register y
self.update_zero_and_negative_flags(self.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) { fn ldx(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let data = self.mem_read(addr); let data = self.mem_read(addr);
@ -299,8 +310,7 @@ impl CPU {
let addr = self.get_operand_address(&mode); let addr = self.get_operand_address(&mode);
let value = self.mem_read(addr); let value = self.mem_read(addr);
self.register_a = value; self.set_register_a(value);
self.update_zero_and_negative_flags(self.register_a);
} }
/// TAX instruction copies the value from teh accumulator register (register_a) into the x /// 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); 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) { fn eor(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let data = self.mem_read(addr); let data = self.mem_read(addr);
self.set_register_a(data ^ self.register_a); 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) { fn ora(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let data = self.mem_read(addr); let data = self.mem_read(addr);
@ -363,13 +379,25 @@ impl CPU {
self.status.remove(CpuFlags::ZERO); self.status.remove(CpuFlags::ZERO);
} }
if result & 0b1000_000 != 0 { if result >> 7 == 1 {
self.status.insert(CpuFlags::NEGATIVE) self.status.insert(CpuFlags::NEGATIVE)
} else { } else {
self.status.remove(CpuFlags::NEGATIVE); 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) { fn set_carry_flag(&mut self) {
self.status.insert(CpuFlags::CARRY); self.status.insert(CpuFlags::CARRY);
} }
@ -445,6 +473,8 @@ impl CPU {
} }
/// ADC stands for Add with Carry /// 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) { fn adc(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let value = self.mem_read(addr); let value = self.mem_read(addr);
@ -475,9 +505,15 @@ impl CPU {
hi << 8 | lo 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) { fn asl_accumulator(&mut self) {
let mut data = self.register_a; let mut data = self.register_a;
// shift all the bits of data to the right by 6 positions
if data >> 6 == 1 { if data >> 6 == 1 {
// set the carry flag to indicate overflow
self.set_carry_flag(); self.set_carry_flag();
} else { } else {
self.clear_carry_flag(); self.clear_carry_flag();
@ -512,18 +548,6 @@ impl CPU {
data 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 { fn lsr(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr); let mut data = self.mem_read(addr);
@ -542,7 +566,7 @@ impl CPU {
fn rol(&mut self, mode: &AddressingMode) -> u8 { fn rol(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr); 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 { if data >> 7 == 1 {
self.set_carry_flag(); self.set_carry_flag();
@ -551,11 +575,11 @@ impl CPU {
} }
data = data << 1; data = data << 1;
if old_carry { if _old_carry {
data = data | 1; data = data | 1;
} }
self.mem_write(addr, data); self.mem_write(addr, data);
self.update_zero_and_negative_flags(data); self.update_negative_flags(data);
data data
} }
@ -579,8 +603,7 @@ impl CPU {
fn ror(&mut self, mode: &AddressingMode) -> u8 { fn ror(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode); let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr); let data = self.mem_read(addr);
let old_carry = self.status.contains(CpuFlags::CARRY);
if data & 1 == 1 { if data & 1 == 1 {
self.set_carry_flag(); self.set_carry_flag();
@ -589,7 +612,7 @@ impl CPU {
} }
self.mem_write(addr, data); self.mem_write(addr, data);
self.update_zero_and_negative_flags(data); self.update_negative_flags(data);
data data
} }
@ -693,7 +716,7 @@ impl CPU {
.wrapping_add(1) .wrapping_add(1)
.wrapping_add(jump as u16); .wrapping_add(jump as u16);
self.program_counter = jump_addr self.program_counter = jump_addr;
} }
} }
@ -729,15 +752,25 @@ impl CPU {
loop { loop {
let code = self.mem_read(self.program_counter); let code = self.mem_read(self.program_counter);
self.program_counter += 1; self.program_counter += 1;
let program_counter_state = self.program_counter;
let opcode = opcodes.get(&code).unwrap(); let opcode = opcodes.get(&code).unwrap();
match code { match code {
0xA9 | 0xa5 | 0xb4 | 0xad | 0xbd | 0xb9 | 0xa1 | 0xb1 => { 0xA9 | 0xA5 | 0xB5 | 0xAD | 0xBD | 0xB9 | 0xA1 | 0xB1 => {
self.lda(&AddressingMode::Immediate); self.lda(&opcode.mode);
self.program_counter += 1; }
0xA0 | 0xA4 | 0xB4 | 0xAC | 0xBC => {
self.ldy(&opcode.mode);
}
0xA2 | 0xA6 | 0xB6 | 0xAE | 0xBE => {
self.ldx(&opcode.mode);
}
0xEA => {
// do nothing
} }
0xAA => self.tax(), 0xAA => self.tax(),
0xe8 => self.inx(), 0xE8 => self.inx(),
0x00 => return, 0x00 => return,
0xd8 => self.status.remove(CpuFlags::DECIMAL_MODE), 0xd8 => self.status.remove(CpuFlags::DECIMAL_MODE),
0x58 => self.status.remove(CpuFlags::INTERRUPT_DISABLE), 0x58 => self.status.remove(CpuFlags::INTERRUPT_DISABLE),
@ -757,10 +790,10 @@ impl CPU {
self.plp(); self.plp();
} }
0x69 | 0x65 | 0x75 | 0x6d | 0x7d | 0x79 | 0x61 | 0x71 => { 0x69 | 0x65 | 0x75 | 0x6d | 0x7d | 0x79 | 0x61 | 0x71 => {
self.adc(&opcode.mod); self.adc(&opcode.mode);
} }
0xe9 | 0xe5 | 0xf5 | 0xed | 0xfd | 0xf9 | 0xe1 | 0xf1 => { 0xe9 | 0xe5 | 0xf5 | 0xed | 0xfd | 0xf9 | 0xe1 | 0xf1 => {
self.adc(&opcode.mode); self.sbc(&opcode.mode);
} }
0x29 | 0x25 | 0x35 | 0x2d | 0x3d | 0x39 | 0x21 | 0x31 => { 0x29 | 0x25 | 0x35 | 0x2d | 0x3d | 0x39 | 0x21 | 0x31 => {
self.and(&opcode.mode); self.and(&opcode.mode);
@ -773,7 +806,7 @@ impl CPU {
} }
0x4a => self.lsr_accumulator(), 0x4a => self.lsr_accumulator(),
0x46 | 0x56 | 0x4e | 0x5e => { 0x46 | 0x56 | 0x4e | 0x5e => {
self.lsr(&opcode.mode) self.lsr(&opcode.mode);
} }
0x0a => self.asl_accumulator(), 0x0a => self.asl_accumulator(),
0x06 | 0x16 | 0x0e | 0x1e => { 0x06 | 0x16 | 0x0e | 0x1e => {
@ -796,14 +829,16 @@ impl CPU {
self.dec(&opcode.mode); self.dec(&opcode.mode);
} }
0xca => self.dex(), 0xca => self.dex(),
0x88 => self.dey, 0x88 => {
self.dey();
}
0xc9 | 0xc5 | 0xd5 | 0xcd | 0xdd | 0xd9 | 0xc1 | 0xd1 => { 0xc9 | 0xc5 | 0xd5 | 0xcd | 0xdd | 0xd9 | 0xc1 | 0xd1 => {
self.compare(&opcode.mode, self.register_a); self.compare(&opcode.mode, self.register_a);
} }
0xc0 | 0xc4 | 0xcc => { 0xc0 | 0xc4 | 0xcc => {
self.compare(&opcode.mode, self.register_y); 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 => { 0x4c => {
let mem_address = self.mem_read_u16(self.program_counter); let mem_address = self.mem_read_u16(self.program_counter);
self.program_counter = mem_address; self.program_counter = mem_address;
@ -813,6 +848,7 @@ impl CPU {
let indirect_ref = if mem_address & 0x00FF == 0x00FF { let indirect_ref = if mem_address & 0x00FF == 0x00FF {
let lo = self.mem_read(mem_address); let lo = self.mem_read(mem_address);
let hi = self.mem_read(mem_address & 0xFF00); let hi = self.mem_read(mem_address & 0xFF00);
(hi as u16) << 8 | lo as u16
} else { } else {
self.mem_read_u16(mem_address) self.mem_read_u16(mem_address)
}; };
@ -828,37 +864,136 @@ impl CPU {
self.program_counter = self.stack_pop_u16() + 1; self.program_counter = self.stack_pop_u16() + 1;
} }
0x40 => { 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.remove(CpuFlags::BREAK);
self.status.insert(CpuFlags::BREAK2); self.status.insert(CpuFlags::BREAK2);
self.program_counter = self.stack_pop_u16(); self.program_counter = self.stack_pop_u16();
} }
// come back here 0xd0 => {
0xA5 => { self.branch(!self.status.contains(CpuFlags::ZERO));
self.lda(&AddressingMode::ZeroPage);
self.program_counter += 1;
} }
0xAD => { 0x70 => {
self.lda(&AddressingMode::Absolute); self.branch(self.status.contains(CpuFlags::OVERFLOW));
self.program_counter += 2;
} }
0x85 => { 0x50 => {
self.sta(&AddressingMode::ZeroPage); self.branch(!self.status.contains(CpuFlags::OVERFLOW));
self.program_counter += 1;
} }
0x95 => { 0x10 => {
self.sta(&AddressingMode::ZeroPage); self.branch(!self.status.contains(CpuFlags::NEGATIVE));
self.program_counter += 1;
} }
// implement the 0xAA opcode which corresponds to the TAX (transfer acculumater to 0x30 => {
// X register) self.branch(self.status.contains(CpuFlags::NEGATIVE));
0xAA => self.tax(), }
// INX stands for Increment Index Register X - increase the value of the X register 0xf0 => {
// by one and update specific processor flags based on the result. self.branch(self.status.contains(CpuFlags::ZERO));
0xe8 => self.inx(), }
_ => todo!(), 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);
} }
} }

227
main.rs

@ -1,90 +1,157 @@
use futures::{SinkExt, StreamExt}; pub mod cpu;
use log::{error, info}; pub mod opcodes;
use std::env; use cpu::Mem;
use std::net::SocketAddr; use cpu::CPU;
use tokio::net::{TcpListener, TcpStream}; use rand::Rng;
use tokio_tungstenite::{accept_async, tungstenite::protocol::Message}; use sdl2::event::Event;
mod cpu; use sdl2::keyboard::Keycode;
mod opcodes; use sdl2::pixels::Color;
// this file is overly commented because I'm learning Rust while I program. I want just want to use sdl2::pixels::PixelFormatEnum;
// thnk aloud. use sdl2::EventPump;
#[tokio::main] mod assembly;
async fn main() {
// initialize logger extern crate lazy_static;
env_logger::init();
extern crate bitflags;
// 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 /// Translate byte values to colors
let addr = env::args() fn color(byte: u8) -> Color {
.nth(1) match byte {
.unwrap_or_else(|| "127.0.0.1:8080".to_string()); 0 => sdl2::pixels::Color::BLACK,
1 => sdl2::pixels::Color::WHITE,
// SocketAddr is used to represent an IP address and port combo. .parse attempts to convert the 2 | 9 => sdl2::pixels::Color::GREY,
// string to a SocketAddr. If successful, returns a Result<SocketAddr, _>. expect() will 3 | 10 => sdl2::pixels::Color::RED,
// terminate the program and print a message if parse returns an error. If parse is successful, 4 | 11 => sdl2::pixels::Color::GREEN,
// expect() will unwrap the SockAddr from the Result and assign it to addr. 5 | 12 => sdl2::pixels::Color::BLUE,
let addr: SocketAddr = addr.parse().expect("Invalid Address"); 6 | 13 => sdl2::pixels::Color::MAGENTA,
7 | 14 => sdl2::pixels::Color::YELLOW,
// attempt to bind the tcplistner to an address. await indicates that this is async. _ => sdl2::pixels::Color::CYAN,
// 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) { /// Read a block of memory and update frame buffer with the corresponding RGB value
// perform the handshake on the given TCP stream /// If returns true, then there were changes to the frame
// The match expression is used to handle the result of the accept_async function fn read_screen_state(cpu: &CPU, frame: &mut [u8; 32 * 3 * 32]) -> bool {
// accept_async will return a Result that is either an Ok(ws) which will hand us the websocket let mut frame_idx = 0;
// connection or an error message. let mut update = false;
let ws_stream = match accept_async(stream).await { for i in 0x0200..0x600 {
Ok(ws) => ws, let color_idx = cpu.mem_read(i as u16);
Err(e) => { let (b1, b2, b3) = color(color_idx).rgb();
error!("Error during the websocket handshake: {}", e); if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 {
return; frame[frame_idx] = b1;
frame[frame_idx + 1] = b2;
frame[frame_idx + 2] = b3;
update = true;
} }
}; frame_idx += 3;
}
// split websocket stream into sender and receiver update
let (mut sender, mut receiver) = ws_stream.split(); }
// handle incoming websocket messages and reverse the string that comes in and then send it fn handle_user_input(cpu: &mut CPU, event_pump: &mut EventPump) {
// back. Why? Because. for event in event_pump.poll_iter() {
while let Some(msg) = receiver.next().await { match event {
match msg { Event::Quit { .. }
Ok(Message::Text(text)) => { | Event::KeyDown {
let reversed = text.chars().rev().collect::<String>(); keycode: Some(Keycode::Escape),
// .into converts the types when possible. The Message::Text variant expects String ..
// but reversed might not match that exact implementation. Libraries like } => std::process::exit(0),
// tokio-tungstenite may require into. Event::KeyDown {
if let Err(e) = sender.send(Message::Text(reversed.into())).await { keycode: Some(Keycode::W),
error!("Error sending message: {}", e); ..
} => {
cpu.mem_write(0xff, 0x77);
} }
Event::KeyDown {
keycode: Some(Keycode::S),
..
} => {
cpu.mem_write(0xff, 0x73);
} }
Ok(Message::Close(_)) => break, Event::KeyDown {
Ok(_) => (), keycode: Some(Keycode::A),
Err(e) => { ..
error!("Error processing message: {}", e); } => {
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));
});
} }

90
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<SocketAddr, _>. 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::<String>();
// .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);
}
}
}
}
Loading…
Cancel
Save