use super::{protocol::Packet, Config}; use crate::State; use anyhow::{anyhow, bail, Result}; use log::{debug, error, info}; use std::sync::Arc; use tokio::{ io::{AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}, net::{TcpListener, TcpStream}, spawn, }; pub async fn game_server(config: Config, state: Arc) -> Result<()> { let listener = TcpListener::bind(config.bind).await?; info!("listening on {}", listener.local_addr()?); while let Ok((sock, addr)) = listener.accept().await { let state = state.clone(); spawn(async move { info!("connected {addr}"); if let Err(e) = handle_client(sock, state).await { error!("client error: {e}") } info!("disconnected {addr}"); }); } bail!("accept failure") } async fn handle_client(sock: TcpStream, state: Arc) -> Result<()> { let mut pid = None; let res = handle_client_inner(sock, &state, &mut pid).await; if let Some(pid) = pid { state.players.write().await.remove(&pid); } res } async fn handle_client_inner( sock: TcpStream, state: &Arc, pid: &mut Option, ) -> anyhow::Result<()> { let (rx, tx) = sock.into_split(); let rx = BufReader::new(rx); let mut tx = BufWriter::new(tx); let mut lines = rx.lines(); let mut ticks = state.tick.subscribe(); let mut chat = state.chat.subscribe(); tx.send_packet(Packet::Motd("This is the GPN-Tron Rust rewrite. The protocol should be compatible with the original: https://github.com/freehuntx/gpn-tron/blob/master/PROTOCOL.md".to_string())).await?; loop { tokio::select! { message = chat.recv() => { let (player, message) = message?; tx.send_packet(Packet::Chat(format!("{player}: {message}"))).await?; }, new_game = ticks.recv() => { let new_game = new_game?; handle_tick(&mut tx, pid, state, new_game).await?; }, line = lines.next_line() => { let line = line?.ok_or(anyhow!("eof"))?; handle_packet(&mut tx, pid, state, line).await?; }, }; } } trait SendPacketExt { async fn send_packet(&mut self, packet: Packet) -> anyhow::Result<()>; } impl SendPacketExt for T { async fn send_packet(&mut self, packet: Packet) -> anyhow::Result<()> { debug!("-> {packet:?}"); self.write_all(packet.dump().as_bytes()).await?; self.write_all(b"\n").await?; self.flush().await?; Ok(()) } } async fn handle_tick( mut tx: impl AsyncWrite + Unpin, pid: &mut Option, state: &Arc, new_game: bool, ) -> anyhow::Result<()> { let Some(pid) = pid else { return Ok(()) }; let mut events = Vec::new(); { let g = state.game.read().await; if new_game { events.push(Packet::Game { my_id: 0, width: g.size.x as usize, height: g.size.y as usize, }); for (player, (_, _, name)) in &g.heads { events.push(Packet::Player { id: *player, name: name.to_owned(), }) } } for (player, (_, pos, _)) in &g.heads { events.push(Packet::Pos { id: *player, x: pos.x, y: pos.y, }) } if !g.dead.is_empty() { events.push(Packet::Die(g.dead.clone())); } if g.dead.contains(pid) { events.push(Packet::Lose(0, 0)); // TODO implement stats } events.push(Packet::Tick); } for e in events { tx.send_packet(e).await?; } Ok(()) } async fn handle_packet( mut tx: impl AsyncWrite + Unpin, pid: &mut Option, state: &Arc, line: String, ) -> anyhow::Result<()> { let packet = match Packet::parse(&line) { Ok(p) => p, Err(e) => { tx.send_packet(Packet::Error(format!("invalid packet: {e:?}"))) .await?; return Err(e); } }; debug!("<- {packet:?}"); match packet { Packet::Join { username, password: _, } => { if pid.is_some() { tx.send_packet(Packet::Error("already joined".to_string())) .await? } else { let mut g = state.players.write().await; let mut id = 0; while g.contains_key(&id) { id += 1; } g.insert(id, username); *pid = Some(id); } } Packet::Move(dir) => { if let Some(pid) = pid { let mut g = state.game.write().await; if let Some((head_dir, _, _)) = g.heads.get_mut(pid) { *head_dir = dir } else { drop(g); tx.send_packet(Packet::Error("you are already dead".to_string())) .await? } } else { tx.send_packet(Packet::Error("you need to join first".to_string())) .await? } } Packet::Chat(message) => { let _ = state.chat.send(("".to_string(), message)); } _ => { tx.send_packet(Packet::Error("clients dont send this packet".to_string())) .await? } }; Ok(()) }