summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstale <redkugelblitzin@gmail.com>2022-07-01 20:53:11 -0300
committerstale <redkugelblitzin@gmail.com>2022-07-01 20:53:11 -0300
commitfe851ee7ea31caa48a6d9a8ce1ddbb30854f2321 (patch)
tree27a5f19799d7c3ba654ea09ac3eb41ef7d9dfde0
parentc68dc16bef476203a4424c4b857f16eeb8f0e119 (diff)
more config options and a good refactoring
-rw-r--r--assets/VT323.ttf (renamed from assets/VT323-Regular.ttf)bin149688 -> 149688 bytes
-rw-r--r--assets/favicon.icobin0 -> 1307 bytes
-rw-r--r--assets/index.html2
-rw-r--r--assets/room.html4
-rw-r--r--assets/style.css2
-rw-r--r--conf.json.sample19
-rw-r--r--src/main.rs205
-rw-r--r--src/types.rs20
8 files changed, 138 insertions, 114 deletions
diff --git a/assets/VT323-Regular.ttf b/assets/VT323.ttf
index 6aec599..6aec599 100644
--- a/assets/VT323-Regular.ttf
+++ b/assets/VT323.ttf
Binary files differ
diff --git a/assets/favicon.ico b/assets/favicon.ico
new file mode 100644
index 0000000..646857a
--- /dev/null
+++ b/assets/favicon.ico
Binary files differ
diff --git a/assets/index.html b/assets/index.html
index cce9248..c67e4d6 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<title>websweeper</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
- <link rel="stylesheet" type="text/css" href="./s.css">
+ <link rel="stylesheet" type="text/css" href="./style.css">
</head>
<body>
<div class="cent">
diff --git a/assets/room.html b/assets/room.html
index 4dadc6b..e128bc7 100644
--- a/assets/room.html
+++ b/assets/room.html
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<title>websweeper</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
- <link rel="stylesheet" type="text/css" href="../s.css">
+ <link rel="stylesheet" type="text/css" href="../style.css">
</head>
<body>
<div>
@@ -26,5 +26,5 @@
</div>
</body>
<script src="https://unpkg.com/fflate"></script>
- <script src="../c.js"></script>
+ <script src="../client.js"></script>
</html>
diff --git a/assets/style.css b/assets/style.css
index 49e832e..4d12602 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -1,6 +1,6 @@
@font-face {
font-family: vt323;
- src: url("./f.ttf");
+ src: url("./VT323.ttf");
}
#board-container {
font-size: 80px;
diff --git a/conf.json.sample b/conf.json.sample
index 7ca5bec..10ec4f5 100644
--- a/conf.json.sample
+++ b/conf.json.sample
@@ -1,4 +1,19 @@
{
- "area_limit": 22500,
- "room_limit": 16
+ "paths": {
+ "cert": "cert.pem",
+ "pkey": "cert.rsa",
+ "assets": "assets/",
+ "index_page": "assets/index.html",
+ "room_page": "assets/room.html"
+ },
+
+ "server": {
+ "listen_on": "0.0.0.0:31235"
+ },
+
+ "limits": {
+ "board_area": 22500,
+ "room_slots": 16,
+ "form_size": 4096
+ }
}
diff --git a/src/main.rs b/src/main.rs
index 07ba00e..abb2e48 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,8 +4,11 @@ use std::{
sync::Arc,
collections::HashMap,
num::NonZeroUsize,
+ path::PathBuf,
};
use futures::stream::StreamExt;
+use tokio::sync::RwLock;
+use serde::Deserialize;
mod types;
mod livepos;
@@ -13,42 +16,47 @@ mod conn;
mod minesweeper;
use types::*;
-use tokio::sync::RwLock;
-
-const FONT_FILE: &[u8] = include_bytes!("../assets/VT323-Regular.ttf");
const CONF_FILE: &str = "./conf.json";
-fn main() -> Result<(), Box<dyn Error>> {
- let conf = Config {
- cert: "./cert.pem".to_owned(),
- pkey: "./cert.rsa".to_owned(),
- index_pg: "./assets/index.html".to_owned(),
- room_pg: "./assets/room.html".to_owned(),
- client_code: "./assets/client.js".to_owned(),
- stylesheet: "./assets/style.css".to_owned(),
- socket_addr: ([0,0,0,0],31235).into(),
- };
+#[derive(Deserialize)]
+struct ConfPaths {
+ pub cert: PathBuf,
+ pub pkey: PathBuf,
+ pub assets: PathBuf,
+ pub index_page: PathBuf,
+ pub room_page: PathBuf,
+}
+#[derive(Deserialize)]
+struct ConfServer {
+ pub listen_on: SocketAddr,
+}
+#[derive(Deserialize)]
+struct ConfLimits {
+ pub board_area: usize,
+ pub room_slots: usize,
+ pub form_size: u64,
+}
+#[derive(Deserialize)]
+struct Conf {
+ pub paths: ConfPaths,
+ pub server: ConfServer,
+ pub limits: ConfLimits,
+}
+fn main() -> Result<(), Box<dyn Error>> {
+ let conf: Conf = serde_json::from_str(&std::fs::read_to_string(CONF_FILE)?)?;
tokio_main(conf)
}
#[tokio::main]
-async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> {
- let conf_file: serde_json::Value = serde_json::from_str(&tokio::fs::read_to_string(CONF_FILE).await?)?;
- let area_limit: usize = conf_file.get("area_limit")
- .expect("no area_limit field in the conf.json file")
- .as_u64().expect("area_limit not a number") as usize;
- let room_limit: usize = conf_file.get("room_limit")
- .expect("no room_limit field in the conf.json file")
- .as_u64().expect("room_limit not a number") as usize;
+async fn tokio_main(conf: Conf) -> Result<(), Box<dyn Error>> {
+ let conf = Arc::new(conf);
let rooms: RoomMap = Arc::new(RwLock::new(HashMap::new()));
let public_rooms = Arc::new(RwLock::new(HashMap::new()));
use warp::*;
- let index = path::end().and(fs::file(conf.index_pg.clone()));
- 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 index = path::end().and(fs::file(conf.paths.index_page.clone()));
+ let assets = any().and(fs::dir(conf.paths.assets.clone()));
let listing = {
let rooms = rooms.clone();
let pubs = public_rooms.clone();
@@ -77,31 +85,30 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> {
};
let roomspace = {
let rooms = rooms.clone();
+ let conf = conf.clone();
path!("rspace").and_then(move || {
let r = rooms.clone();
+ let conf = conf.clone();
async move {
let empty_len = empty_rooms(r.clone()).await.len();
- let space = room_limit - r.read().await.len() + empty_len;
- Ok::<_,std::convert::Infallible>(
- hyper::Response::builder()
- .status(hyper::StatusCode::OK)
- .body(hyper::Body::from(space.to_string()))
- .unwrap()
- )
+ let space = conf.limits.room_slots - r.read().await.len() + empty_len;
+ Ok::<String, std::convert::Infallible>(space.to_string())
}
})
};
let rform_recv = {
let rooms = rooms.clone();
let pubs = public_rooms.clone();
- post().and(path("r")).and(body::content_length_limit(4096)).and(body::form())
+ let conf = conf.clone();
+ post().and(path("r")).and(body::content_length_limit(conf.limits.form_size)).and(body::form())
.and_then(move |rinfo: HashMap<String, String>| {
println!("{:?}", rinfo);
let rooms = rooms.clone();
let pubs = pubs.clone();
+ let conf = conf.clone();
async move {
- let slots_available = room_limit - rooms.read().await.len();
+ let slots_available = conf.limits.room_slots - rooms.read().await.len();
let empty = empty_rooms(rooms.clone()).await;
if slots_available < 1 {
if slots_available + empty.len() > 0 {
@@ -111,61 +118,26 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> {
}
}
- if let (Some(w),Some(h),Some(num),Some(denom),access,asfm,limit) = (
- rinfo.get("rwidth").and_then(|wt| wt.parse::<NonZeroUsize>().ok()),
- rinfo.get("rheight").and_then(|ht| ht.parse::<NonZeroUsize>().ok()),
- rinfo.get("rration").and_then(|nt| nt.parse::<usize>().ok()),
- rinfo.get("rratiod").and_then(|dt| dt.parse::<NonZeroUsize>().ok()),
- rinfo.get("raccess"),
- rinfo.get("ralwayssafe1move"),
- rinfo.get("rlimit").and_then(|l| l.parse::<usize>().ok()),
- ) {
- if w.get()*h.get() > area_limit {
- return Err(reject::custom(BoardTooBig))
- }
- let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm.is_some() };
- let mut rooms = rooms.write().await;
- let uid = types::RoomId::new_in(&rooms);
- let name = {
- let n = rinfo.get("rname").unwrap().to_owned();
- if n.is_empty() { uid.to_string() } else { n }
- };
-
- let players = 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 mut rooms = rooms.write().await;
+ let uid = RoomId::new_among(rooms.keys());
- let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel();
- let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx));
-
- let room_conf = RoomConf {
- name,
- player_cap: match limit { Some(i) => i, None => usize::MAX },
- public: access.is_some(),
- board_conf,
- };
- let new_room = Room {
- conf: room_conf,
- players,
- game_driver: game_handle,
- cmd_stream: cmd_tx,
- livepos_driver: livepos_handle,
- pos_stream: pos_tx,
- };
- if access.is_some() {
- pubs.write().await.insert(uid.clone(), serde_json::to_string(&new_room.conf).unwrap());
- }
- rooms.insert(uid.clone(), Arc::new(RwLock::new(new_room)));
+ match room_from_form(uid.clone(), &rinfo, &conf) {
+ Ok((room, access)) => {
+ if access {
+ pubs.write().await.insert(uid.clone(), serde_json::to_string(&room.conf).unwrap());
+ }
+ rooms.insert(uid.clone(), Arc::new(RwLock::new(room)));
- Ok(
- hyper::Response::builder()
- .status(hyper::StatusCode::SEE_OTHER)
- .header(hyper::header::LOCATION, format!("./room/{uid}"))
- .body(hyper::Body::empty())
- .unwrap()
- )
- } else { Err(reject::custom(BadFormData)) }
+ Ok(
+ hyper::Response::builder()
+ .status(hyper::StatusCode::SEE_OTHER)
+ .header(hyper::header::LOCATION, format!("./room/{uid}"))
+ .body(hyper::Body::empty())
+ .unwrap()
+ )
+ },
+ Err(e) => Err(e),
+ }
}
})
};
@@ -197,7 +169,7 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> {
}
})
.or(prefix.and(path::end())
- .and(fs::file(conf.room_pg.clone()))
+ .and(fs::file(conf.paths.room_page.clone()))
.then(move |id: String, f: fs::File| {
let rooms = rooms_lobby.clone();
async move {
@@ -214,27 +186,26 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> {
let route = get()
.and(index)
- .or(style)
- .or(code)
- .or(font)
.or(listing)
.or(roomspace)
.or(rform_recv)
.or(room)
+ .or(assets)
.recover(error_handler);
let server = warp::serve(route)
.tls()
- .cert_path(conf.cert)
- .key_path(conf.pkey)
- .run(conf.socket_addr);
- println!("Serving on {}", conf.socket_addr);
+ .cert_path(conf.paths.cert.clone())
+ .key_path(conf.paths.pkey.clone())
+ .run(conf.server.listen_on);
+ println!("Serving on {}", conf.server.listen_on);
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<MetaMove>, players: PlayerMapData, 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;
@@ -315,6 +286,50 @@ async fn empty_rooms(rooms: RoomMap) -> Vec<RoomId> {
.collect::<Vec<RoomId>>().await
}
+fn room_from_form(uid: RoomId, rinfo: &HashMap<String,String>, conf: &Conf) -> Result<(types::Room, bool), Rejection> {
+ if let (Some(w),Some(h),Some(num),Some(denom),access,asfm,limit) = (
+ rinfo.get("rwidth").and_then(|wt| wt.parse::<NonZeroUsize>().ok()),
+ rinfo.get("rheight").and_then(|ht| ht.parse::<NonZeroUsize>().ok()),
+ rinfo.get("rration").and_then(|nt| nt.parse::<usize>().ok()),
+ rinfo.get("rratiod").and_then(|dt| dt.parse::<NonZeroUsize>().ok()),
+ rinfo.get("raccess"),
+ rinfo.get("ralwayssafe1move"),
+ rinfo.get("rlimit").and_then(|l| l.parse::<usize>().ok()),
+ ) {
+ if w.get()*h.get() > conf.limits.board_area {
+ return Err(warp::reject::custom(BoardTooBig))
+ }
+ let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm.is_some() };
+ let name = {
+ let n = rinfo.get("rname").unwrap().to_owned();
+ if n.is_empty() { uid.to_string() } else { n }
+ };
+
+ let players = 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 (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel();
+ let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx));
+
+ let room_conf = RoomConf {
+ name,
+ player_cap: match limit { Some(i) => i, None => usize::MAX },
+ public: access.is_some(),
+ board_conf,
+ };
+ Ok((Room {
+ conf: room_conf,
+ players,
+ game_driver: game_handle,
+ cmd_stream: cmd_tx,
+ livepos_driver: livepos_handle,
+ pos_stream: pos_tx,
+ }, access.is_some()))
+ } else { Err(warp::reject::custom(BadFormData)) }
+}
+
async fn remove_room<T>(rooms: RoomMap, pubs: Arc<RwLock<HashMap<RoomId,T>>>, id: RoomId) {
{
let mut rwl = rooms.write().await;
diff --git a/src/types.rs b/src/types.rs
index f9f166a..7cdd44c 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -14,17 +14,6 @@ use serde::Serialize;
use crate::minesweeper;
use crate::livepos;
-#[derive(Debug, Clone)]
-pub struct Config {
- pub cert: String,
- pub pkey: String,
- pub index_pg: String,
- pub room_pg: String,
- pub client_code: String,
- pub stylesheet: String,
- pub socket_addr: SocketAddr,
-}
-
#[derive(Debug, Serialize, Clone)]
pub struct RoomConf {
pub name: String,
@@ -83,14 +72,19 @@ impl std::borrow::Borrow<str> for RoomId {
}
impl RoomId {
- pub fn new_in<T>(map: &HashMap<RoomId, T>) -> Self {
+ pub fn new_among<'a, I>(existing: I) -> Self
+ where
+ I: IntoIterator<Item = &'a RoomId>,
+ <I as IntoIterator>::IntoIter: Clone,
+ {
use rand::{ thread_rng, Rng, distributions::Alphanumeric };
let id = RoomId(thread_rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect::<String>());
- if map.contains_key(&id) { RoomId::new_in(map) }
+ let existing = existing.into_iter();
+ if existing.clone().any(|x| *x == id) { Self::new_among(existing) }
else { id }
}
}