diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 175 | ||||
-rw-r--r-- | src/minesweeper.rs | 18 |
2 files changed, 127 insertions, 66 deletions
diff --git a/src/main.rs b/src/main.rs index 8f556c3..d42ad9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ type PeerInfo = (PeerMap, Arc::<AtomicUsize>); #[derive(Debug)] enum MetaMove { - Move(Move), + Move(Move,SocketAddr), Dump, Reset, } @@ -77,26 +77,33 @@ async fn main() { // If a move is made, broadcast new board, else just send current board async fn gameloop(mut move_rx: mpsc::UnboundedReceiver<MetaMove>, peers: PeerMap) { let mut game = Game::new(Board::new(75,35), (75*35)/8); + let mut latest_player_name = None; while let Some(req) = move_rx.recv().await { - let mut done = game.phase == Phase::Die || game.phase == Phase::Win; - match req { - MetaMove::Move(m) => if !done { game = game.act(m)}, - MetaMove::Dump => (), - MetaMove::Reset => { game = Game::new(Board::new(75,35), (75*35)/8); done = false;}, - } - if !done { - let mut reply = vec![]; - match game.phase { - Phase::Win => { reply.push(Message::text("win")); game.board = game.board.grade(); }, - Phase::Die => { reply.push(Message::text("lose")); game.board = game.board.grade(); }, - _ => (), - } - reply.push(Message::binary(game.board.render())); - { - let peers = peers.read().await; - for (_, (tx, _, _, _)) in peers.iter() { - for r in reply.iter() { - tx.unbounded_send(r.clone()).unwrap(); + let done = game.phase == Phase::Die || game.phase == Phase::Win; + match req { + MetaMove::Move(m, o) => if !done { + game = game.act(m); + if game.phase == Phase::Win || game.phase == Phase::Die { + game.board = game.board.grade(); + } + latest_player_name = peers.read().await.get(&o).map(|(_,_,n,_)| n.clone()); + }, + MetaMove::Dump => (), + MetaMove::Reset => { game = Game::new(Board::new(75,35), (75*35)/8); }, + } + let mut reply = vec![Message::binary(game.board.render())]; + let lpname = latest_player_name.as_ref().map(|s| s.as_str()).unwrap_or("unknown player"); + match game.phase { + Phase::Win => { reply.push(Message::text(format!("win {lpname}"))); }, + Phase::Die => { reply.push(Message::text(format!("lose {lpname}"))); }, + _ => (), + } + { + let peers = peers.read().await; + for (addr, (tx, _, _, _)) in peers.iter() { + for r in reply.iter() { + if let Err(e) = tx.unbounded_send(r.clone()) { + println!("couldn't send game update {r} to {addr}: {e}"); } } } @@ -125,49 +132,111 @@ async fn peer_connection(peer_info: PeerInfo, cmd_tx: MovReqTx, raw_stream: TcpS let peer_map = peer_info.0; let peer_seqid = peer_info.1.fetch_add(1, atomic::Ordering::AcqRel); + let mut peer_name = "unknown".to_string(); - // Insert the write part of this peer to the peer map. let (tx, rx) = unbounded(); let (outgoing, mut incoming) = ws_stream.split(); let process_incoming = async { - while let Some(cmd) = incoming.try_next().await.unwrap() { - let cmd = cmd.to_text().unwrap(); - - let mut fields = cmd.split(" ").skip(1); - if cmd.starts_with("pos") { - let pos = (fields.next().unwrap().parse::<usize>().unwrap(), fields.next().unwrap().parse::<usize>().unwrap()); - let (name, id) = { + while let Ok(cmd) = incoming.try_next().await { + if let Some(cmd) = cmd { + if cmd.is_close() { + println!("closing \"{peer_name}\"@{addr}"); let mut peers = peer_map.write().await; - let mut entry = peers.get_mut(&addr).unwrap(); - entry.3 = pos.clone(); - (entry.2.clone(), entry.1) - }; - { - let peers = peer_map.read().await; - for peer_tx in peers.iter().filter(|(s, _)| **s != addr).map(|(_,(peer_tx,_,_,_))| peer_tx) { - peer_tx.unbounded_send(Message::text(format!("pos {} {} {} {}", id, name, pos.0, pos.1))).unwrap(); + if let Some(_) = peers.get(&addr) { + peers.remove(&addr); + } + for (paddr, (tx, _, pname, _)) in peers.iter() { + if let Err(e) = tx.unbounded_send(Message::text("logoff {peer_seqid} {peer_name}")) { + println!("couldn't deliver logoff info to \"{pname}\"@{paddr}: {e}"); + } } + break; } - } else if cmd.starts_with("reveal") { - println!("got {} from {}", cmd, addr); - let pos = (fields.next().unwrap().parse::<usize>().unwrap(), fields.next().unwrap().parse::<usize>().unwrap()); - cmd_tx.send(MetaMove::Move(Move { t: MoveType::Reveal, pos })).unwrap(); - } else if cmd.starts_with("flag") { - println!("got {} from {}", cmd, addr); - let pos = (fields.next().unwrap().parse::<usize>().unwrap(), fields.next().unwrap().parse::<usize>().unwrap()); - cmd_tx.send(MetaMove::Move(Move { t: MoveType::ToggleFlag, pos })).unwrap(); - } else if cmd.starts_with("reset") { - println!("got {} from {}", cmd, addr); - cmd_tx.send(MetaMove::Reset).unwrap(); - } else if cmd.starts_with("register") { - let name = fields.next().unwrap(); - { // new scope cuz paranoid bout deadlocks - peer_map.write().await.insert(addr, (tx.clone(), peer_seqid, name.to_string(), (0,0))); + // if it ain't text we can't handle it + if !cmd.is_text() { continue; } + let cmd = cmd.to_text().unwrap(); + + let mut fields = cmd.split(" "); + let parse_pos = |mut fields: std::str::Split<&str>| -> Option<(usize, usize)> { + let x = fields.next().and_then(|xstr| xstr.parse::<usize>().ok()); + let y = fields.next().and_then(|ystr| ystr.parse::<usize>().ok()); + x.zip(y) + }; + if let Some(cmd_name) = fields.next() { + match cmd_name { + "pos" => { + match parse_pos(fields) { + Some(pos) => { + let (name, id) = { + let mut peers = peer_map.write().await; + let mut entry = peers.get_mut(&addr).unwrap(); + entry.3 = pos.clone(); + (entry.2.clone(), entry.1) + }; + let sanitized_name = name.replace(" ", " ").to_string(); + { + let peers = peer_map.read().await; + for peer_tx in peers.iter().filter(|(s, _)| **s != addr).map(|(_,(peer_tx,_,_,_))| peer_tx) { + let r = peer_tx.unbounded_send(Message::text(format!("pos {id} {sanitized_name} {} {}", pos.0, pos.1))); + if let Err(e) = r { + println!("error sending pos update: {e}"); + } + } + } + }, + None => { + println!("bad position update from \"{peer_name}@{addr}\""); + }, + } + }, + "reveal" => { + match parse_pos(fields) { + Some(pos) => { + println!("{cmd} from \"{peer_name}\"@{addr}"); + cmd_tx.send(MetaMove::Move(Move { t: MoveType::Reveal, pos }, addr)).unwrap(); + }, + None => { + println!("bad reveal from \"{peer_name}\"@{addr}"); + } + } + }, + "flag" => { + match parse_pos(fields) { + Some(pos) => { + println!("{cmd} from \"{peer_name}\"@{addr}"); + cmd_tx.send(MetaMove::Move(Move { t: MoveType::ToggleFlag, pos }, addr)).unwrap(); + }, + None => { + println!("bad flag from \"{peer_name}\"@{addr}"); + } + } + }, + "reset" => { + println!("{cmd} from \"{peer_name}\"@{addr}"); + if let Err(e) = cmd_tx.send(MetaMove::Reset) { + println!("couldn't send game dump to \"{peer_name}\"@{addr}: {e}"); + } + }, + "register" => { + let name = fields.collect::<Vec<&str>>().join(&" "); + if name.is_empty() { + peer_name = "anon".to_string(); + } else { + peer_name = name; + } + { // new scope cuz paranoid bout deadlocks + peer_map.write().await.insert(addr, (tx.clone(), peer_seqid, peer_name.clone(), (0,0))); + } + tx.unbounded_send(Message::text(format!("id {}", peer_seqid))).unwrap(); + if let Err(e) = cmd_tx.send(MetaMove::Dump) { + println!("couldn't send game dump to \"{peer_name}\"@{addr}: {e}"); + } + }, + e => println!("unknown command {e:?} from {peer_name}@{addr}, \"{cmd}\""), + } } - tx.unbounded_send(Message::text(format!("id {}", peer_seqid))).unwrap(); - cmd_tx.send(MetaMove::Dump).unwrap(); } } }; diff --git a/src/minesweeper.rs b/src/minesweeper.rs index a56758b..8a0ee8d 100644 --- a/src/minesweeper.rs +++ b/src/minesweeper.rs @@ -172,28 +172,20 @@ impl Board { } pub fn render(&self) -> Vec<u8> { - const CYAN: &[u8] = &[27,b'[',b'3',b'6',b'm']; - const YELLOW: &[u8] = &[27,b'[',b'3',b'3',b'm']; - const GREEN: &[u8] = &[27,b'[',b'3',b'2',b'm']; - const RED: &[u8] = &[27,b'[',b'3',b'1',b'm']; - const FG: &[u8] = &[27,b'[',b'3',b'9',b'm']; - let mut ret = vec![27]; - let mut cur_clr = FG; for y in 0..self.height { for x in 0..self.width { let c = &self.data[self.pos_to_off((x,y))]; match *c { 0 => ret.extend_from_slice(b" "), - _ if *c <= 8 => { if cur_clr != FG { ret.extend_from_slice(FG); cur_clr = FG; } ret.push(b'0' + c); }, - _ if (*c & CORRECT_BIT) > 0 => { if cur_clr != GREEN { ret.extend_from_slice(GREEN); cur_clr = GREEN; } ret.push(b'F') }, - _ if (*c & FLAGGED_BIT) > 0 => { if cur_clr != YELLOW { ret.extend_from_slice(YELLOW); cur_clr = YELLOW; } ret.push(b'F'); }, - _ if (*c & HIDDEN_BIT) > 0 => { if cur_clr != CYAN { ret.extend_from_slice(CYAN); cur_clr = CYAN; } ret.push(35); }, - _ if *c == MINE_VAL => { if cur_clr != RED { ret.extend_from_slice(RED); cur_clr = RED; } ret.push(b'O'); }, + _ if *c <= 8 => ret.push(b'0' + c), + _ if (*c & CORRECT_BIT) > 0 => ret.push(b'C'), + _ if (*c & FLAGGED_BIT) > 0 => ret.push(b'F'), + _ if (*c & HIDDEN_BIT) > 0 => ret.push(b'#'), + _ if *c == MINE_VAL => ret.push(b'O'), _ => ret.push(b'?'), } } - ret.extend_from_slice(FG); cur_clr = FG; ret.extend_from_slice(b"<br>"); } ret |