use std::{ error::Error, net::SocketAddr, sync::Arc, }; mod types; mod conn; mod minesweeper; use types::*; use tokio::sync::RwLock; const FONT_FILE: &[u8] = include_bytes!("../assets/VT323-Regular.ttf"); fn main() -> Result<(), Box> { let conf = Config { cert: "./cert.pem".to_owned(), pkey: "./cert.rsa".to_owned(), room_pg: "./assets/room.html".to_owned(), form_pg: "./assets/form.html".to_owned(), client_code: "./assets/client.js".to_owned(), stylesheet: "./assets/style.css".to_owned(), socket_addr: ([0,0,0,0],31235).into(), }; tokio_main(conf) } #[tokio::main] async fn tokio_main(conf: Config) -> Result<(), Box> { // Start the temporary single room let room = Arc::new(RwLock::new({ let name = "Testing room".to_string(); let players = PlayerMap::default(); let bconf = BoardConf { w: 75, h: 35, mine_ratio: (1, 8) }; let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); let handle = tokio::spawn(gameloop(cmd_rx, players.clone(), bconf)); Room { name, players, peer_limit: 32, board_conf: bconf, cmd_stream: cmd_tx, driver: handle, } })); use warp::*; let style = path("s.css").and(fs::file(conf.stylesheet.clone())); let code = path("c.js").and(fs::file(conf.client_code.clone())); let font = path("f.ttf").map(|| FONT_FILE); let listing = path("rlist").map(|| "placeholder'em"); let room_form = path("r").map(|| "yeah placeholder mate"); let index = path::end().and(fs::file(conf.room_pg.clone())); let websocket_route = { let room = room.clone(); use warp::*; path("ws") .and(ws()) .and(addr::remote()) .map(move |ws: warp::ws::Ws, saddr: Option| { let room = room.clone(); println!("conn from {saddr:?}"); ws.on_upgrade(move |socket| { conn::lobby(socket, saddr.expect("socket without address"), room.clone()) }) }) }; let route = any().and(get().and(index).or(style).or(code).or(font).or(listing)).or(post().and(room_form)); let routes = websocket_route.or(route); let server = warp::serve(routes) .tls() .cert_path(conf.cert) .key_path(conf.pkey) .run(conf.socket_addr); println!("Serving on {}", 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, players: PlayerMapData, bconf: BoardConf) { use minesweeper::*; let mine_cnt = (bconf.w * bconf.h * bconf.mine_ratio.0)/(bconf.mine_ratio.1); let mut game = Game::new(Board::new(bconf.w, bconf.h), mine_cnt); 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 = players.read().await.get(&o).map(|p| p.name.clone()); }, MetaMove::Dump => (), MetaMove::Reset => { game = Game::new(Board::new(bconf.w, bconf.h), mine_cnt); }, } 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 = 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}"); } } } } } }