diff options
| -rw-r--r-- | Cargo.lock | 64 | ||||
| -rwxr-xr-x | deploy.sh | 4 | ||||
| -rw-r--r-- | page.html | 55 | ||||
| -rw-r--r-- | src/main.rs | 175 | ||||
| -rw-r--r-- | src/minesweeper.rs | 18 | 
5 files changed, 197 insertions, 119 deletions
| @@ -210,9 +210,9 @@ dependencies = [  [[package]]  name = "http" -version = "0.2.6" +version = "0.2.7"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"  dependencies = [   "bytes",   "fnv", @@ -301,9 +301,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"  [[package]]  name = "libc" -version = "0.2.124" +version = "0.2.125"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"  [[package]]  name = "lock_api" @@ -317,9 +317,9 @@ dependencies = [  [[package]]  name = "log" -version = "0.4.16" +version = "0.4.17"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"  dependencies = [   "cfg-if",  ] @@ -332,9 +332,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"  [[package]]  name = "memchr" -version = "2.4.1" +version = "2.5.0"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"  [[package]]  name = "mio" @@ -396,9 +396,9 @@ dependencies = [  [[package]]  name = "parking_lot_core" -version = "0.9.2" +version = "0.9.3"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"  dependencies = [   "cfg-if",   "libc", @@ -538,9 +538,9 @@ dependencies = [  [[package]]  name = "syn" -version = "1.0.91" +version = "1.0.92"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"  dependencies = [   "proc-macro2",   "quote", @@ -549,18 +549,18 @@ dependencies = [  [[package]]  name = "thiserror" -version = "1.0.30" +version = "1.0.31"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"  dependencies = [   "thiserror-impl",  ]  [[package]]  name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"  dependencies = [   "proc-macro2",   "quote", @@ -584,9 +584,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"  [[package]]  name = "tokio" -version = "1.17.0" +version = "1.18.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc"  dependencies = [   "bytes",   "libc", @@ -725,9 +725,9 @@ dependencies = [  [[package]]  name = "unicode-xid" -version = "0.2.2" +version = "0.2.3"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"  [[package]]  name = "url" @@ -812,9 +812,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"  [[package]]  name = "windows-sys" -version = "0.34.0" +version = "0.36.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"  dependencies = [   "windows_aarch64_msvc",   "windows_i686_gnu", @@ -825,30 +825,30 @@ dependencies = [  [[package]]  name = "windows_aarch64_msvc" -version = "0.34.0" +version = "0.36.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"  [[package]]  name = "windows_i686_gnu" -version = "0.34.0" +version = "0.36.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"  [[package]]  name = "windows_i686_msvc" -version = "0.34.0" +version = "0.36.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"  [[package]]  name = "windows_x86_64_gnu" -version = "0.34.0" +version = "0.36.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"  [[package]]  name = "windows_x86_64_msvc" -version = "0.34.0" +version = "0.36.1"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" @@ -2,5 +2,5 @@  cargo build --release --target x86_64-unknown-linux-musl && \    strip target/x86_64-unknown-linux-musl/release/websweeper && \ -  rsync --progress page.html root@masba.net:/srv/www/tooling/ -  rsync --progress target/x86_64-unknown-linux-musl/release/websweeper root@masba.net:/srv/www/tooling/ +  rsync --progress page.html root@masba.net:/srv/tooling/ +  rsync --progress target/x86_64-unknown-linux-musl/release/websweeper root@masba.net:/srv/tooling/ @@ -43,10 +43,9 @@        <p id="miscinfo">Loading...</p>      </div>    </body> -  <script src="https://masba.net/ansispan.js"></script>    <script>      window.id = NaN; -    let s = new WebSocket("ws://127.0.0.1:31236"); +    let s = new WebSocket(`ws://${window.location.hostname}:31236`);      let info_elem = document.getElementById("miscinfo");      let board_elem = document.getElementById("board");      let name = "deadbeef"; @@ -58,7 +57,6 @@            let name_in = document.getElementById("name-in");            name = name_in.value;            s.send(`register ${name}`); -          info_elem.innerHTML = "Running";          }      s.onopen = function(e) {            info_elem.innerHTML = @@ -70,6 +68,8 @@            let d = e.data;            if (typeof d == "object") {              d.arrayBuffer().then(acceptBoard); +            info_elem.onclick = undefined; +            info_elem.innerHTML = "Running";            } else if (typeof e.data == "string") {              let fields = d.split(" ");              if (d.startsWith("pos")) { @@ -90,13 +90,17 @@              }              else if (d.startsWith("win")) {                info_elem.innerHTML = "<p>You win! Reset?</p>"; -              info_elem.onclick = e => { s.send("reset"); info_elem.onclick = undefined; -                    info_elem.innerHTML = "Running"; }; +              info_elem.onclick = e => { s.send("reset") };              }              else if (d.startsWith("lose")) { -              info_elem.innerHTML = "<p>You lose... Reset?</p>"; -              info_elem.onclick = e => { s.send("reset"); info_elem.onclick = undefined; -                    info_elem.innerHTML = "Running"; }; +              let badone = fields[1]; +                  info_elem.innerHTML = `<p>You lost, ${badone} was blown up. Reset?</p>`; +              info_elem.onclick = e => { s.send("reset") }; +            } +            else if (d.startsWith("logoff")) { +              let oid = fields[1]; +              cursors.get(oid).elem.remove(); +              cursors.delete(oid);              }            }        } @@ -105,18 +109,31 @@      function acceptBoard(data) {            let vals = new Uint8Array(data); -          if (vals[0] == 27) { -                // starts with escape char, is a board dump -                board = Array.from(vals.subarray(1,vals.length).values()).reduce((s,c) => s + -                      String.fromCodePoint(c), ""); -                board_elem.innerHTML = "<span>" + ansispan(board) + "</span>"; -              } -          else if (vals[0] == 'p'.charCodeAt(0)) { -                // starts with a p, is a cursor position update -                // unimplemented! +          board = Array.from(vals).reduce((s,c) => s + String.fromCodePoint(c), ""); +          let last = board[0]; +          let last_idx = 0; +          let split_board = []; +          for (let i = 1; i < board.length+1; i++) { +            let cur = board[i]; +            let gamechars = /^[CFO# 1-8]+$/; +            if ((cur != last && gamechars.test(cur)) || cur == undefined) { +              let txt = board.substr(last_idx, i-last_idx); +              if (txt[0] == 'O') { +                txt = `<span style="color:red;">${txt}</span>`; +              } else if (txt[0] == 'C') { +                    txt = `<span style="color:green;">${txt}</span>`; +              } else if (txt[0] == 'F') { +                    txt = `<span style="color:yellow;">${txt}</span>`; +              } else { +                    txt = `<span style="color:white;">${txt}</span>`;                } - -        } +              split_board.push(txt); +              last_idx = i; +            } +            last = board[i]; +          } +          board_elem.innerHTML = "<span>" + split_board.join("") + "</span>"; +    }      function createCursor(id, name) {            // shit doesn't line up 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 | 
