kajam_10/telnet/src/main.rs

141 lines
3.2 KiB
Rust

use anyhow::{
Context,
};
use tokio::{
io::{
AsyncReadExt,
AsyncWrite,
AsyncWriteExt,
},
net::{
TcpListener,
TcpStream,
},
};
use tracing_subscriber::{
fmt,
fmt::format::FmtSpan,
EnvFilter,
};
async fn print <T: AsyncWrite + Unpin> (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 <R: AsyncReadExt + Unpin, W: AsyncWrite + Unpin> (
read: &mut R, write: &mut W
) -> anyhow::Result <String>
{
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;
}
}
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 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 {
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 {} beat the game", id),
Response::Quit => break 'main_loop,
_ => (),
}
}
}
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);
});
}
}