fix(web client): show dice animation & sound only once

This commit is contained in:
Henri Bourcereau 2026-05-05 17:49:00 +02:00
parent 9755ab1d41
commit 8f40304f41
4 changed files with 38 additions and 6 deletions

View file

@ -272,6 +272,9 @@ pub fn Board(
/// Fields where a hit (battue) was scored this turn — show ripple animation.
#[prop(default = vec![])]
hit_fields: Vec<u8>,
/// Suppress dice animation (echo screen shown after a pending confirm was dismissed).
#[prop(default = false)]
suppress_dice_anim: bool,
) -> impl IntoView {
let board = view_state.board;
let white_points = view_state.scores[0].points;
@ -283,6 +286,11 @@ pub fn Board(
view_state.turn_stage,
SerTurnStage::Move | SerTurnStage::HoldOrGoChoice
);
// True when ANY player is in the Move/HoldOrGoChoice stage — i.e., dice are fresh for the active player.
let active_is_move_stage = matches!(
view_state.turn_stage,
SerTurnStage::Move | SerTurnStage::HoldOrGoChoice
);
let is_white = player_id == 0;
let hovered_moves = use_context::<RwSignal<Vec<(CheckerMove, CheckerMove)>>>();
@ -533,8 +541,13 @@ pub fn Board(
bar_matched_dice_used(&staged, dice_vals)
} else if is_my_turn {
(true, true)
} else {
} else if active_is_move_stage && !suppress_dice_anim {
// Opponent has fresh dice in their Move stage (first view).
(false, false)
} else {
// Dice are old: either from the previous turn (opponent not yet
// rolled) or this is the echo screen after a pending confirm.
(true, true)
};
let used = if die_idx == 0 { u0 } else { u1 };
view! { <Die value=die_val used=used is_double=bar_is_double /> }

View file

@ -29,6 +29,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
);
let waiting_for_confirm = state.waiting_for_confirm;
let pause_reason = state.pause_reason.clone();
let suppress_dice_anim = state.suppress_dice_anim;
// ── Hovered jan moves (shown as arrows on the board) ──────────────────────
let hovered_jan_moves: RwSignal<Vec<(CheckerMove, CheckerMove)>> = RwSignal::new(vec![]);
@ -206,8 +207,14 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
};
// ── Sound effects (fire once on mount = once per state snapshot) ──────────
// Dice roll: dice just appeared (no preceding moves in this snapshot).
if show_dice && last_moves.is_none() {
// Dice roll: dice are fresh for the currently active player (Move stage means
// someone just rolled). Skipped on turn-switch states where the old dice linger
// in RollDice/MarkPoints stage before the opponent has rolled.
let active_is_move_stage = matches!(
vs.turn_stage,
SerTurnStage::Move | SerTurnStage::HoldOrGoChoice
);
if show_dice && last_moves.is_none() && active_is_move_stage && !suppress_dice_anim {
crate::game::sound::play_dice_roll();
}
// Checker move: moves were committed in the preceding action.
@ -328,6 +335,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
bar_is_double=is_double_dice
last_moves=last_moves
hit_fields=hit_fields
suppress_dice_anim=suppress_dice_anim
/>
// ── Status, hints, and actions — cream strip below board ─