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>, tx: BufWriter, } impl Client { pub fn new(host: &str) -> Result { 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 { 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!("error|{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::>() .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 { 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), 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 { 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, } } }