fix: die animation

This commit is contained in:
Henri Bourcereau 2026-06-20 21:42:11 +02:00
parent cf289fa779
commit 7a760980ba
5 changed files with 26 additions and 100 deletions

View file

@ -1,9 +1,9 @@
pub mod platform;
pub mod session;
pub mod traits;
mod client;
mod host;
mod platform;
mod protocol;
pub use session::{ConnectError, GameSession, RoomConfig, RoomRole, SessionEvent};

View file

@ -839,8 +839,8 @@ a:hover { text-decoration: underline; }
}
.peg-hole {
width: 10px;
height: 10px;
width: 14px;
height: 14px;
border-radius: 50%;
border: 1.5px solid rgba(138,106,40,0.45);
background: rgba(0,0,0,0.06);
@ -848,12 +848,6 @@ a:hover { text-decoration: underline; }
transition: background 0.3s ease-out, border-color 0.3s, box-shadow 0.3s;
}
.peg-hole.filled {
background: var(--ui-gold);
border-color: var(--ui-gold-dark);
box-shadow: 0 0 4px rgba(200,164,72,0.6);
}
.bredouille-badge {
font-size: 0.62rem;
font-weight: 500;
@ -868,20 +862,7 @@ a:hover { text-decoration: underline; }
margin: 0.4em;
}
/* ── Merged scoreboard (both players, above board) ──────────────────── */
.merged-score-panel {
background: var(--ui-parchment);
border-radius: 5px;
padding: 0.5rem 1.25rem 0.45rem;
font-size: 0.88rem;
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
width: 100%;
border-top: 2px solid var(--ui-gold-dark);
display: flex;
flex-direction: column;
gap: 0.2rem;
}
/* ── scoreboard (both players, above board) ──────────────────── */
.score-row {
display: flex;
align-items: center;
@ -964,33 +945,6 @@ a:hover { text-decoration: underline; }
padding-bottom: 2px;
}
/* ── Hole pegs — larger and coloured (me = green, opp = red) ─────────── */
.merged-score-panel .peg-track {
gap: 4px;
}
.merged-score-panel .peg-hole {
width: 14px;
height: 14px;
border-radius: 50%;
border: 1.5px solid rgba(138,106,40,0.3);
background: rgba(0,0,0,0.06);
flex-shrink: 0;
transition: background 0.3s ease-out, border-color 0.3s, box-shadow 0.3s;
}
.merged-score-panel .peg-hole.filled {
background: #5aab38;
border-color: #3a7828;
box-shadow: 0 0 5px rgba(90,171,56,0.55);
}
.merged-score-panel .peg-hole.peg-opp.filled {
background: #c05030;
border-color: #8a3018;
box-shadow: 0 0 5px rgba(192,80,48,0.55);
}
/* Peg pop-in animation when a new hole is scored */
@keyframes peg-pop {
0% { transform: scale(0.15); opacity: 0; }
@ -999,10 +953,6 @@ a:hover { text-decoration: underline; }
100% { transform: scale(1.0); opacity: 1; }
}
.merged-score-panel .peg-hole.peg-new {
animation: peg-pop 0.52s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
/* Thin separator between the two player rows */
.score-row-sep {
height: 1px;
@ -1010,31 +960,6 @@ a:hover { text-decoration: underline; }
margin: 0.05rem 0;
}
/* ── Non-blocking hole flash (replaces old toast) ───────────────────── */
@keyframes hole-flash-in-out {
0% { opacity: 0; transform: translateY(-3px); }
14% { opacity: 1; transform: translateY(0); }
65% { opacity: 1; }
100% { opacity: 0; transform: translateY(2px); }
}
.hole-flash {
margin-left: auto;
flex-shrink: 0;
white-space: nowrap;
font-family: var(--font-display);
font-size: 0.88rem;
font-weight: 600;
color: var(--ui-green-accent);
letter-spacing: 0.05em;
animation: hole-flash-in-out 2.5s ease-out forwards;
pointer-events: none;
}
.hole-flash.hole-flash-bredouille {
color: var(--ui-gold-dark);
}
/* ── Game bottom strip — status, hints, buttons on cream ────────────── */
.game-bottom-strip {
background: var(--ui-parchment);
@ -2365,7 +2290,6 @@ a:hover { text-decoration: underline; }
/* Strip peg overrides */
.players-strip .peg-track { gap: 3px; direction: ltr; }
.players-strip .peg-hole { width: 12px; height: 12px; }
.players-strip .peg-hole.filled {
background: #5aab38; border-color: #3a7828;
box-shadow: 0 0 5px rgba(90,171,56,0.55);

View file

@ -126,7 +126,13 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
}
let dice = vs.dice;
let show_dice = dice != (0, 0);
// Hide dice during RollDice/RollWaiting: the stored dice values are stale from the
// previous turn and showing them would trigger the tumble animation incorrectly.
let show_dice = dice != (0, 0)
&& !matches!(
vs.turn_stage,
SerTurnStage::RollDice | SerTurnStage::RollWaiting
);
// ── Button senders ─────────────────────────────────────────────────────────
let cmd_tx_go = cmd_tx.clone();

View file

@ -183,19 +183,6 @@ pub fn MergedScorePanel(
})}
</div>
</div>
{(my_holes_gained > 0).then(|| {
let label = if my_bredouille {
format!("Trou {} · ×2 bredouille", my_holes)
} else {
format!("Trou {}", my_holes)
};
view! {
<div class="hole-flash"
class:hole-flash-bredouille=my_bredouille>
{label}
</div>
}
})}
</div>
</div>

View file

@ -6,9 +6,8 @@ use backbone_lib::traits::{BackEndArchitecture, BackendCommand};
use crate::app::{GameUiState, NetCommand, PauseReason, Screen};
use crate::game::trictrac::backend::TrictracBackend;
use crate::game::trictrac::bot_local::bot_decide;
use crate::game::trictrac::types::{
JanEntry, ScoredEvent, SerStage, SerTurnStage, ViewState,
};
use crate::game::trictrac::types::{JanEntry, ScoredEvent, SerStage, SerTurnStage, ViewState};
use backbone_lib::platform::sleep_ms;
use trictrac_store::CheckerMove;
use std::collections::VecDeque;
@ -124,6 +123,7 @@ async fn run_local_bot_game_loop(
match bot_decide(backend.get_game(), pgr.as_ref()) {
None => break,
Some(action) => {
sleep_ms(500).await;
backend.inform_rpc(1, action);
for cmd in backend.drain_commands() {
if let BackendCommand::Delta(delta) = cmd {
@ -189,7 +189,11 @@ pub fn compute_last_moves(
}
/// Computes a scoring event for `player_id` by comparing the previous and next ViewState.
pub fn compute_scored_event(prev: &ViewState, next: &ViewState, player_id: u16) -> Option<ScoredEvent> {
pub fn compute_scored_event(
prev: &ViewState,
next: &ViewState,
player_id: u16,
) -> Option<ScoredEvent> {
let prev_score = &prev.scores[player_id as usize];
let next_score = &next.scores[player_id as usize];
@ -275,7 +279,11 @@ pub fn push_or_show(
/// Compares the previous and next ViewState to decide whether the transition
/// warrants a confirmation pause.
pub fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) -> Option<PauseReason> {
pub fn infer_pause_reason(
prev: &ViewState,
next: &ViewState,
player_id: u16,
) -> Option<PauseReason> {
let opponent_id = 1 - player_id;
if next.stage == SerStage::PreGameRoll {
@ -297,7 +305,8 @@ pub fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) ->
if next.dice != prev.dice {
return Some(PauseReason::AfterOpponentRoll);
}
if prev.turn_stage == SerTurnStage::HoldOrGoChoice && next.turn_stage == SerTurnStage::Move {
if prev.turn_stage == SerTurnStage::HoldOrGoChoice && next.turn_stage == SerTurnStage::Move
{
return Some(PauseReason::AfterOpponentGo);
}
}