feat(web client): opponent sounds

This commit is contained in:
Henri Bourcereau 2026-05-02 20:01:30 +02:00
parent e0f059e09c
commit 60f33750eb
2 changed files with 65 additions and 2 deletions

View file

@ -224,6 +224,11 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
crate::game::sound::play_hole_scored(); crate::game::sound::play_hole_scored();
} }
} }
if let Some(ref ev) = opp_scored_event {
if ev.holes_gained > 0 {
crate::game::sound::play_opp_hole_scored();
}
}
// ── Capture for closures ─────────────────────────────────────────────────── // ── Capture for closures ───────────────────────────────────────────────────
let stage = vs.stage.clone(); let stage = vs.stage.clone();
@ -473,6 +478,11 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
// ── Game-over overlay ───────────────────────────────────────────── // ── Game-over overlay ─────────────────────────────────────────────
{stage_is_ended.then(|| { {stage_is_ended.then(|| {
if winner_is_me {
crate::game::sound::play_victory();
} else {
crate::game::sound::play_defeat();
}
let opp_name_end_clone = opp_name_end.clone(); let opp_name_end_clone = opp_name_end.clone();
let winner_text = move || if winner_is_me { let winner_text = move || if winner_is_me {
t_string!(i18n, you_win).to_owned() t_string!(i18n, you_win).to_owned()

View file

@ -176,14 +176,61 @@ mod inner {
} }
}); });
} }
/// Brief descending minor phrase when the opponent scores a hole.
pub fn play_opp_hole_scored() {
with_ctx(|ctx| {
let notes: [(f32, f64, f64); 3] = [
(392.00, 0.00, 0.32), // G4
(349.23, 0.20, 0.32), // F4
(293.66, 0.40, 0.50), // D4
];
for (freq, offset, dur) in notes {
play_tone(ctx, freq, 0.10, dur, offset, OscillatorType::Sine);
}
});
}
/// Victory fanfare: five-note ascending major (C5E5G5C6E6).
pub fn play_victory() {
with_ctx(|ctx| {
let notes: [(f32, f64, f64, f32); 5] = [
(523.25, 0.00, 0.32, 0.18), // C5
(659.25, 0.20, 0.32, 0.20), // E5
(783.99, 0.40, 0.32, 0.22), // G5
(1046.5, 0.60, 0.50, 0.25), // C6
(1318.5, 0.88, 0.80, 0.28), // E6
];
for (freq, offset, dur, gain) in notes {
play_tone(ctx, freq, gain, dur, offset, OscillatorType::Sine);
play_tone(ctx, freq * 2.0, gain * 0.12, dur, offset, OscillatorType::Sine);
}
});
}
/// Defeat phrase: descending minor (E5Eb5D5C5).
pub fn play_defeat() {
with_ctx(|ctx| {
let notes: [(f32, f64, f64); 4] = [
(659.25, 0.00, 0.45), // E5
(622.25, 0.35, 0.45), // Eb5
(587.33, 0.70, 0.45), // D5
(523.25, 1.05, 0.80), // C5
];
for (freq, offset, dur) in notes {
play_tone(ctx, freq, 0.14, dur, offset, OscillatorType::Sine);
play_tone(ctx, freq / 2.0, 0.06, dur, offset, OscillatorType::Triangle);
}
});
}
} }
// ── Public API: WASM delegates to `inner`, other targets are no-ops ─────────── // ── Public API: WASM delegates to `inner`, other targets are no-ops ───────────
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub use inner::{ pub use inner::{
play_checker_move, play_dice_roll, play_dice_roll_cinematic, play_hole_scored, play_checker_move, play_defeat, play_dice_roll, play_dice_roll_cinematic, play_hole_scored,
play_opp_points_tick, play_points_scored, play_points_tick, play_opp_hole_scored, play_opp_points_tick, play_points_scored, play_points_tick, play_victory,
}; };
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -200,3 +247,9 @@ pub fn play_points_tick() {}
pub fn play_opp_points_tick() {} pub fn play_opp_points_tick() {}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn play_hole_scored() {} pub fn play_hole_scored() {}
#[cfg(not(target_arch = "wasm32"))]
pub fn play_opp_hole_scored() {}
#[cfg(not(target_arch = "wasm32"))]
pub fn play_victory() {}
#[cfg(not(target_arch = "wasm32"))]
pub fn play_defeat() {}