use std::{ collections::HashMap, error::Error, net::SocketAddr, }; use warp::Filter; mod types; mod conn; mod minesweeper; use types::*; use tokio::sync::RwLock; const FONT_FILE: &[u8] = include_bytes!("./VT323-Regular.ttf"); fn main() -> Result<(), Box> { let conf = Config { cert_path: "./cert.pem".to_owned(), pkey_path: "./cert.rsa".to_owned(), page_path: "./page.html".to_owned(), socket_addr: ([0,0,0,0],31235).into(), }; let state = State { conf, peers: PeerMap::new(RwLock::new(HashMap::new())), }; tokio_main(state) } #[tokio::main] async fn tokio_main(state: State) -> Result<(), Box> { // Start the temporary single lobby let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); let _game_t = tokio::spawn(gameloop(cmd_rx, state.peers.clone())); let cmd_filter = warp::any().map(move || cmd_tx.clone()); let page_route = { use warp::*; get() .and(path("font.ttf")) .map(|| FONT_FILE) .or(fs::file(state.conf.page_path.clone())) }; let websocket_route = { let state = state.clone(); use warp::*; path("ws") .and(ws()) .and(addr::remote()) .and(cmd_filter) .map(move |ws: warp::ws::Ws, saddr: Option, cmd_tx: CmdTx| { let state = state.clone(); println!("conn from {saddr:?}"); ws.on_upgrade(move |socket| { let conn_data = types::ConnData { remote_addr: saddr.unwrap(), cmd_tx: cmd_tx.clone(), peers: state.peers.clone() }; conn::peer_connection(socket, conn_data) }) }) }; let routes = websocket_route.or(page_route); let server = warp::serve(routes) .tls() .cert_path(state.conf.cert_path) .key_path(state.conf.pkey_path) .run(state.conf.socket_addr); println!("Serving on {}", state.conf.socket_addr); server.await; Ok(()) } // If a move is made, broadcast new board, else just send current board async fn gameloop(mut move_rx: tokio::sync::mpsc::UnboundedReceiver, peers: PeerMap) { use minesweeper::*; 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 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(|p| p.name.clone()); }, MetaMove::Dump => (), MetaMove::Reset => { game = Game::new(Board::new(75,35), (75*35)/8); }, } use warp::ws::Message; 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, p) in peers.iter() { for r in reply.iter() { if let Err(e) = p.tx.send(r.clone()) { println!("couldn't send game update {r:?} to {addr}: {e}"); } } } } } }