diff options
author | stale <redkugelblitzin@gmail.com> | 2022-07-02 05:49:10 -0300 |
---|---|---|
committer | stale <redkugelblitzin@gmail.com> | 2022-07-02 05:49:10 -0300 |
commit | 517df189e71626025d33a442cebff592df2d5358 (patch) | |
tree | bd24571f39d529fe0b9412346667ba2dd6af0a4e | |
parent | cc2eb4ef053da7a53663bdecac912a1c49490301 (diff) |
a bunch of stuff iunno, v 1.2
-rw-r--r-- | Cargo.lock | 14 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | assets/index.html | 15 | ||||
-rw-r--r-- | src/conn.rs | 38 | ||||
-rw-r--r-- | src/main.rs | 34 | ||||
-rw-r--r-- | src/minesweeper.rs | 27 | ||||
-rw-r--r-- | src/types.rs | 5 |
7 files changed, 76 insertions, 59 deletions
@@ -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", @@ -1,6 +1,6 @@ [package] name = "websweeper" -version = "1.1.0" +version = "1.2.0" authors = ["stale <stale@masba.net>"] 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 @@ <label>room name <input name="rname" type="text" autofocus></label><br> <label> board dimensions - <input name="rwidth" type="number" value="30" required> + <input name="bwidth" type="number" value="30" required> x - <input name="rheight" type="number" value="20" required><br> - where <input name="rration" type="number" value="1" required> - in every <input name="rratiod" type="number" value="8" required> + <input name="bheight" type="number" value="20" required><br> + where <input name="mineratio-n" type="number" value="1" required> + in every <input name="mineratio-d" type="number" value="8" required> tiles are mines </label><br> - <label>public, ie. shown in the lobby <input name="raccess" type="checkbox" checked></label><br> - <label>safe first move (if possible) <input name="ralwayssafe1move" type="checkbox" checked></label><br> - <label>player limit <input name="rlimit" type="number" value="32"></label><br> + <label>public, ie. shown in the lobby <input name="public" type="checkbox" checked></label><br> + <label>safe first move (if possible) <input name="allsafe1move" type="checkbox" checked></label><br> + <label>revealed borders <input name="rborders" type="checkbox"></label><br> + <label>player limit <input name="limit" type="number" value="32"></label><br> <button id="createbtn">create</button> </fieldset> </form> 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<RwLock<Room>>)) { +pub async fn setup_conn(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc<RwLock<Room>>), 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<RwLoc let rl = room.read().await; let pcap = rl.conf.player_cap; let pl = rl.players.read().await; - pl.len() >= 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<RwLoc } } -type RoomStreams = (SplitStream<WebSocket>,tokio_mpsc::UnboundedSender<Message>); -pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, Arc<RwLock<Room>>)) { - let (mut incoming, tx) = streams; +pub async fn drive_conn(conn: (Conn, SplitStream<WebSocket>), rinfo: (RoomId, Arc<RwLock<Room>>), 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<dyn Error>> { post().and(path("r")).and(body::content_length_limit(conf.limits.form_size)).and(body::form()) .and_then(move |rinfo: HashMap<String, String>| { - 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<dyn Error>> { 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<dyn Error>> { 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<dyn Error>> { 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<dyn Error>> { } }) .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<RoomId> { } fn room_from_form(uid: RoomId, rinfo: &HashMap<String,String>, 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::<NonZeroUsize>().ok()), - rinfo.get("rheight").and_then(|ht| ht.parse::<NonZeroUsize>().ok()), - rinfo.get("rration").and_then(|nt| nt.parse::<usize>().ok()), - rinfo.get("rratiod").and_then(|dt| dt.parse::<NonZeroUsize>().ok()), - rinfo.get("raccess"), - rinfo.get("ralwayssafe1move"), - rinfo.get("rlimit").and_then(|l| l.parse::<usize>().ok()), + if let (Some(w),Some(h),Some(num),Some(denom),public,asfm,rborders,Some(limit)) = ( + rinfo.get("bwidth").and_then(|w| w.parse::<NonZeroUsize>().ok()), + rinfo.get("bheight").and_then(|h| h.parse::<NonZeroUsize>().ok()), + rinfo.get("mineratio-n").and_then(|n| n.parse::<usize>().ok()), + rinfo.get("mineratio-d").and_then(|d| d.parse::<NonZeroUsize>().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::<NonZeroUsize>().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<String,String>, 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<String,String>, 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<Message>, pub addr: SocketAddr, |