aboutsummaryrefslogtreecommitdiff
path: root/src/game/protocol.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/protocol.rs')
-rw-r--r--src/game/protocol.rs204
1 files changed, 204 insertions, 0 deletions
diff --git a/src/game/protocol.rs b/src/game/protocol.rs
new file mode 100644
index 0000000..c180282
--- /dev/null
+++ b/src/game/protocol.rs
@@ -0,0 +1,204 @@
+use anyhow::{anyhow, bail, Result};
+use glam::IVec2;
+use log::debug;
+use serde::Serialize;
+use std::{
+ fmt::Display,
+ io::{BufRead, BufReader, BufWriter, Lines, Write},
+ net::TcpStream,
+ str::FromStr,
+};
+
+pub struct Client {
+ rx: Lines<BufReader<TcpStream>>,
+ tx: BufWriter<TcpStream>,
+}
+impl Client {
+ pub fn new(host: &str) -> Result<Self> {
+ let sock = TcpStream::connect(host)?;
+ let rx = BufReader::new(sock.try_clone()?).lines();
+ let tx = BufWriter::new(sock);
+ Ok(Self { rx, tx })
+ }
+ pub fn read(&mut self) -> Result<Packet> {
+ let line = self.rx.next().ok_or(anyhow!("eof"))??;
+ let packet = Packet::parse(&line)?;
+ debug!("<- {packet:?}");
+ Ok(packet)
+ }
+ pub fn write(&mut self, packet: Packet) -> Result<()> {
+ debug!("-> {packet:?}");
+ self.tx.write_all(packet.dump().as_bytes())?;
+ self.tx.flush()?;
+ Ok(())
+ }
+}
+
+impl Packet {
+ pub fn dump(&self) -> String {
+ match self {
+ Packet::Join { username, password } => {
+ format!("join|{username}|{password}")
+ }
+ Packet::Move(dir) => {
+ format!("move|{dir}")
+ }
+ Packet::Chat(message) => {
+ format!("chat|{message}")
+ }
+ Packet::Motd(motd) => format!("motd|{motd}"),
+ Packet::Error(message) => format!("erro|{message}"),
+ Packet::Game {
+ my_id,
+ width,
+ height,
+ } => format!("game|{width}|{height}|{my_id}"),
+ Packet::Pos { id, x, y } => format!("pos|{id}|{x}|{y}"),
+ Packet::Player { id, name } => format!("player|{id}|{name}"),
+ Packet::Tick => format!("tick"),
+ Packet::Die(players) => format!(
+ "die|{}",
+ players
+ .into_iter()
+ .map(|x| format!("{x}"))
+ .collect::<Vec<_>>()
+ .join("|")
+ ),
+ Packet::Message { id, message } => format!("message|{id}|{message}"),
+ Packet::Win(x, y) => format!("win|{x}|{y}"),
+ Packet::Lose(x, y) => format!("lose|{x}|{y}"),
+ }
+ }
+ pub fn parse(line: &str) -> Result<Self> {
+ let mut toks = line.split("|");
+ Ok(match toks.next().ok_or(anyhow!("packet type missing"))? {
+ "motd" => Packet::Motd(toks.next().ok_or(anyhow!("motd missing"))?.to_string()),
+ "error" => Packet::Error(
+ toks.next()
+ .ok_or(anyhow!("error message missing"))?
+ .to_string(),
+ ),
+ "pos" => Packet::Pos {
+ id: toks.next().ok_or(anyhow!("id missing"))?.parse()?,
+ x: toks.next().ok_or(anyhow!("x missing"))?.parse()?,
+ y: toks.next().ok_or(anyhow!("y missing"))?.parse()?,
+ },
+ "game" => Packet::Game {
+ width: toks.next().ok_or(anyhow!("width missing"))?.parse()?,
+ height: toks.next().ok_or(anyhow!("height missing"))?.parse()?,
+ my_id: toks.next().ok_or(anyhow!("my id missing"))?.parse()?,
+ },
+ "player" => Packet::Player {
+ id: toks.next().ok_or(anyhow!("id missing"))?.parse()?,
+ name: toks.next().ok_or(anyhow!("name missing"))?.to_string(),
+ },
+ "tick" => Packet::Tick,
+ "die" => Packet::Die(toks.map(|e| e.parse()).try_collect()?),
+ "message" => Packet::Message {
+ id: toks.next().ok_or(anyhow!("id missing"))?.parse()?,
+ message: toks.next().ok_or(anyhow!("message missing"))?.to_string(),
+ },
+ "win" => Packet::Win(
+ toks.next().ok_or(anyhow!("something missing"))?.parse()?,
+ toks.next().ok_or(anyhow!("something missing"))?.parse()?,
+ ),
+ "lose" => Packet::Lose(
+ toks.next().ok_or(anyhow!("something missing"))?.parse()?,
+ toks.next().ok_or(anyhow!("something missing"))?.parse()?,
+ ),
+ "move" => Packet::Move(Direction::from_str(
+ toks.next().ok_or(anyhow!("direction missing"))?,
+ )?),
+ "join" => Packet::Join {
+ username: toks.next().ok_or(anyhow!("username missing"))?.to_string(),
+ password: toks.next().ok_or(anyhow!("password missing"))?.to_string(),
+ },
+ x => bail!("unknown command: {x:?}"),
+ })
+ }
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum Packet {
+ Motd(String),
+ Error(String),
+ Game {
+ my_id: u32,
+ width: usize,
+ height: usize,
+ },
+ Pos {
+ id: u32,
+ x: i32,
+ y: i32,
+ },
+ Player {
+ id: u32,
+ name: String,
+ },
+ Tick,
+ Die(Vec<u32>),
+ Message {
+ id: u32,
+ message: String,
+ },
+ Win(usize, usize),
+ Lose(usize, usize),
+ Join {
+ username: String,
+ password: String,
+ },
+ Move(Direction),
+ Chat(String),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
+pub enum Direction {
+ Up,
+ Down,
+ Left,
+ Right,
+}
+
+impl Display for Direction {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(match self {
+ Direction::Up => "up",
+ Direction::Down => "down",
+ Direction::Left => "left",
+ Direction::Right => "right",
+ })
+ }
+}
+impl FromStr for Direction {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "up" => Direction::Up,
+ "down" => Direction::Down,
+ "left" => Direction::Left,
+ "right" => Direction::Right,
+ x => bail!("unknown direction: {x:?}"),
+ })
+ }
+}
+impl Direction {
+ pub const ALL: [Direction; 4] = [Self::Up, Self::Down, Self::Left, Self::Right];
+ pub fn vector(self) -> IVec2 {
+ match self {
+ Direction::Up => (0, -1).into(),
+ Direction::Down => (0, 1).into(),
+ Direction::Left => (-1, 0).into(),
+ Direction::Right => (1, 0).into(),
+ }
+ }
+ pub fn rotate_ccw(self) -> Self {
+ match self {
+ Direction::Up => Direction::Right,
+ Direction::Down => Direction::Left,
+ Direction::Left => Direction::Up,
+ Direction::Right => Direction::Down,
+ }
+ }
+}