diff options
author | metamuffin <metamuffin@disroot.org> | 2024-06-04 15:19:39 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-06-04 15:19:39 +0200 |
commit | ce0b808a01081322abc7ed51e09d0f452b606ad7 (patch) | |
tree | f3574b5b1a6d935629f78ec80b5ce191c41c4cfa | |
parent | 3d107ea4710f3dec0eedd91ed5bc1e52d8f15912 (diff) | |
download | gpn-tron-rust-ce0b808a01081322abc7ed51e09d0f452b606ad7.tar gpn-tron-rust-ce0b808a01081322abc7ed51e09d0f452b606ad7.tar.bz2 gpn-tron-rust-ce0b808a01081322abc7ed51e09d0f452b606ad7.tar.zst |
more code
-rw-r--r-- | src/game/protocol.rs | 2 | ||||
-rw-r--r-- | src/game/server.rs | 165 | ||||
-rw-r--r-- | src/lib.rs | 16 | ||||
-rw-r--r-- | src/main.rs | 26 | ||||
-rw-r--r-- | src/spectate/server.rs | 2 |
5 files changed, 177 insertions, 34 deletions
diff --git a/src/game/protocol.rs b/src/game/protocol.rs index c180282..abb246f 100644 --- a/src/game/protocol.rs +++ b/src/game/protocol.rs @@ -47,7 +47,7 @@ impl Packet { format!("chat|{message}") } Packet::Motd(motd) => format!("motd|{motd}"), - Packet::Error(message) => format!("erro|{message}"), + Packet::Error(message) => format!("error|{message}"), Packet::Game { my_id, width, diff --git a/src/game/server.rs b/src/game/server.rs index 1775bd1..a4472a8 100644 --- a/src/game/server.rs +++ b/src/game/server.rs @@ -1,14 +1,10 @@ -use super::{ - protocol::{Direction, Packet}, - Config, -}; +use super::{protocol::Packet, Config}; use crate::State; -use anyhow::{bail, Result}; -use glam::IVec2; -use log::{error, info}; +use anyhow::{anyhow, bail, Result}; +use log::{debug, error, info}; use std::sync::Arc; use tokio::{ - io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, + io::{AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}, net::{TcpListener, TcpStream}, spawn, }; @@ -17,9 +13,10 @@ pub async fn game_server(config: Config, state: Arc<State>) -> 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).await { + if let Err(e) = handle_client(sock, state).await { error!("client error: {e}") } info!("disconnected {addr}"); @@ -28,19 +25,157 @@ pub async fn game_server(config: Config, state: Arc<State>) -> Result<()> { bail!("accept failure") } -async fn handle_client(sock: TcpStream) -> Result<()> { +async fn handle_client(sock: TcpStream, state: Arc<State>) -> 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<State>, + pid: &mut Option<u32>, +) -> 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?; + }, + }; + } +} - while let Some(line) = lines.next_line().await? { - let packet = Packet::parse(&line)?; +trait SendPacketExt { + async fn send_packet(&mut self, packet: Packet) -> anyhow::Result<()>; +} +impl<T: AsyncWrite + Unpin> 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(()) + } +} - tx.write_all(packet.dump().as_bytes()).await?; - tx.flush().await?; +async fn handle_tick( + mut tx: impl AsyncWrite + Unpin, + pid: &mut Option<u32>, + state: &Arc<State>, + 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<u32>, + state: &Arc<State>, + 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(()) } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a213a83 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +#![feature(async_closure)] +#![feature(iterator_try_collect)] +use std::collections::HashMap; + +use game::Game; +use tokio::sync::{broadcast, RwLock}; +pub mod config; +pub mod game; +pub mod spectate; + +pub struct State { + pub tick: broadcast::Sender<bool>, // true for new game + pub game: RwLock<Game>, + pub players: RwLock<HashMap<u32, String>>, + pub chat: broadcast::Sender<(String, String)>, +} diff --git a/src/main.rs b/src/main.rs index 368f439..2e379fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,11 @@ -#![feature(iterator_try_collect)] -use config::Config; -use game::{server::game_server, Game}; -use spectate::server::spectate_server; -use std::sync::Arc; -use tokio::{ - spawn, - sync::{broadcast, RwLock}, +use gpn_tron2::{ + config::Config, + game::{server::game_server, Game}, + spectate::server::spectate_server, + State, }; - -pub mod config; -pub mod game; -pub mod spectate; - -pub struct State { - tick: broadcast::Sender<bool>, // true for new game - game: RwLock<Game>, -} +use std::sync::Arc; +use tokio::{spawn, sync::broadcast}; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -24,6 +14,8 @@ async fn main() -> anyhow::Result<()> { let state = Arc::new(State { tick: broadcast::channel(16).0, game: Game::new(vec![]).into(), + players: Default::default(), + chat: broadcast::channel(16).0, }); spawn(spectate_server(config.spectate, state.clone())); game_server(config.game, state.clone()).await?; diff --git a/src/spectate/server.rs b/src/spectate/server.rs index 8fa1a3d..9dbfebe 100644 --- a/src/spectate/server.rs +++ b/src/spectate/server.rs @@ -79,8 +79,8 @@ async fn broadcaster(sstate: Arc<SpectateState>, state: Arc<State>) { if !g.dead.is_empty() { events.push(Packet::Die(g.dead.clone())); } + events.push(Packet::Tick); } - sstate.past_events.write().await.extend(events.clone()); for ev in events { let _ = sstate.events.send(ev); |