From 517df189e71626025d33a442cebff592df2d5358 Mon Sep 17 00:00:00 2001 From: stale Date: Sat, 2 Jul 2022 05:49:10 -0300 Subject: a bunch of stuff iunno, v 1.2 --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- assets/index.html | 15 ++++++++------- src/conn.rs | 38 ++++++++++++++++---------------------- src/main.rs | 34 +++++++++++++++++++--------------- src/minesweeper.rs | 27 ++++++++++++++++++++++----- src/types.rs | 5 +++-- 7 files changed, 76 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40fa6fb..c7c9ab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,18 +874,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -1263,9 +1263,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -1423,7 +1423,7 @@ dependencies = [ [[package]] name = "websweeper" -version = "1.1.0" +version = "1.2.0" dependencies = [ "ammonia", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 3c68a1b..8d3942e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "websweeper" -version = "1.1.0" +version = "1.2.0" authors = ["stale "] edition = "2021" diff --git a/assets/index.html b/assets/index.html index c67e4d6..9729132 100644 --- a/assets/index.html +++ b/assets/index.html @@ -17,16 +17,17 @@

-
-
-
+
+
+
+
diff --git a/src/conn.rs b/src/conn.rs index 7adeeae..047d7db 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -4,19 +4,15 @@ use std::{ net::SocketAddr, }; use tokio::sync::RwLock; -use tokio::sync::mpsc as tokio_mpsc; -use futures::{SinkExt, StreamExt, TryStreamExt, stream::SplitStream}; +use futures::{SinkExt, TryStreamExt, StreamExt, stream::SplitStream}; use warp::ws::{ WebSocket, Message }; use crate::livepos; -const MAX_IN: usize = 2048; - -pub async fn lobby(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc>)) { +pub async fn setup_conn(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc>), max_in: usize) { let (room_id, room) = rinfo; let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - - // server <-> client comms let (mut outgoing, incoming) = socket.split(); + let conn = Conn { addr, tx }; println!("{room_id} I: Incoming TCP connection from: {}", addr); @@ -24,10 +20,10 @@ pub async fn lobby(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc= pcap + pl.len() >= pcap.get() }; if full { return } - let drive_game = handle_room((incoming,tx), addr, (room_id.clone(),room.clone())); + let drive_game = drive_conn((conn, incoming), (room_id.clone(),room.clone()), max_in); let send_to_client = { let room_id = room_id.clone(); async move { @@ -61,10 +57,9 @@ pub async fn lobby(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc,tokio_mpsc::UnboundedSender); -pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, Arc>)) { - let (mut incoming, tx) = streams; +pub async fn drive_conn(conn: (Conn, SplitStream), rinfo: (RoomId, Arc>), max_in: usize) { + let (conn, mut incoming) = conn; let (room_id, room) = rinfo; let (players, cmd_tx, pos_tx, room_conf) = { let room = room.read().await; @@ -74,7 +69,7 @@ pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, if let Some(cmd) = cmd { // if it ain't text we can't handle it let cmd = match cmd.to_str() { - Ok(cmd) => { if cmd.len() > MAX_IN { + Ok(cmd) => { if cmd.len() > max_in { println!("{room_id} E: string too big: {cmd}"); return } else { cmd.to_owned() } }, @@ -90,7 +85,7 @@ pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, if let Some(cmd_name) = fields.next() { use crate::minesweeper::{Move,MoveType}; let mut players_lock = players.write().await; - match players_lock.get_mut(&addr) { + match players_lock.get_mut(&conn.addr) { Some(me) => match cmd_name { "pos" => { if let Some(pos) = parse_pos(fields) { @@ -102,7 +97,7 @@ pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, "reveal" => { match parse_pos(fields) { Some(pos) => { - if let Err(e) = cmd_tx.send(MetaMove::Move(Move { t: MoveType::Reveal, pos }, addr)) { + if let Err(e) = cmd_tx.send(MetaMove::Move(Move { t: MoveType::Reveal, pos }, conn.addr)) { println!("{room_id} E: couldn't process {me}'s reveal command: {e}"); }; }, @@ -114,7 +109,7 @@ pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, "flag" => { match parse_pos(fields) { Some(pos) => { - if let Err(e) = cmd_tx.send(MetaMove::Move(Move { t: MoveType::ToggleFlag, pos }, addr)) { + if let Err(e) = cmd_tx.send(MetaMove::Move(Move { t: MoveType::ToggleFlag, pos }, conn.addr)) { println!("{room_id} E: couldn't process {me}'s flag command: {e}"); }; }, @@ -142,16 +137,15 @@ pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, if n.is_empty() { def } else { n } } }; - println!("{room_id} I: registered \"{name}@{addr}\""); + println!("{room_id} I: registered \"{name}@{}\"", conn.addr); drop(players_lock); let uid = { // new scope cuz paranoid bout deadlocks - let conn = Conn { addr, tx: tx.clone() }; - room.write().await.players.write().await.insert_conn(conn, name.clone(), clr) + room.write().await.players.write().await.insert_conn(conn.clone(), name.clone(), clr) }; let players_lock = players.read().await; - let me = players_lock.get(&addr).unwrap(); - tx.send(Message::text(format!("regack {} {} {} {}", + let me = players_lock.get(&conn.addr).unwrap(); + conn.tx.send(Message::text(format!("regack {} {} {} {}", room_conf.name.replace(' ', " "), name.replace(' ', " "), uid, room_conf.board_conf)) ).expect("couldn't send register ack"); @@ -176,7 +170,7 @@ pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, } } } else { - println!("{room_id} E: reached end of stream for {addr}"); + println!("{room_id} E: reached end of stream for {}", conn.addr); break; } } diff --git a/src/main.rs b/src/main.rs index eec9cf8..2321289 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,7 @@ struct ConfLimits { pub board_area: usize, pub room_slots: usize, pub form_size: u64, + pub inbound_packet_size: usize, } #[derive(Deserialize)] struct Conf { @@ -106,7 +107,6 @@ async fn tokio_main(conf: Conf) -> Result<(), Box> { post().and(path("r")).and(body::content_length_limit(conf.limits.form_size)).and(body::form()) .and_then(move |rinfo: HashMap| { - println!("{:?}", rinfo); let rooms = rooms.clone(); let pubs = pubs.clone(); let conf = conf.clone(); @@ -131,6 +131,7 @@ async fn tokio_main(conf: Conf) -> Result<(), Box> { if access { pubs.write().await.insert(uid.clone(), serde_json::to_string(&room.conf).unwrap()); } + println!("New room: {:?}", room.conf); rooms.insert(uid.clone(), Arc::new(RwLock::new(room))); Ok( @@ -150,6 +151,8 @@ async fn tokio_main(conf: Conf) -> Result<(), Box> { let rooms_ws = rooms.clone(); let rooms_lobby = rooms.clone(); let prefix = get().and(path!("room" / String / ..)); + let max_inbound_packet_size = conf.limits.inbound_packet_size; + let room_path = conf.paths.room_page.clone(); // Fixme: better errors prefix.and(path!("ws")) @@ -163,7 +166,7 @@ async fn tokio_main(conf: Conf) -> Result<(), Box> { Some(r) => { println!("{id} I: conn from {saddr:?}"); Ok(websocket.on_upgrade(move |socket| { - conn::lobby(socket, saddr.expect("socket without address"), (id,r)) + conn::setup_conn(socket, saddr.expect("socket without address"), (id,r), max_inbound_packet_size) })) }, None => { @@ -174,7 +177,7 @@ async fn tokio_main(conf: Conf) -> Result<(), Box> { } }) .or(prefix.and(path::end()) - .and(fs::file(conf.paths.room_page.clone())) + .and(fs::file(room_path)) .then(move |id: String, f: fs::File| { let rooms = rooms_lobby.clone(); async move { @@ -291,19 +294,20 @@ async fn empty_rooms(rooms: &RoomMap) -> Vec { } fn room_from_form(uid: RoomId, rinfo: &HashMap, conf: &Conf) -> Result<(types::Room, bool), Rejection> { - if let (Some(w),Some(h),Some(num),Some(denom),access,asfm,limit) = ( - rinfo.get("rwidth").and_then(|wt| wt.parse::().ok()), - rinfo.get("rheight").and_then(|ht| ht.parse::().ok()), - rinfo.get("rration").and_then(|nt| nt.parse::().ok()), - rinfo.get("rratiod").and_then(|dt| dt.parse::().ok()), - rinfo.get("raccess"), - rinfo.get("ralwayssafe1move"), - rinfo.get("rlimit").and_then(|l| l.parse::().ok()), + if let (Some(w),Some(h),Some(num),Some(denom),public,asfm,rborders,Some(limit)) = ( + rinfo.get("bwidth").and_then(|w| w.parse::().ok()), + rinfo.get("bheight").and_then(|h| h.parse::().ok()), + rinfo.get("mineratio-n").and_then(|n| n.parse::().ok()), + rinfo.get("mineratio-d").and_then(|d| d.parse::().ok()), + rinfo.get("public").map(|s| s == "on").unwrap_or(false), + rinfo.get("rborders").map(|s| s == "on").unwrap_or(false), + rinfo.get("allsafe1move").map(|s| s == "on").unwrap_or(false), + rinfo.get("limit").and_then(|l| l.parse::().ok()), ) { if w.get()*h.get() > conf.limits.board_area { return Err(warp::reject::custom(BoardTooBig)) } - let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm.is_some() }; + let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm, revealed_borders: rborders }; let name = { let n = rinfo.get("rname").unwrap().to_owned(); if n.is_empty() { uid.to_string() } else { n } @@ -319,8 +323,8 @@ fn room_from_form(uid: RoomId, rinfo: &HashMap, conf: &Conf) -> R let room_conf = RoomConf { name, - player_cap: match limit { Some(i) => i, None => usize::MAX }, - public: access.is_some(), + player_cap: limit, + public, board_conf, }; Ok((Room { @@ -330,7 +334,7 @@ fn room_from_form(uid: RoomId, rinfo: &HashMap, conf: &Conf) -> R cmd_stream: cmd_tx, livepos_driver: livepos_handle, pos_stream: pos_tx, - }, access.is_some())) + }, public)) } else { Err(warp::reject::custom(BadFormData)) } } diff --git a/src/minesweeper.rs b/src/minesweeper.rs index 9e362dc..616a6a0 100644 --- a/src/minesweeper.rs +++ b/src/minesweeper.rs @@ -38,6 +38,7 @@ pub struct BoardConf { /// mines/tiles, expressed as (numerator, denominator) pub mine_ratio: (usize,NonZeroUsize), pub always_safe_first_move: bool, + pub revealed_borders: bool, } impl std::fmt::Display for BoardConf { @@ -100,6 +101,7 @@ impl Game { if self.phase == Phase::FirstMoveFail { let winnable = self.board.mine_count < (self.board.width.get() * self.board.height.get()); if winnable { + self.board.hidden_tiles += 1; self.board.move_mine_elsewhere(m.pos); self.phase = Phase::Run; self = self.act(m); @@ -113,10 +115,12 @@ impl Game { } } impl Board { - pub fn new(conf: BoardConf) -> Self { + pub fn new(mut conf: BoardConf) -> Self { let (w,h) = (conf.w,conf.h); let area = w.get()*h.get(); - let mine_count = ((conf.mine_ratio.0 * area) / conf.mine_ratio.1.get()).clamp(0, area); + if w.get() < 3 || h.get() < 3 { conf.revealed_borders = false; } + let mined_area = area - if conf.revealed_borders { 2*(w.get()-1) + 2*(h.get()-1) } else { 0 }; + let mine_count = ((conf.mine_ratio.0 * mined_area) / conf.mine_ratio.1.get()).clamp(0, mined_area); let b = Board { data: [HIDDEN_BIT].repeat(area), width: w, @@ -124,14 +128,27 @@ impl Board { hidden_tiles: area, mine_count, }; - b.spread_mines(mine_count) + if conf.revealed_borders { + let (w,h) = (w.get(),h.get()); + let mut b = b.spread_mines(mine_count, true); + for x in 0..w { + b = b.reveal((x, 0)).0; + b = b.reveal((x, h-1)).0; + } + for y in 1..h-1 { + b = b.reveal(( 0, y)).0; + b = b.reveal((w-1, y)).0; + } + b + } else { b.spread_mines(mine_count, false) } } - pub fn spread_mines(mut self, mut count: usize) -> Self { + pub fn spread_mines(mut self, mut count: usize, without_edges: bool) -> Self { let mut rng = thread_rng(); let w = self.width.get(); let h = self.height.get(); + let (wr,hr) = if without_edges { ((1,w-1),(1,h-1)) } else { ((0,w),(0,h)) }; while count > 0 { - let randpos: (usize, usize) = (rng.sample(Uniform::new(0,w)), rng.sample(Uniform::new(0,h))); + let randpos: (usize, usize) = (rng.sample(Uniform::new(wr.0, wr.1)), rng.sample(Uniform::new(hr.0, hr.1))); let o = self.pos_to_off_unchecked(randpos); if self.data[o] == MINED { continue } else { diff --git a/src/types.rs b/src/types.rs index 0bd7881..002b5d7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,6 +7,7 @@ use std::{ }, fmt::Display, ops::{ Deref, DerefMut }, + num::NonZeroUsize, }; use warp::ws::Message; use tokio::sync::RwLock; @@ -17,7 +18,7 @@ use crate::livepos; #[derive(Debug, Serialize, Clone)] pub struct RoomConf { pub name: String, - pub player_cap: usize, + pub player_cap: NonZeroUsize, pub public: bool, pub board_conf: minesweeper::BoardConf, } @@ -38,7 +39,7 @@ pub enum MetaMove { Reset, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Conn { pub tx: tokio::sync::mpsc::UnboundedSender, pub addr: SocketAddr, -- cgit v1.2.3