diff --git a/.pingscores.json.swp b/.pingscores.json.swp deleted file mode 100644 index ea86b8c..0000000 Binary files a/.pingscores.json.swp and /dev/null differ diff --git a/pingscores.json b/pingscores.json deleted file mode 100644 index eac76ad..0000000 --- a/pingscores.json +++ /dev/null @@ -1 +0,0 @@ -{"error":[0,60],"anon":[15,10],"deadvey":[1129258,0]} diff --git a/src/.main.rs.swp b/src/.main.rs.swp new file mode 100644 index 0000000..5f0449a Binary files /dev/null and b/src/.main.rs.swp differ diff --git a/src/main.rs b/src/main.rs index 043c5dd..2514d8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -use std::{ +use std:: +{ collections::HashMap, fs, io::Write, @@ -6,9 +7,11 @@ use std::{ sync::Arc, }; -use axum::{ +use axum:: +{ Router, - extract::{ + extract:: + { State, ws::{Message, WebSocket, WebSocketUpgrade}, }, @@ -19,31 +22,36 @@ use futures::StreamExt as _; use rand::random_bool; use serde::{Deserialize, Serialize, de}; use serde_json::json; -use tokio::{ +use tokio:: +{ sync::{Mutex, mpsc}, time::{Duration, sleep}, }; #[derive(Deserialize, Serialize, Debug, Ord, Eq, PartialEq, PartialOrd, Clone)] -struct Entry { +struct Entry +{ score: u32, person: String, } #[derive(Clone)] -struct AppState { +struct AppState +{ tx: mpsc::Sender, hiscores: Arc>>, loscores: Arc>>, pingscores: Arc>>, // u64 is reset count and u32 is PB } -struct LeaderboardUpdate { +struct LeaderboardUpdate +{ name: Arc, update: LeaderboardUpdateType, } -enum LeaderboardUpdateType { +enum LeaderboardUpdateType +{ Reset { hiscore_pingscore: u32 }, Increment { loscore: u32 }, } @@ -57,19 +65,25 @@ const PATH_PINGSCORES: &str = "pingscores.json"; const MAX_LEADERBOARD: usize = 20; #[tokio::main] -async fn main() -> anyhow::Result<()> { - fn read_file de::Deserialize<'de>>( +async fn main() -> anyhow::Result<()> +{ + fn read_file de::Deserialize<'de>> + ( file_path: &str, - ) -> anyhow::Result>> { + ) + -> anyhow::Result>> + { let file_contents: String = fs::read_to_string(file_path)?; Ok(Arc::new(Mutex::new(serde_json::from_str(&file_contents)?))) } /// Makes the vector at `vec` one with a capacity of exactly [`MAX_LEADERBOARD`] if `vec` is /// smaller or equal. - fn exact_leaderboard(mut vec: impl DerefMut>) { + fn exact_leaderboard(mut vec: impl DerefMut>) + { let old_vec = std::mem::replace(&mut *vec, Vec::with_capacity(MAX_LEADERBOARD)); - for e in old_vec { + for e in old_vec + { vec.push(e); } } @@ -81,20 +95,22 @@ async fn main() -> anyhow::Result<()> { let pingscores: Arc>> = read_file(PATH_PINGSCORES)?; let (tx, rx) = mpsc::channel::(1024); - { let (hiscores, loscores, pingscores) = (hiscores.clone(), loscores.clone(), pingscores.clone()); - tokio::spawn(async move { + tokio::spawn + (async move { handle_hiscores(rx, &hiscores, &loscores, &pingscores).await; }); } { let pingscores = pingscores.clone(); - tokio::spawn(async move { + tokio::spawn + (async move { // write pingscores every 30s - loop { + loop + { sleep(Duration::from_secs(30)).await; let pingscores = pingscores.lock().await; let file_contents: String = serde_json::to_string(&pingscores.clone()) @@ -116,7 +132,8 @@ async fn main() -> anyhow::Result<()> { .route("/", get(index)) .route("/ws", get(ws_handler)) .route("/leaderboard", get(leaderboard)) - .with_state(AppState { + .with_state + (AppState { tx, hiscores: Arc::clone(&hiscores), loscores: Arc::clone(&loscores), @@ -132,40 +149,50 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn index() -> Html<&'static str> { +async fn index() -> Html<&'static str> +{ Html(include_str!("../index.html")) } -async fn leaderboard() -> Html<&'static str> { +async fn leaderboard() -> Html<&'static str> +{ Html(include_str!("../leaderboard.html")) } // receiver: 0 for hiscore, 1 for loscore, 2 for pingscore -async fn handle_hiscores( +async fn handle_hiscores +( mut rx: mpsc::Receiver, hiscores: &Mutex>, loscores: &Mutex>, pingscores: &Mutex>, -) { - fn update_scoretable> + DerefMut>( +) +{ + fn update_scoretable> + DerefMut> + ( score_name: &str, mut scoretable_lock: G, name: &str, score: u32, file_path: &str, - ) -> anyhow::Result<()> { + ) + -> anyhow::Result<()> + { let scoretable = &mut *scoretable_lock; - - if let Some(index_to_insert_at) = scoretable.iter().position(|e| score > e.score) { + if let Some(index_to_insert_at) = scoretable.iter().position(|e| score > e.score) + { println!("New {score_name} {score} by {name}"); scoretable[index_to_insert_at..].rotate_right(1); - let push_out = std::mem::replace( + let push_out = std::mem::replace + ( &mut scoretable[index_to_insert_at], - Entry { + Entry + { score, person: name.to_string(), }, ); - if scoretable.len() < MAX_LEADERBOARD { + if scoretable.len() < MAX_LEADERBOARD + { scoretable.push(push_out); } @@ -177,9 +204,12 @@ async fn handle_hiscores( .open(file_path)?; file.write_all(file_contents.as_bytes())?; file.flush()?; - } else if scoretable.len() < MAX_LEADERBOARD { + } + else if scoretable.len() < MAX_LEADERBOARD + { println!("New {score_name} {score} by {name}"); - scoretable.push(Entry { + scoretable.push + (Entry { score, person: name.to_string(), }); @@ -204,13 +234,17 @@ async fn handle_hiscores( .expect("failed to write hiscores"); drop(file); - loop { + loop + { let LeaderboardUpdate { name, update } = rx.recv().await.expect("channel error"); - match update { - LeaderboardUpdateType::Reset { hiscore_pingscore } => { + match update + { + LeaderboardUpdateType::Reset { hiscore_pingscore } => + { // Hiscore - update_scoretable( + update_scoretable + ( "hiscore", hiscores.lock().await, &name, @@ -222,7 +256,8 @@ async fn handle_hiscores( // Pingscore let mut pingscores = pingscores.lock().await; // pb - if hiscore_pingscore > pingscores.get(&*name).unwrap_or(&(0, 0)).1 { + if hiscore_pingscore > pingscores.get(&*name).unwrap_or(&(0, 0)).1 + { pingscores.entry(name.to_string()).or_insert((0, 0)).1 = hiscore_pingscore; println!("{name} new PB: {hiscore_pingscore}"); } @@ -230,8 +265,10 @@ async fn handle_hiscores( drop(pingscores); } - LeaderboardUpdateType::Increment { loscore } => { - update_scoretable( + LeaderboardUpdateType::Increment { loscore } => + { + update_scoretable + ( "loscore", loscores.lock().await, &name, @@ -244,9 +281,17 @@ async fn handle_hiscores( } } -async fn ws_handler(ws: WebSocketUpgrade, State(state): State) -> impl IntoResponse { - ws.on_upgrade(|socket| async move { - handle_socket( +async fn ws_handler +( + ws: WebSocketUpgrade, + State(state): State +) + -> impl IntoResponse +{ + ws.on_upgrade + (|socket| async move { + handle_socket + ( socket, &state.tx, &state.hiscores, @@ -257,17 +302,20 @@ async fn ws_handler(ws: WebSocketUpgrade, State(state): State) -> impl }) } -async fn handle_socket( +async fn handle_socket +( mut socket: WebSocket, tx: &mpsc::Sender, hiscores: &Mutex>, loscores: &Mutex>, pingscores: &Mutex>, -) { +) +{ let mut value: u32 = 0; - - let msg = { - json!({ + let msg = + { + json! + ({ "hiscores": &*hiscores.lock().await, "loscores": &*loscores.lock().await, "pingscores": &*pingscores.lock().await @@ -275,11 +323,13 @@ async fn handle_socket( .to_string() }; - let Some(name) = socket.next().await else { + let Some(name) = socket.next().await else + { eprintln!("user gave no username"); return; }; - let name: Arc = match name.expect("failed to recv socket msg") { + let name: Arc = match name.expect("failed to recv socket msg") + { Message::Text(text) if let text = text.to_string() && validate_name(&text) => @@ -296,15 +346,22 @@ async fn handle_socket( let _ = socket.send(Message::Text(msg.into())).await; - while let Some(msg) = socket.next().await { - match msg { - Ok(Message::Text(_)) => { - if random_bool(CHANCE) { + while let Some(msg) = socket.next().await + { + match msg + { + Ok(Message::Text(_)) => + { + // 1/3 chance of failing + if random_bool(CHANCE) + { // reset let _ = tx - .send(LeaderboardUpdate { + .send(LeaderboardUpdate + { name: name.clone(), - update: LeaderboardUpdateType::Reset { + update: LeaderboardUpdateType::Reset + { hiscore_pingscore: value, }, }) @@ -312,12 +369,14 @@ async fn handle_socket( resets += 1; value = 0; } - // 1/3 chance of failing - else { + else + { value += 1; - if prev == 0 { + if prev == 0 + { let _ = tx - .send(LeaderboardUpdate { + .send + (LeaderboardUpdate { name: name.clone(), update: LeaderboardUpdateType::Increment { loscore: resets }, }) @@ -329,7 +388,8 @@ async fn handle_socket( prev = value; } - Ok(Message::Close(_)) => { + Ok(Message::Close(_)) => + { break; } @@ -342,7 +402,8 @@ fn validate_name(input: &str) -> bool { let input = input.trim(); // Length check - if input.is_empty() || input.len() > 32 { + if input.is_empty() || input.len() > 32 + { return false; }