Browse Source

a bunch of stuff

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

41
Cargo.lock generated

@ -103,6 +103,12 @@ version = "0.21.7" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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"

2
Cargo.toml

@ -9,6 +9,8 @@ env_logger = "0.11.6" @@ -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"] }

257
cpu.rs

@ -1,7 +1,7 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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<u8>) {
self.load(program);
self.reset();
self.program_counter = self.mem_read_u16(0xFFFC);
self.run();
}
@ -275,17 +278,25 @@ impl CPU { @@ -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<u8>) {
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -720,7 +743,7 @@ impl CPU {
// - Decode instruction
// - Execute the instruction
// - Repeat
pub fn run_with_callback<F>(&mut self, mut callback: F)
pub fn run_with_callback<F>(&mut self, mut callback: F)
where
F: FnMut(&mut CPU),
{
@ -729,15 +752,25 @@ impl 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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}

229
main.rs

@ -1,90 +1,157 @@ @@ -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<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));
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::<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);
}
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));
});
}

90
websocket.rs

@ -0,0 +1,90 @@ @@ -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