summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Hafskjold Thoresen <martin@vind.ai>2025-01-10 18:40:59 +0100
committerMartin Hafskjold Thoresen <martin@vind.ai>2025-01-10 18:40:59 +0100
commit4b090965a2c76af9c997853c8cc16c643befdceb (patch)
tree795faa984632eaabc4c240cf08d0434f11021a61
parented791666b2575fd1e60934f0a178441e063bdfca (diff)
downloadmusicgame-4b090965a2c76af9c997853c8cc16c643befdceb.tar.gz
musicgame-4b090965a2c76af9c997853c8cc16c643befdceb.zip
Randomly choose sound effect
Also some JS cleanup
-rw-r--r--src/main.rs102
-rw-r--r--static/main.js22
2 files changed, 73 insertions, 51 deletions
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<UserId, String>,
/// [true] means the judge has accepted it. Missing means they haven't done anything
correct: HashMap<UserId, bool>,
+
+ 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<Server>) -> 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<u64>, State(st): State<Server>) -> Response
StatusCode::OK.into_response()
}
+async fn restart_game(Path(gid): Path<u64>, State(st): State<Server>) -> 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<u64>,
Extension(uid): Extension<UserId>,
@@ -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))
diff --git a/static/main.js b/static/main.js
new file mode 100644
index 0000000..144254f
--- /dev/null
+++ b/static/main.js
@@ -0,0 +1,22 @@
+let _context;
+function getContext() {
+ if (!_context) {
+ _context = new AudioContext();
+ }
+ return _context;
+}
+
+function loadSample(url) {
+ return fetch(url)
+ .then((response) => response.arrayBuffer())
+ .then((buffer) => getContext().decodeAudioData(buffer));
+}
+
+function playSoundSample(sample, sampleNote, noteToPlay) {
+ const ctx = getContext();
+ const source = ctx.createBufferSource();
+ source.buffer = sample;
+ source.playbackRate.value = 2 ** ((noteToPlay - sampleNote) / 12);
+ source.connect(ctx.destination);
+ source.start(0);
+}