use anyhow::{ Context, }; use tokio::{ io::{ AsyncReadExt, AsyncWrite, AsyncWriteExt, }, net::{ TcpListener, TcpStream, }, }; use tracing_subscriber::{ fmt, fmt::format::FmtSpan, EnvFilter, }; async fn print (socket: &mut T, mut s: &str) -> std::io::Result <()> { const COLS: usize = 80; while s.len () > COLS { socket.write_all (format! ("{}\n", &s [0..80]).as_bytes ()).await?; s = &s [80..]; } socket.write_all (format! ("{}\n", s).as_bytes ()).await?; Ok (()) } async fn read_input ( read: &mut R, write: &mut W ) -> anyhow::Result { write.write_all (b"> ").await?; write.flush ().await?; let mut buffer = Vec::with_capacity (80); for _ in 0..80 { let b = read.read_u8 ().await?; buffer.push (b); if b == b'\n' { break; } } if ! buffer.contains (&b'\n') { anyhow::bail! ("No newline. User might be spamming us or something."); } let buffer = String::from_utf8 (buffer)?; // I don't know why I need the type annotation here, but I do. let x: &[_] = &['\r', '\n']; let buffer = buffer.trim_end_matches (x).to_string (); Ok (buffer) } async fn sleep (ms: u32) { tokio::time::sleep (std::time::Duration::from_millis (ms.into ())).await; } async fn process_socket (socket: TcpStream, id: &str) -> anyhow::Result <()> { use kajam_10_game::Response; let mut interval = tokio::time::interval (tokio::time::Duration::from_millis (500)); interval.set_missed_tick_behavior (tokio::time::MissedTickBehavior::Delay); interval.tick ().await; let mut seq = 0; let (mut read, mut write) = socket.into_split (); let mut state = kajam_10_game::State::default (); let responses = state.step (""); for response in responses.into_iter () { match response { Response::Print (line) => print (&mut write, &line).await?, Response::PrintMany (lines) => { for line in lines { print (&mut write, line).await?; } }, Response::Sleep (x) => sleep (x).await, _ => (), } } 'main_loop: loop { interval.tick ().await; let input = read_input (&mut read, &mut write).await.context ("couldn't get player input")?; tracing::debug! ("Processing input {} from connection {}", seq, id); seq += 1; let responses = state.step (&input); for response in responses.into_iter () { match response { Response::Print (line) => print (&mut write, &line).await?, Response::PrintMany (lines) => { for line in lines { print (&mut write, line).await?; } }, Response::Sleep (x) => sleep (x).await, Response::PlayerVictory => tracing::info! ("Connection {} PlayerVictory", id), Response::JokeEnding => tracing::info! ("Connection {} JokeEnding", id), Response::Quit => break 'main_loop, _ => (), } } } tracing::info! ("Connection {} ending gracefully", id); Ok (()) } #[tokio::main] async fn main () -> std::io::Result <()> { fmt () .with_env_filter (EnvFilter::from_default_env ()) .with_span_events (FmtSpan::CLOSE) .init () ; let listen_addr = "0.0.0.0:2300"; let listener = TcpListener::bind (listen_addr).await?; tracing::debug! ("Listening on TCP {}", listen_addr); loop { let (socket, addr) = listener.accept ().await?; let ulid_string = rusty_ulid::generate_ulid_string (); tracing::info! ("Accepted connection {} from {}", ulid_string, addr); tokio::spawn (async move { if let Err (e) = process_socket (socket, &ulid_string).await { tracing::error! ("Error in connection {}: {:?}", ulid_string, e); } tracing::debug! ("Ended connection {}", ulid_string); }); } }