5 changed files with 475 additions and 144 deletions
@ -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)); |
||||
}); |
||||
} |
||||
|
||||
@ -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…
Reference in new issue