From 0c0491ad17a0adc1d63c183b934df683e5e98b3f Mon Sep 17 00:00:00 2001
From: stale <redkugelblitzin@gmail.com>
Date: Sun, 3 Jul 2022 05:34:07 -0300
Subject: hooray for untested code

---
 Cargo.toml   |  2 +-
 src/conn.rs  |  2 +-
 src/main.rs  | 69 ++++++++++++++++++++++++++++++++++++------------------------
 src/types.rs |  3 ++-
 4 files changed, 45 insertions(+), 31 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 8d3942e..1693248 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "websweeper"
-version = "1.2.0"
+version = "1.3.0"
 authors = ["stale <stale@masba.net>"]
 edition = "2021"
 
diff --git a/src/conn.rs b/src/conn.rs
index 89fd5e0..a0ba899 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -165,7 +165,7 @@ pub async fn drive_conn(conn: (Conn, SplitStream<WebSocket>), rinfo: (RoomId, Ar
                             if let Err(e) = pos_tx.send(livepos::Req { id: uid, data: livepos::ReqData::StateDump }) {
                                 println!("{room_id} E: couldn't request position dump for {me}: {e}");
                             }
-                            if let Err(e) = cmd_tx.send(MetaMove::Dump) {
+                            if let Err(e) = cmd_tx.send(MetaMove::StateDump) {
                                 println!("{room_id} E: couldn't request game dump for {me}: {e}");
                             }
                         }
diff --git a/src/main.rs b/src/main.rs
index e4ccc56..ce1cd45 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -214,46 +214,59 @@ async fn tokio_main(conf: Conf) -> Result<(), Box<dyn Error>> {
 }
 
 // If a move is made, broadcast new board, else just send current board
-async fn gameloop(mut move_rx: tokio::sync::mpsc::UnboundedReceiver<MetaMove>, players: Arc<RwLock<PlayerMap>>, bconf: minesweeper::BoardConf) {
+type MoveStreamHandles = (tokio::sync::mpsc::UnboundedSender<MetaMove>, tokio::sync::mpsc::UnboundedReceiver<MetaMove>);
+async fn gameloop(moves: MoveStreamHandles, players: Arc<RwLock<PlayerMap>>, bconf: minesweeper::BoardConf) {
     // FIXME: push new board if and only if there aren't any remaining commands in the queue
     use minesweeper::*;
     use flate2::{ Compression, write::DeflateEncoder };
     use std::io::Write;
+    let (move_tx, mut move_rx) = moves;
     let mut game = Game::new(bconf);
-    let mut latest_player_name = None;
+    let mut final_player_name = None;
+    let mut desynced = true;
     while let Some(req) = move_rx.recv().await {
-        let done = game.phase == Phase::Die || game.phase == Phase::Win;
+        let done = |p: &Phase| { *p == Phase::Die || *p == Phase::Win };
         match req {
-            MetaMove::Move(m, o) => if !done {
+            MetaMove::Move(m, o) => if !done(&game.phase) {
                 game = game.act(m);
-                if game.phase == Phase::Win || game.phase == Phase::Die {
+                desynced = true;
+                if done(&game.phase) {
                     game.board = game.board.grade();
+                    final_player_name = players.read().await.get(&o).map(|p| p.name.clone());
                 }
-                latest_player_name = players.read().await.get(&o).map(|p| p.name.clone());
+                move_tx.send(MetaMove::StateSync).unwrap();
             },
-            MetaMove::Dump => (),
-            MetaMove::Reset => { game = Game::new(bconf); },
-        }
-        use warp::ws::Message;
-        let mut board_encoder = DeflateEncoder::new(Vec::new(), Compression::default());
-        board_encoder.write_all(&game.board.render()).unwrap();
-        let compressed_board = board_encoder.finish().unwrap();
-        let mut reply = vec![Message::binary(compressed_board)];
-        let lpname = latest_player_name.as_deref().unwrap_or("unknown player").replace(' ', "&nbsp");
-        match game.phase {
-            Phase::Win => { reply.push(Message::text(format!("win {lpname}"))); },
-            Phase::Die => { reply.push(Message::text(format!("lose {lpname}"))); },
-            _ => (),
-        }
-        {
-            let peers = players.read().await;
-            for (addr, p) in peers.iter() {
-                for r in reply.iter() {
-                    if let Err(e) = p.conn.tx.send(r.clone()) {
-                        println!("couldn't send game update {r:?} to {addr}: {e}");
+            MetaMove::StateSync => { // a StateDump, but consecutive ones in the queue get merged
+                if desynced { move_tx.send(MetaMove::StateDump).unwrap(); desynced = false; }
+            },
+            MetaMove::StateDump => {
+                use warp::ws::Message;
+                let mut board_encoder = DeflateEncoder::new(Vec::new(), Compression::default());
+                board_encoder.write_all(&game.board.render()).unwrap();
+                let compressed_board = board_encoder.finish().unwrap();
+                let mut reply = vec![Message::binary(compressed_board)];
+                let lpname = final_player_name.as_deref().unwrap_or("unknown player").replace(' ', "&nbsp");
+                match game.phase {
+                    Phase::Win => { reply.push(Message::text(format!("win {lpname}"))); },
+                    Phase::Die => { reply.push(Message::text(format!("lose {lpname}"))); },
+                    _ => (),
+                }
+                let peers = players.read().await;
+                for (addr, p) in peers.iter() {
+                    for r in reply.iter() {
+                        if let Err(e) = p.conn.tx.send(r.clone()) {
+                            println!("couldn't send game update {r:?} to {addr}: {e}");
+                        }
                     }
                 }
-            }
+                desynced = false;
+            },
+            MetaMove::Reset => {
+                if done(&game.phase) {
+                    game = Game::new(bconf);
+                    move_tx.send(MetaMove::StateDump).unwrap();
+                }
+            },
         }
     }
 }
@@ -318,7 +331,7 @@ fn room_from_form(uid: RoomId, rinfo: &HashMap<String,String>, conf: &Conf) -> R
         let players = Arc::new(RwLock::new(PlayerMap::default()));
 
         let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
-        let game_handle = tokio::spawn(gameloop(cmd_rx, players.clone(), board_conf));
+        let game_handle = tokio::spawn(gameloop((cmd_tx.clone(), cmd_rx), players.clone(), board_conf));
 
         let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel();
         let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx));
diff --git a/src/types.rs b/src/types.rs
index 002b5d7..a8a7742 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -35,7 +35,8 @@ pub struct Room {
 #[derive(Debug)]
 pub enum MetaMove {
     Move(minesweeper::Move,SocketAddr),
-    Dump,
+    StateDump,
+    StateSync,
     Reset,
 }
 
-- 
cgit v1.2.3