From 4b090965a2c76af9c997853c8cc16c643befdceb Mon Sep 17 00:00:00 2001 From: Martin Hafskjold Thoresen Date: Fri, 10 Jan 2025 18:40:59 +0100 Subject: Randomly choose sound effect Also some JS cleanup --- src/main.rs | 102 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 2f8d5a5..caf01c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use axum::{ }; use axum_extra::extract::CookieJar; use maud::{html, Markup, PreEscaped, DOCTYPE}; -use rand::random; +use rand::{random, seq::SliceRandom}; use serde::{Deserialize, Serialize}; use tokio::sync::{ broadcast::{self, Receiver, Sender}, @@ -68,6 +68,8 @@ struct Round { guesses: HashMap, /// [true] means the judge has accepted it. Missing means they haven't done anything correct: HashMap, + + sound_path: String, } struct Game { @@ -105,6 +107,22 @@ impl Game { .unwrap(); } + fn restart(&mut self) { + assert!( + self.is_finished, + "Game needs to be finished to be restarted" + ); + self.is_started = true; + self.is_finished = false; + self.active_users = self.senders.keys().cloned().collect(); + self.submissions = HashMap::new(); + self.next_rounds = Vec::new(); + self.rounds = Vec::new(); + self.broadcast_screen(self.screen_compose()) + .context("Broadcast compose screen") + .unwrap(); + } + /// User submits a melody. fn submit(&mut self, uid: UserId, notes: Notes) { if !self.active_users.contains(&uid) { @@ -135,10 +153,17 @@ impl Game { error!("No more rounds"); return; }; + + let mut rng = rand::thread_rng(); + let sound_fx = ["dog.mp3", "cat.mp3", "car.mp3"] + .choose(&mut rng) + .expect("not empty"); + self.rounds.push(Round { author: uid, guesses: HashMap::new(), correct: HashMap::new(), + sound_path: format!("/static/{}", sound_fx), }); for &u in &self.active_users { @@ -238,6 +263,7 @@ impl Game { return html! { h1 { "No more rounds" }}; }; let gid = self.id; + let sound = &r.sound_path; let Some(notes) = self.submissions.get(&r.author) else { return html! { @@ -259,30 +285,15 @@ impl Game { input type="submit" hx-post=(format!("/game/{gid}/guess")) hx-target="#form-guess" {}; } - script { (PreEscaped(format!(r#" + script { (PreEscaped(format!(r#"{{ const notes = {notes_json}; -{}"#, r#" -const context = new AudioContext(); - -function loadSample(url) { - return fetch(url) - .then(response => response.arrayBuffer()) - .then(buffer => context.decodeAudioData(buffer)); -} - -function playSoundSample(sample, sampleNote, noteToPlay) { - const source = context.createBufferSource(); - source.buffer = sample; - source.playbackRate.value = 2 ** ((noteToPlay - sampleNote) / 12); - source.connect(context.destination); - source.start(0); -} - -const soundUrl = "/static/dog.mp3"; +const soundUrl = "{sound}"; +{} +}}"#, r#" loadSample(soundUrl).then((sample) => { const t0 = notes[0].time - 1_000; notes.forEach(({time, y}) => { - const pitch = 60 + 12 * y; + const pitch = 60 + 12 * (1.0 - y); setTimeout(() => playSoundSample(sample, 60, pitch), time - t0); }); }); @@ -366,6 +377,8 @@ loadSample(soundUrl).then((sample) => { } a href="/" { "Back" } + + button hx-post=(format!("/game/{}/restart", self.id)) { "Start new round" } } } @@ -443,36 +456,6 @@ async fn route_index(State(st): State) -> Response { (game_list) button hx-post="/game" { "Create game" } - - button #soundtest { "Sound test" } - - script { (PreEscaped(r#" - -async function soundtest() { - const context = new AudioContext(); - - function loadSample(url) { - return fetch(url) - .then(response => response.arrayBuffer()) - .then(buffer => context.decodeAudioData(buffer)); - } - - function playSample(sample, sampleNote, noteToPlay) { - const source = context.createBufferSource(); - source.buffer = sample; - source.playbackRate.value = 2 ** ((noteToPlay - sampleNote) / 12); - source.connect(context.destination); - source.start(0); - } - - const sample = await loadSample('/static/cat.mp3'); - const audio = new Audio("/static/dog.mp3"); - [0, 2, 4, 5, 7, 9, 11, 12].forEach((p, i) => setTimeout(() => - playSample(sample, 60, 60 + p), 500 * i) - ); -} -document.getElementById("soundtest").onclick = soundtest; -"#)) } }; html_response(html! { @@ -525,6 +508,20 @@ async fn start_game(Path(gid): Path, State(st): State) -> Response StatusCode::OK.into_response() } +async fn restart_game(Path(gid): Path, State(st): State) -> Response { + let lock = { + if let Some(g) = st.lock().await.games.get(&gid) { + g.clone() + } else { + return StatusCode::NOT_FOUND.into_response(); + } + }; + + lock.lock().await.restart(); + + StatusCode::OK.into_response() +} + async fn get_game( Path(gid): Path, Extension(uid): Extension, @@ -575,6 +572,8 @@ async fn get_game( script src="https://unpkg.com/htmx-ext-ws@2.0.1/ws.js" {}; link rel="stylesheet" type="text/css" href="/static/style.css"; + + script src="/static/main.js" {}; } body hx-ext="ws" ws-connect=(format!("/game/{gid}/ws")) { footer { } @@ -802,6 +801,7 @@ 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}/restart", post(restart_game)) .route("/game/{gid}/guess", post(guess_tune)) .route("/game/{gid}/submit", post(submit_game)) .route("/game/{gid}/mark/{uid}/{status}", post(mark_guess)) -- cgit v1.2.3