diff options
author | stale <redkugelblitzin@gmail.com> | 2022-04-30 17:32:53 -0300 |
---|---|---|
committer | stale <redkugelblitzin@gmail.com> | 2022-04-30 17:32:53 -0300 |
commit | c9272f5580b00180738eb6dde623ea0abb37f7af (patch) | |
tree | 2659ae18c11ea3c08801bd0ad4aa6bd680aef9e9 | |
parent | 9987d7abf6acd647b4a3a00c20d15f716dce653c (diff) |
lazy sync
-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 } |