summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock64
-rwxr-xr-xdeploy.sh4
-rw-r--r--page.html55
-rw-r--r--src/main.rs175
-rw-r--r--src/minesweeper.rs18
5 files changed, 197 insertions, 119 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d4df158..cd78441 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/deploy.sh b/deploy.sh
index e4244ea..6edc1e8 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -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/
diff --git a/page.html b/page.html
index 2b7590d..52cfb77 100644
--- a/page.html
+++ b/page.html
@@ -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(" ", "&nbsp").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"&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'); },
- _ 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