diff options
Diffstat (limited to 'src/game')
-rw-r--r-- | src/game/map.rs | 41 | ||||
-rw-r--r-- | src/game/mod.rs | 28 | ||||
-rw-r--r-- | src/game/server.rs | 70 |
3 files changed, 109 insertions, 30 deletions
diff --git a/src/game/map.rs b/src/game/map.rs new file mode 100644 index 0000000..420e256 --- /dev/null +++ b/src/game/map.rs @@ -0,0 +1,41 @@ +use glam::IVec2; +use std::ops::{Index, IndexMut}; + +pub struct Map { + pub cells: Vec<Option<u32>>, + pub size: IVec2, +} +impl Map { + pub fn new(width: usize, height: usize) -> Self { + Self { + cells: vec![None; width * height], + size: IVec2::new(width as i32, height as i32), + } + } + pub fn index(&self, v: IVec2) -> usize { + let i = v.x.rem_euclid(self.size.x as i32) + + v.y.rem_euclid(self.size.y as i32) * self.size.x as i32; + i as usize + } + pub fn clear_player(&mut self, p: u32) { + self.cells.iter_mut().for_each(|c| { + if let Some(cm) = c { + if *cm == p { + *c = None; + } + } + }); + } +} +impl Index<IVec2> for Map { + type Output = Option<u32>; + fn index(&self, index: IVec2) -> &Self::Output { + &self.cells[self.index(index)] + } +} +impl IndexMut<IVec2> for Map { + fn index_mut(&mut self, index: IVec2) -> &mut Self::Output { + let i = self.index(index); + &mut self.cells[i] + } +} diff --git a/src/game/mod.rs b/src/game/mod.rs index 27faa27..f59b5d5 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,34 +1,34 @@ use glam::IVec2; +use map::Map; use protocol::Direction; use serde::Deserialize; use std::{collections::HashMap, net::SocketAddr, ops::ControlFlow}; +pub mod map; pub mod protocol; pub mod server; -#[derive(Deserialize)] +#[derive(Deserialize, Clone)] pub struct Config { bind: SocketAddr, + tickrate: f32, } pub struct Game { pub heads: HashMap<u32, (Direction, IVec2, String)>, - pub map: HashMap<IVec2, u32>, - pub size: IVec2, + pub map: Map, pub dead: Vec<u32>, } impl Game { - pub fn new(players: Vec<String>) -> Self { - let mut map = HashMap::new(); + pub fn new(players: Vec<(u32, String)>) -> Self { + let mut map = Map::new(players.len() * 2, players.len() * 2); let mut heads = HashMap::new(); - let plen = players.len(); - for (p, name) in players.into_iter().enumerate() { + for (p, name) in players { let pos = IVec2::ONE * p as i32 * 2; - map.insert(pos, p as u32); - heads.insert(p as u32, (Direction::Up, pos, name)); + map[pos] = Some(p); + heads.insert(p, (Direction::Up, pos, name)); } Self { - size: IVec2::ONE * plen as i32 * 2, heads, map, dead: Vec::new(), @@ -36,14 +36,14 @@ impl Game { } pub fn tick(&mut self) -> ControlFlow<Option<u32>, ()> { for (_player, (dir, head, _)) in &mut self.heads { - *head = (*head + dir.vector()).rem_euclid(self.size) + *head = (*head + dir.vector()).rem_euclid(self.map.size) } self.dead.clear(); let mut h = HashMap::<IVec2, Vec<u32>>::new(); for (player, (_, head, _)) in &self.heads { h.entry(*head).or_default().push(*player); - if self.map.contains_key(head) { + if self.map[*head].is_some() { self.dead.push(*player); } } @@ -54,11 +54,11 @@ impl Game { } for (player, (_, head, _)) in &mut self.heads { - self.map.insert(*head, *player); + self.map[*head] = Some(*player); } for d in &self.dead { - self.map.retain(|_, p| *d != *p); + self.map.clear_player(*d); self.heads.remove(&d); } diff --git a/src/game/server.rs b/src/game/server.rs index a4472a8..f1d10d2 100644 --- a/src/game/server.rs +++ b/src/game/server.rs @@ -1,15 +1,18 @@ -use super::{protocol::Packet, Config}; +use super::{protocol::Packet, Config, Game}; use crate::State; use anyhow::{anyhow, bail, Result}; use log::{debug, error, info}; -use std::sync::Arc; +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<State>) -> 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 { @@ -25,10 +28,39 @@ pub async fn game_server(config: Config, state: Arc<State>) -> Result<()> { bail!("accept failure") } +async fn game_loop(config: Config, state: Arc<State>) { + loop { + sleep(Duration::from_secs_f32(1. / config.tickrate)).await; + + let mut g = state.game.write().await; + let res = g.tick(); + match res { + ControlFlow::Continue(()) => { + let _ = state.tick.send(false); + } + ControlFlow::Break(winner) => { + info!("winner: {winner:?}"); + let p = state.players.read().await; + *g = Game::new(p.clone().into_iter().collect()); + let _ = state.tick.send(true); + } + } + drop(g); + } +} + +struct ClientState { + pid: Option<u32>, + alive: bool, +} + 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 { + 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 @@ -37,7 +69,7 @@ async fn handle_client(sock: TcpStream, state: Arc<State>) -> Result<()> { async fn handle_client_inner( sock: TcpStream, state: &Arc<State>, - pid: &mut Option<u32>, + pid: &mut ClientState, ) -> anyhow::Result<()> { let (rx, tx) = sock.into_split(); let rx = BufReader::new(rx); @@ -79,19 +111,25 @@ impl<T: AsyncWrite + Unpin> SendPacketExt for T { async fn handle_tick( mut tx: impl AsyncWrite + Unpin, - pid: &mut Option<u32>, + cstate: &mut ClientState, state: &Arc<State>, new_game: bool, ) -> anyhow::Result<()> { - let Some(pid) = pid else { return Ok(()) }; + 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; + } { 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, + width: g.map.size.x as usize, + height: g.map.size.y as usize, }); for (player, (_, _, name)) in &g.heads { events.push(Packet::Player { @@ -110,7 +148,7 @@ async fn handle_tick( if !g.dead.is_empty() { events.push(Packet::Die(g.dead.clone())); } - if g.dead.contains(pid) { + if g.dead.contains(&pid) { events.push(Packet::Lose(0, 0)); // TODO implement stats } events.push(Packet::Tick); @@ -123,7 +161,7 @@ async fn handle_tick( async fn handle_packet( mut tx: impl AsyncWrite + Unpin, - pid: &mut Option<u32>, + cstate: &mut ClientState, state: &Arc<State>, line: String, ) -> anyhow::Result<()> { @@ -141,7 +179,7 @@ async fn handle_packet( username, password: _, } => { - if pid.is_some() { + if cstate.pid.is_some() { tx.send_packet(Packet::Error("already joined".to_string())) .await? } else { @@ -151,13 +189,13 @@ async fn handle_packet( id += 1; } g.insert(id, username); - *pid = Some(id); + cstate.pid = Some(id); } } Packet::Move(dir) => { - if let Some(pid) = pid { + if let Some(pid) = cstate.pid { let mut g = state.game.write().await; - if let Some((head_dir, _, _)) = g.heads.get_mut(pid) { + if let Some((head_dir, _, _)) = g.heads.get_mut(&pid) { *head_dir = dir } else { drop(g); |