summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstale <redkugelblitzin@gmail.com>2022-04-30 17:32:53 -0300
committerstale <redkugelblitzin@gmail.com>2022-04-30 17:32:53 -0300
commitc9272f5580b00180738eb6dde623ea0abb37f7af (patch)
tree2659ae18c11ea3c08801bd0ad4aa6bd680aef9e9
parent9987d7abf6acd647b4a3a00c20d15f716dce653c (diff)
lazy sync
-rwxr-xr-xdeploy.sh6
-rw-r--r--page.html86
-rwxr-xr-xrun.sh4
-rw-r--r--src/main.rs74
-rw-r--r--src/minesweeper.rs19
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/
diff --git a/page.html b/page.html
index 75bb7fd..2b7590d 100644
--- a/page.html
+++ b/page.html
@@ -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>
diff --git a/run.sh b/run.sh
deleted file mode 100755
index 4547312..0000000
--- a/run.sh
+++ /dev/null
@@ -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"&nbsp"),
_ 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
}