aboutsummaryrefslogtreecommitdiff
path: root/src/game
diff options
context:
space:
mode:
Diffstat (limited to 'src/game')
-rw-r--r--src/game/map.rs41
-rw-r--r--src/game/mod.rs28
-rw-r--r--src/game/server.rs70
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);