use super::{protocol::Packet, Config, Game}; use crate::{database::DatabaseExt, State}; use anyhow::{anyhow, bail, Result}; use log::{debug, error, info}; use std::{ops::ControlFlow, sync::Arc, time::Duration}; use tokio::{ io::{AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}, net::{TcpListener, TcpStream}, spawn, time::sleep, }; pub async fn game_server(config: Config, state: Arc) -> Result<()> { spawn(game_loop(config.clone(), state.clone())); 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 game_loop(config: Config, state: Arc) { let mut speed = config.tickrate; loop { sleep(Duration::from_secs_f32(1. / speed)).await; let mut g = state.game.write().await; let res = g.tick(); match res { ControlFlow::Continue(()) => { let _ = state.tick.send(false); speed += config.tickrate_speedup; speed = speed.min(config.tickrate_max) } ControlFlow::Break(winner) => { info!("winner: {winner:?}"); if let Some(winner) = winner { if let Some(winner) = state.players.write().await.get(&winner).cloned() { let mut h = state.win_history.write().await; h.push_front(winner); while h.len() > 64 { h.pop_back(); } } } let p = state.players.read().await; *g = Game::new(p.clone().into_iter().collect()); speed = config.tickrate; let _ = state.tick.send(true); } } drop(g); } } struct ClientState { pid: Option, alive: bool, } async fn handle_client(sock: TcpStream, state: Arc) -> Result<()> { let mut cstate = ClientState { pid: None, alive: false, }; let res = handle_client_inner(sock, &state, &mut cstate).await; if let Some(pid) = cstate.pid { state.players.write().await.remove(&pid); } res } async fn handle_client_inner( sock: TcpStream, state: &Arc, pid: &mut ClientState, ) -> 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. It should be compatible with the original protocol: 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, cstate: &mut ClientState, state: &Arc, new_game: bool, ) -> anyhow::Result<()> { let Some(pid) = cstate.pid else { return Ok(()) }; let mut events = Vec::new(); if new_game { if cstate.alive { tx.send_packet(Packet::Win(0, 0)).await?; } cstate.alive = true; } if !cstate.alive { return Ok(()); } { let g = state.game.read().await; if new_game { events.push(Packet::Game { my_id: 0, width: g.map.size.x as usize, height: g.map.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) { cstate.alive = false; events.push(Packet::Lose(0, 0)); // TODO implement stats } else { events.push(Packet::Tick); } } for e in events { tx.send_packet(e).await?; } Ok(()) } async fn handle_packet( mut tx: impl AsyncWrite + Unpin, cstate: &mut ClientState, 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 username.len() > 64 || password.len() > 64 { tx.send_packet(Packet::Error( "password or username too long (> 64)".to_string(), )) .await?; return Ok(()); } if !state.db.check_or_insert_creds(&username, &password)? { tx.send_packet(Packet::Error("incorrect password".to_string())) .await?; return Ok(()); } if cstate.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); cstate.pid = Some(id); } } Packet::Move(dir) => { if let Some(pid) = cstate.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(()) }