diff --git a/.gitignore b/.gitignore index ea8c4bf..1a4595e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +*.log diff --git a/src/defaults.rs b/src/defaults.rs index 636d751..bdb2b2a 100644 --- a/src/defaults.rs +++ b/src/defaults.rs @@ -53,11 +53,11 @@ pub const MAP: &str = " pub const PLAYERS: usize = 4; -pub const DEFENDER: usize = 3; +pub const POSITIONS: [Option; 4] = + [Some((40, 1)), Some((1, 5)), Some((45, 45)), Some((1, 40))]; -pub const POSITIONS: [Option; 4] = [Some((40, 1)), Some((1, 5)), Some((45, 45)), None]; - -pub const TARGETS: [Option; 4] = [Some((1, 46)), Some((45, 45)), Some((1, 1)), None]; +pub const TARGETS: [Option; 4] = + [Some((1, 46)), Some((45, 45)), Some((1, 1)), Some((42, 1))]; pub const GUARDS: [Option<(Point, Direction)>; 5] = [ Some(((15, 12), Direction::Up)), diff --git a/src/game.rs b/src/game.rs index a5954db..7fe0476 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,7 +1,7 @@ use crate::{defaults, Cli, Config, MsgToClient, MsgToServer, Result, UIBackend, UserInterface}; use rand::{ distributions::{Distribution, Standard}, - random, Rng, + random, thread_rng, Rng, }; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -260,11 +260,16 @@ impl Game { let config = Config::new(); let player = 0; - let defender = defaults::DEFENDER; + // Currently use test defaults let map = defaults::MAP.into(); - let positions = defaults::POSITIONS.to_vec(); + let mut positions = defaults::POSITIONS.to_vec(); + let mut targets = defaults::TARGETS.to_vec(); let guards = defaults::GUARDS.to_vec(); - let targets = defaults::TARGETS.to_vec(); + + let mut rng = thread_rng(); + let defender = rng.gen_range(0..config.players); + positions[defender] = None; + targets[defender] = None; Game { address, @@ -362,6 +367,16 @@ impl Game { }) } + /// Guard placement for defending player + pub fn place_guards(&mut self, ui: &mut UserInterface) -> Result { + ui.place_guards(self)?; + Ok(MsgToServer { + new: self.positions[self.player], + guards: self.guards.clone(), + quit: self.quit, + }) + } + /// Tiles within guard's line-of-sight pub fn view_cone(&self, guard: usize) -> Vec<(Point, Tile)> { let mut cone = HashSet::new(); diff --git a/src/net.rs b/src/net.rs index 6f71660..65c9285 100644 --- a/src/net.rs +++ b/src/net.rs @@ -47,6 +47,13 @@ impl ClientThread { // Send initial game state serialize_into(&self.stream, &game)?; + if game.player == game.defender { + // If this is the defender we wait to receive + // their choice of guard positions + let msg = deserialize_from(&self.stream)?; + self.tx.send(msg)?; + } + loop { // Send latest info to client let msg = self.rx.recv()?; @@ -102,7 +109,7 @@ pub struct Server { } impl Server { - pub fn new(game: Game) -> Result { + pub fn new(mut game: Game) -> Result { info!( "Address: {}, players: {}", game.address, game.config.players @@ -116,6 +123,11 @@ impl Server { clients.push(ClientHandle::new(&listener, g)?); } + // Update guards' positions from defending player + let msg = clients[game.defender].rx.recv()?; + info!("Received guard positions from defender!"); + game.update(msg, game.defender); + Ok(Server { clients, game }) } @@ -160,17 +172,28 @@ pub struct Client { } impl Client { - pub fn new(address: &str, ui: UserInterface) -> Result { + pub fn new(address: &str, mut ui: UserInterface) -> Result { let stream = TcpStream::connect(address)?; - stream.set_read_timeout(Some(Duration::from_millis(100)))?; - let game = deserialize_from(&stream)?; + let mut game: Game = deserialize_from(&stream)?; info!("Connected to {}. Waiting for server...", address); + // Defender sets positions of their guards + if game.player == game.defender { + let msg = game.place_guards(&mut ui)?; + serialize_into(&stream, &msg)?; + } + + // Display splash screen + ui.splash()?; + Ok(Client { stream, game, ui }) } pub fn run(&mut self) -> Result<()> { + self.stream + .set_read_timeout(Some(Duration::from_millis(100)))?; let quit: Status; + let mut begun = false; loop { // Receive update from server if let Ok(msg) = deserialize_from::<&TcpStream, MsgToClient>(&self.stream) { @@ -179,14 +202,16 @@ impl Client { quit = msg.quit; break; } + begun = true; // Send back update if it's our turn if msg.turn { let msg = self.game.play(msg.defender, &mut self.ui)?; serialize_into(&self.stream, &msg)?; } - } else { - self.ui.idle()?; + } else if self.ui.idle(begun)? { + self.ui.reset(); + return Ok(()); } } self.ui.reset(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index dc22c43..b1ab3a6 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,7 @@ pub mod term; use crate::{Game, Point, Result, Status}; +use rand::{thread_rng, Rng}; use std::time::{Duration, Instant}; pub enum Key { @@ -70,7 +71,7 @@ impl UserInterface { } } - fn defender(&mut self, game: &mut Game, key: Key) -> isize { + fn defender(&mut self, game: &mut Game, key: Key, set: &mut bool) -> isize { match key { Key::Tab => loop { self.guard = (self.guard + 1) % game.guards.len(); @@ -90,6 +91,9 @@ impl UserInterface { '[' => game.rotate_guard(self.guard, false), ']' => game.rotate_guard(self.guard, true), '.' => (), + ' ' => { + *set = true; + } _ => return 0, }, } @@ -254,7 +258,8 @@ impl UserInterface { .input(Duration::from_millis(game.config.input_timeout))? { actions -= if defender { - self.defender(game, k) + let mut x = true; + self.defender(game, k, &mut x) } else { self.player(game, k) }; @@ -291,16 +296,88 @@ impl UserInterface { Ok(()) } + /// Event loop to for placing guard positions + pub fn place_guards(&mut self, game: &mut Game) -> Result<()> { + let mut remaining: usize = game.config.num_guards; + self.guard = 0; + let mut final_choice = vec![]; + + // Hide player positions + let players = game.positions.clone(); + game.positions = vec![None; game.config.players]; + + self.display(game, true)?; + while remaining > 0 { + self.message(&format!("{} guards remaining to place", remaining))?; + + if let Some(k) = self + .backend + .input(Duration::from_millis(game.config.input_timeout))? + { + let mut done = false; + let _ = self.defender(game, k, &mut done); + if done { + final_choice.push(game.guards[self.guard]); + game.guards[self.guard] = None; + remaining -= 1; + self.guard = game.guards.iter().position(|&x| x.is_some()).unwrap_or(0); + } + } else { + continue; + } + + self.display(game, true)?; + } + game.guards = final_choice; + game.positions = players; + + Ok(()) + } + + /// Splash screen + pub fn splash(&mut self) -> Result<()> { + const SPLASH: &str = "██ ██ █████ ███ ██ ███████ ██████ +██ ██ ██ ██ ████ ██ ███ ██ ██ +███████ ███████ ██ ██ ██ ███ ██ ██ +██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +██ ██ ██ ██ ██ ████ ███████ ██████ + +Version: 0.1.0 +"; + + self.backend.clear()?; + let mut p = (5, 5); + for line in SPLASH.lines() { + self.backend.draw(p, line, Colour::Red)?; + p.1 += 1; + } + self.backend.flush()?; + Ok(()) + } + /// De-initialise the user interface pub fn reset(&mut self) { self.backend.reset(); } /// Idle screen - pub fn idle(&mut self) -> Result<()> { + pub fn idle(&mut self, begun: bool) -> Result { // Consume accidental input - let _ = self.backend.input(Duration::from_millis(100))?; - self.message("Waiting for other players...") + if let Some(Key::Char('q')) = self.backend.input(Duration::from_millis(100))? { + return Ok(true); + } + if begun { + // Draw @s at random points on the screen + let mut rng = thread_rng(); + let size = self.backend.size(); + for _ in 0..(size.0 / 3) { + let x = rng.gen_range(0..size.0); + let y = rng.gen_range(0..(size.1 - 1)); + self.backend.draw((x, y), "@", Colour::Grey)?; + } + } + self.message("Waiting for other players...")?; + Ok(false) } } diff --git a/src/ui/term.rs b/src/ui/term.rs index 88d3e82..7aa1d14 100644 --- a/src/ui/term.rs +++ b/src/ui/term.rs @@ -116,5 +116,6 @@ impl UIBackend for Terminal { fn reset(&mut self) { execute!(self.stdout, MoveTo(0, self.size.1 - 1), Show).ok(); terminal::disable_raw_mode().ok(); + println!(); } }