From 517df189e71626025d33a442cebff592df2d5358 Mon Sep 17 00:00:00 2001
From: stale <redkugelblitzin@gmail.com>
Date: Sat, 2 Jul 2022 05:49:10 -0300
Subject: a bunch of stuff iunno, v 1.2

---
 src/conn.rs        | 38 ++++++++++++++++----------------------
 src/main.rs        | 34 +++++++++++++++++++---------------
 src/minesweeper.rs | 27 ++++++++++++++++++++++-----
 src/types.rs       |  5 +++--
 4 files changed, 60 insertions(+), 44 deletions(-)

(limited to 'src')

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(' ', "&nbsp;"), name.replace(' ', "&nbsp;"), 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,
-- 
cgit v1.2.3