141 lines
3.2 KiB
Rust
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);
|
|
});
|
|
}
|
|
}
|