aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-06-04 15:19:39 +0200
committermetamuffin <metamuffin@disroot.org>2024-06-04 15:19:39 +0200
commitce0b808a01081322abc7ed51e09d0f452b606ad7 (patch)
treef3574b5b1a6d935629f78ec80b5ce191c41c4cfa
parent3d107ea4710f3dec0eedd91ed5bc1e52d8f15912 (diff)
downloadgpn-tron-rust-ce0b808a01081322abc7ed51e09d0f452b606ad7.tar
gpn-tron-rust-ce0b808a01081322abc7ed51e09d0f452b606ad7.tar.bz2
gpn-tron-rust-ce0b808a01081322abc7ed51e09d0f452b606ad7.tar.zst
more code
-rw-r--r--src/game/protocol.rs2
-rw-r--r--src/game/server.rs165
-rw-r--r--src/lib.rs16
-rw-r--r--src/main.rs26
-rw-r--r--src/spectate/server.rs2
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);