summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs194
1 files changed, 169 insertions, 25 deletions
diff --git a/src/main.rs b/src/main.rs
index bb3e9b3..a4224d7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -47,6 +47,9 @@ fn handle_panic(err: Box<dyn std::any::Any + Send + 'static>) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR).into_response()
}
+#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Deserialize)]
+struct UserId(u64);
+
#[derive(Deserialize, Debug, Clone)]
struct NoteDatum {
// Epoch time in ms
@@ -60,15 +63,12 @@ struct Notes {
notes: Vec<NoteDatum>,
}
-#[derive(Deserialize)]
-struct NotesForm {
- notes: String,
-}
-
struct Round {
author: UserId,
notes: Notes,
guesses: HashMap<UserId, String>,
+ /// [true] means the judge has accepted it. Missing means they haven't done anything
+ correct: HashMap<UserId, bool>,
}
struct Game {
@@ -118,15 +118,15 @@ impl Game {
self.submissions.insert(uid, notes);
- self.message_screen(uid, self.screen_submitted())
- .context("message submit screen")
- .unwrap();
-
if self.active_users.len() == self.submissions.len() {
info!("All users have submitted");
let users = self.submissions.keys().cloned();
self.next_rounds = users.collect();
self.new_round();
+ } else {
+ self.message_screen(uid, self.screen_submitted())
+ .context("message submit screen")
+ .unwrap();
}
}
@@ -144,11 +144,49 @@ impl Game {
.unwrap()
.clone(),
guesses: HashMap::new(),
+ correct: HashMap::new(),
});
- self.broadcast_screen(self.screen_round())
- .context("broadcast screen round")
+ for &u in &self.active_users {
+ if u != uid {
+ self.message_screen(u, self.screen_round())
+ .context("message screen round")
+ .unwrap();
+ }
+ }
+ self.message_screen(uid, self.screen_judge())
+ .context("message screen judge")
+ .unwrap();
+ }
+
+ fn user_guess(&mut self, uid: UserId, guess: String) {
+ let r = self.rounds.last_mut().context("No current round").unwrap();
+ if r.guesses.contains_key(&uid) {
+ warn!("User tried to guess again");
+ return;
+ }
+ r.guesses.insert(uid, guess.clone());
+
+ if let Some(s) = self.senders.get(&r.author) {
+ s.send(
+ html! {
+ li id=(format!("guess-{}", uid.0))
+ hx-swap-oob="outerHTML" {
+ (self.guess_li(uid))
+ }
+ }
+ .into_string(),
+ )
+ .context("Send screen to author")
.unwrap();
+ } else {
+ warn!("Missing sender for author");
+ }
+ }
+
+ fn mark_guess(&mut self, uid: UserId, mark: bool) {
+ let r = self.rounds.last_mut().context("No round").unwrap();
+ r.correct.insert(uid, mark);
}
fn screen_submitted(&self) -> Markup {
@@ -191,16 +229,68 @@ impl Game {
}
}
+ /// Players guess the melody
fn screen_round(&self) -> Markup {
let i = self.rounds.len();
let n = self.rounds.len() + self.next_rounds.len();
let Some(r) = self.rounds.last() else {
return html! { h1 { "No more rounds" }};
};
+ let gid = self.id;
html! {
h1 { (format!("Round {i}/{n}"))}
p { "Author was " (r.author.0)}
+ form #form-guess {
+ input placeholder="Your guess..." name="guess" {};
+ input type="submit" hx-post=(format!("/game/{gid}/guess")) hx-target="#form-guess" {};
+ }
+ }
+ }
+
+ /// Return the `<li>` for the guess of `uid` so that its status is marked correctly
+ fn guess_li(&self, uid: UserId) -> Markup {
+ let r = self.rounds.last().unwrap();
+ let Some(g) = r.guesses.get(&uid) else {
+ return html! {
+ li id=(format!("guess-{}", uid.0)) style="display: none;" { }
+ };
+ };
+ let gid = self.id;
+ let curr = r.correct.get(&uid).cloned();
+ let accept = match curr {
+ None => "none",
+ Some(false) => "false",
+ Some(true) => "true",
+ };
+ let post = format!(
+ "/game/{gid}/mark/{}/{}",
+ uid.0,
+ !curr.unwrap_or(false) // None should mark as True when clicked
+ );
+
+ html! {
+ li id=(format!("guess-{}", uid.0))
+ data-accept=(accept)
+ hx-post=(post)
+ hx-swap="outerHTML"
+ { (g) }
+ }
+ }
+
+ /// Author of the melody marks correct answers
+ fn screen_judge(&self) -> Markup {
+ let mut v = self.active_users.iter().collect::<Vec<_>>();
+ v.sort();
+
+ html! {
+ h1 { "Mark correct answers" }
+ ul #guesslist {
+ @for &u in v {
+ (self.guess_li(u))
+ }
+ }
+ button { "Done" }
}
}
@@ -353,9 +443,17 @@ async fn get_game(
g.screen_compose()
}
} else {
- // TODO
- warn!("Wrong screen");
- g.screen_lobby()
+ if let Some(r) = g.rounds.last() {
+ if r.author == uid {
+ g.screen_judge()
+ } else {
+ g.screen_round()
+ }
+ } else {
+ // TODO
+ warn!("Missing screen, default to lobby");
+ g.screen_lobby()
+ }
}
};
@@ -380,6 +478,10 @@ async fn get_game(
})
}
+#[derive(Deserialize)]
+struct NotesForm {
+ notes: String,
+}
async fn submit_game(
Path(gid): Path<u64>,
State(st): State<Server>,
@@ -403,6 +505,57 @@ async fn submit_game(
StatusCode::OK.into_response()
}
+#[derive(Deserialize)]
+struct GuessTune {
+ guess: String,
+}
+async fn guess_tune(
+ Path(gid): Path<u64>,
+ State(st): State<Server>,
+ Extension(uid): Extension<UserId>,
+ Form(form): Form<GuessTune>,
+) -> Response {
+ let glock = {
+ if let Some(g) = st.lock().await.games.get(&gid) {
+ g.clone()
+ } else {
+ return (StatusCode::NOT_FOUND).into_response();
+ }
+ };
+ glock.lock().await.user_guess(uid, form.guess.clone());
+
+ html! {
+ span {
+ (format!(r#"You guessed "{}" "#, form.guess))
+ }
+ }
+ .into_string()
+ .into_response()
+}
+
+async fn mark_guess(
+ Path((gid, guess_id, mark)): Path<(u64, UserId, bool)>,
+ State(st): State<Server>,
+ Extension(uid): Extension<UserId>,
+) -> Response {
+ let glock = {
+ if let Some(g) = st.lock().await.games.get(&gid) {
+ g.clone()
+ } else {
+ return (StatusCode::NOT_FOUND).into_response();
+ }
+ };
+
+ let mut g = glock.lock().await;
+ let allowed = g.rounds.last().map(|r| r.author == uid).unwrap_or(false);
+ if !allowed {
+ return StatusCode::UNAUTHORIZED.into_response();
+ }
+ g.mark_guess(guess_id, mark);
+
+ g.guess_li(guess_id).into_string().into_response()
+}
+
async fn game_ws(
ws: WebSocketUpgrade,
Path(gid): Path<u64>,
@@ -435,14 +588,6 @@ async fn handle_socket(mut socket: WebSocket, uid: UserId, game: Arc<Mutex<Game>
}
let mut single_rx = {
let mut g = game.lock().await;
- // g.broadcast_tx
- // .send(
- // html! {
- // div #messages hx-swap-oob="beforeend" { p { "Someone joined" } }
- // }
- // .into_string(),
- // )
- // .unwrap();
let (tx, rx) = broadcast::channel(16);
g.senders.insert(uid, tx);
rx
@@ -482,9 +627,6 @@ async fn handle_socket(mut socket: WebSocket, uid: UserId, game: Arc<Mutex<Game>
}
}
-#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)]
-struct UserId(u64);
-
async fn identity_middleware(jar: CookieJar, mut req: Request, next: Next) -> Response {
const UID: &'static str = "uid";
@@ -532,7 +674,9 @@ async fn main() -> Result<()> {
.route("/game", post(create_game))
.route("/game/{gid}", get(get_game))
.route("/game/{gid}/start", post(start_game))
+ .route("/game/{gid}/guess", post(guess_tune))
.route("/game/{gid}/submit", post(submit_game))
+ .route("/game/{gid}/mark/{uid}/{status}", post(mark_guess))
.route("/game/{gid}/ws", get(game_ws))
.nest_service("/static", ServeDir::new("static"))
.layer(CatchPanicLayer::custom(handle_panic))