diff options
| -rwxr-xr-x | deploy.sh | 6 | ||||
| -rw-r--r-- | page.html | 86 | ||||
| -rwxr-xr-x | run.sh | 4 | ||||
| -rw-r--r-- | src/main.rs | 74 | ||||
| -rw-r--r-- | src/minesweeper.rs | 19 | 
5 files changed, 137 insertions, 52 deletions
| diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..e4244ea --- /dev/null +++ b/deploy.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +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/ @@ -19,7 +19,6 @@        background-color: black;        color: white;      } -      .unsel {      -webkit-touch-callout: none;      -webkit-user-select: none; @@ -28,9 +27,11 @@      -ms-user-select: none;      user-select: none;      } - -    .cursor * { +    .cursor {        font-size: 16pt; +      pointer-events: none; +    } +    .cursor * {        margin: 0 0;      }    </style> @@ -42,26 +43,37 @@        <p id="miscinfo">Loading...</p>      </div>    </body> -  <script src="https://p.masba.net:8443/ansispan.js"></script> +  <script src="https://masba.net/ansispan.js"></script>    <script> -    let id = NaN; +    window.id = NaN;      let s = new WebSocket("ws://127.0.0.1:31236");      let info_elem = document.getElementById("miscinfo");      let board_elem = document.getElementById("board"); +    let name = "deadbeef";      let last_packet = {};      let cursors = new Map();      let board = {}; -    s.onopen = function(e) { info_elem.innerHTML = "Connected"; } +    let board_bounds = {}; // init when we actually have the board loaded +    function register() { +          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 = +            `<input id="name-in" type="text"> +   <button onclick=register()>Join</button>`; +        }      s.onmessage = function(e) { -          console.log(`got msg ${e.data}`);            last_packet = e;            let d = e.data;            if (typeof d == "object") { -            d.arrayBuffer().then(acceptPacket); +            d.arrayBuffer().then(acceptBoard);            } else if (typeof e.data == "string") {              let fields = d.split(" ");              if (d.startsWith("pos")) { -              let oid = fields[1]; +              let oid = Number(fields[1]);                let name = fields[2];                let x = fields[3];                let y = fields[4]; @@ -73,21 +85,31 @@                celem.style.top = y + 'px';              }              else if (d.startsWith("id")) { -              id = fields[1]; -              createCursor(id, "You"); +              window.id = Number(fields[1]); +              createCursor(id, name); +            } +            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"; }; +            } +            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"; };              }            }        }      s.onerror = function(e) { info_elem.innerHTML += `<br>error ${e}`; }      s.onclose = function(e) { info_elem.innerHTML = "Closed"; } -    function acceptPacket(data) { +    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 = ansispan(board); +                board_elem.innerHTML = "<span>" + ansispan(board) + "</span>";                }            else if (vals[0] == 'p'.charCodeAt(0)) {                  // starts with a p, is a cursor position update @@ -110,14 +132,52 @@            let dx = tip.width/2 - cb.width/2;            let dy = -(tip.height/2 - cb.height/2);            document.getElementById('board-container').append(cursor); +          if (id == window.id) {            document.addEventListener('mousemove', e => {                  cursor.style.left = (e.pageX + dx) + 'px';                  cursor.style.top = (e.pageY + dy) + 'px';                  s.send(`pos ${e.pageX} ${e.pageY}`);                },                  false); +          }            cursors.set(id, {name: name, elem: cursor});            return cursor;          } +    function getBoardBounds() { +          let boardb = board_elem.getBoundingClientRect(); +          let textb = board_elem.firstChild.getBoundingClientRect(); +          return { +                ox: boardb.x, +                oy: boardb.y, +                w: textb.width, +                h: boardb.height +              }; +        } + +    board_elem.onclick = function(e) { +          let tpos = tilepos(e.clientX, e.clientY); +          let cmd = `reveal ${tpos.x} ${tpos.y}`; +          console.log(cmd); +          s.send(cmd); +        } +    board_elem.oncontextmenu = function(e) { +          let tpos = tilepos(e.clientX, e.clientY); +          let cmd = `flag ${tpos.x} ${tpos.y}`; +          console.log(cmd); +          s.send(cmd); +          return false; +        } +    function tilepos(x,y) { +           board_bounds = getBoardBounds(); +          let b = board_bounds; +          let relx = (x - b.ox); +          let rely = (y - b.oy); +          let tilex = Math.floor(75 * relx/b.w); +          let tiley = Math.floor(35 * rely/b.h); +          return { x: tilex, y: tiley }; +        } +    function sleep(ms) { +      return new Promise(resolve => setTimeout(resolve, ms)); +    }    </script>  </html> @@ -1,4 +0,0 @@ -#!/bin/sh -stty raw -echo -echoe -echok -cargo run -stty sane diff --git a/src/main.rs b/src/main.rs index 28a10ed..8f556c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,12 +26,16 @@ use tokio::fs;  use tokio_tungstenite::tungstenite::protocol::Message;  type Tx = UnboundedSender<Message>; -type MovReqTx = mpsc::UnboundedSender<BoardRequest>; +type MovReqTx = mpsc::UnboundedSender<MetaMove>;  type PeerMap = Arc<RwLock<HashMap<SocketAddr, (Tx, usize, String, (usize, usize))>>>;  type PeerInfo = (PeerMap, Arc::<AtomicUsize>); -// Which will either perform a move, or simply request the current board -type BoardRequest = Option<Move>; +#[derive(Debug)] +enum MetaMove { +    Move(Move), +    Dump, +    Reset, +}  const PAGE_RELPATH: &str = "./page.html";  const FONT_FILE_FUCKIT: &[u8] = include_bytes!("./VT323-Regular.ttf"); @@ -71,18 +75,30 @@ async fn main() {  }  // If a move is made, broadcast new board, else just send current board -async fn gameloop(mut move_rx: mpsc::UnboundedReceiver<BoardRequest>, peers: PeerMap) { -    let mut game = Game::new(Board::new(30,10), (30*10)/8); +async fn gameloop(mut move_rx: mpsc::UnboundedReceiver<MetaMove>, peers: PeerMap) { +    let mut game = Game::new(Board::new(75,35), (75*35)/8);      while let Some(req) = move_rx.recv().await { -        let m = req; -        if let Some(m) = m { -            game = game.act(m); -        } -        let reply = Message::binary(game.board.render(None)); -        { -            let peers = peers.read().await; -            for (_, (tx, _, _, _)) in peers.iter() { -                tx.unbounded_send(reply.clone()).unwrap(); +        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(); +                    } +                }              }          }      } @@ -112,25 +128,15 @@ async fn peer_connection(peer_info: PeerInfo, cmd_tx: MovReqTx, raw_stream: TcpS      // Insert the write part of this peer to the peer map.      let (tx, rx) = unbounded(); -    peer_map.write().await.insert(addr, (tx.clone(), peer_seqid, "Dummy".to_string(), (0,0)));      let (outgoing, mut incoming) = ws_stream.split(); -    { // 1 - Inform the new player its id -        tx.unbounded_send(Message::text(format!("id {}", peer_seqid))).unwrap(); -    } - -    { // 2 - Queue up a current board broadcast -        cmd_tx.send(None).unwrap(); -    } -      let process_incoming = async {          while let Some(cmd) = incoming.try_next().await.unwrap() {              let cmd = cmd.to_text().unwrap(); -            println!("Received a message from {}: {}", addr, cmd); +            let mut fields = cmd.split(" ").skip(1);              if cmd.starts_with("pos") { -                let mut fields = cmd.split(" ").skip(1);                  let pos = (fields.next().unwrap().parse::<usize>().unwrap(), fields.next().unwrap().parse::<usize>().unwrap());                  let (name, id) = {                      let mut peers = peer_map.write().await; @@ -144,6 +150,24 @@ async fn peer_connection(peer_info: PeerInfo, cmd_tx: MovReqTx, raw_stream: TcpS                          peer_tx.unbounded_send(Message::text(format!("pos {} {} {} {}", id, name, pos.0, pos.1))).unwrap();                      }                  } +            } 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))); +                } +                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 8c0dc77..a56758b 100644 --- a/src/minesweeper.rs +++ b/src/minesweeper.rs @@ -148,11 +148,14 @@ impl Board {          }      }      pub fn reveal(mut self, pos: (usize, usize)) -> MoveResult { -        if pos.0 > self.width - 1 || pos.1 > self.height - 1 { panic!("OOB reveal"); } +        if pos.0 > self.width - 1 || pos.1 > self.height - 1 { +            println!("attempted OOB reveal @ {:?}", pos); +            return MoveResult { 0: self, 1: false }; +        }          let off = self.pos_to_off(pos);          self.flood_reveal(pos);          let c = self.data[off]; -        MoveResult { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL } // Kaboom +        MoveResult { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL }      }      pub fn grade(mut self) -> Board {          for i in &mut self.data { @@ -168,25 +171,20 @@ impl Board {          MoveResult { 0: self, 1: false }      } -    pub fn render(&self, cursor: Option<(usize,usize)>) -> Vec<u8> { +    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']; -        const ULINE: &[u8] = &[27,b'[',b'4',b'm']; -        const REGULAR: &[u8] = &[27,b'[',b'0',b'm'];          let mut ret = vec![27];          let mut cur_clr = FG;          for y in 0..self.height { -            ret.extend_from_slice(b"<br>");              for x in 0..self.width {                  let c = &self.data[self.pos_to_off((x,y))]; -                let is_cursor = if let Some(cursor) = cursor { cursor.0 == x && cursor.1 == y } else { false }; -                if is_cursor { ret.extend_from_slice(ULINE); }                  match *c { -                    0 => ret.push(b' '), +                    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'); }, @@ -194,8 +192,9 @@ impl Board {                      _ if *c == MINE_VAL => { if cur_clr != RED { ret.extend_from_slice(RED); cur_clr = RED; } ret.push(b'O'); },                      _ => ret.push(b'?'),                  } -                if is_cursor { ret.extend_from_slice(REGULAR); ret.extend_from_slice(cur_clr); }              } +            ret.extend_from_slice(FG); cur_clr = FG; +            ret.extend_from_slice(b"<br>");          }          ret      } | 
