fix: die animation
This commit is contained in:
parent
cf289fa779
commit
7a760980ba
5 changed files with 26 additions and 100 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
|
pub mod platform;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod host;
|
mod host;
|
||||||
mod platform;
|
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
|
||||||
pub use session::{ConnectError, GameSession, RoomConfig, RoomRole, SessionEvent};
|
pub use session::{ConnectError, GameSession, RoomConfig, RoomRole, SessionEvent};
|
||||||
|
|
|
||||||
|
|
@ -839,8 +839,8 @@ a:hover { text-decoration: underline; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.peg-hole {
|
.peg-hole {
|
||||||
width: 10px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 14px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 1.5px solid rgba(138,106,40,0.45);
|
border: 1.5px solid rgba(138,106,40,0.45);
|
||||||
background: rgba(0,0,0,0.06);
|
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;
|
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 {
|
.bredouille-badge {
|
||||||
font-size: 0.62rem;
|
font-size: 0.62rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
@ -868,20 +862,7 @@ a:hover { text-decoration: underline; }
|
||||||
margin: 0.4em;
|
margin: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Merged scoreboard (both players, above board) ──────────────────── */
|
/* ── 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-row {
|
.score-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -964,33 +945,6 @@ a:hover { text-decoration: underline; }
|
||||||
padding-bottom: 2px;
|
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 */
|
/* Peg pop-in animation when a new hole is scored */
|
||||||
@keyframes peg-pop {
|
@keyframes peg-pop {
|
||||||
0% { transform: scale(0.15); opacity: 0; }
|
0% { transform: scale(0.15); opacity: 0; }
|
||||||
|
|
@ -999,10 +953,6 @@ a:hover { text-decoration: underline; }
|
||||||
100% { transform: scale(1.0); opacity: 1; }
|
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 */
|
/* Thin separator between the two player rows */
|
||||||
.score-row-sep {
|
.score-row-sep {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|
@ -1010,31 +960,6 @@ a:hover { text-decoration: underline; }
|
||||||
margin: 0.05rem 0;
|
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 — status, hints, buttons on cream ────────────── */
|
||||||
.game-bottom-strip {
|
.game-bottom-strip {
|
||||||
background: var(--ui-parchment);
|
background: var(--ui-parchment);
|
||||||
|
|
@ -2365,7 +2290,6 @@ a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
/* Strip peg overrides */
|
/* Strip peg overrides */
|
||||||
.players-strip .peg-track { gap: 3px; direction: ltr; }
|
.players-strip .peg-track { gap: 3px; direction: ltr; }
|
||||||
.players-strip .peg-hole { width: 12px; height: 12px; }
|
|
||||||
.players-strip .peg-hole.filled {
|
.players-strip .peg-hole.filled {
|
||||||
background: #5aab38; border-color: #3a7828;
|
background: #5aab38; border-color: #3a7828;
|
||||||
box-shadow: 0 0 5px rgba(90,171,56,0.55);
|
box-shadow: 0 0 5px rgba(90,171,56,0.55);
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,13 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
let dice = vs.dice;
|
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 ─────────────────────────────────────────────────────────
|
// ── Button senders ─────────────────────────────────────────────────────────
|
||||||
let cmd_tx_go = cmd_tx.clone();
|
let cmd_tx_go = cmd_tx.clone();
|
||||||
|
|
|
||||||
|
|
@ -183,19 +183,6 @@ pub fn MergedScorePanel(
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,8 @@ use backbone_lib::traits::{BackEndArchitecture, BackendCommand};
|
||||||
use crate::app::{GameUiState, NetCommand, PauseReason, Screen};
|
use crate::app::{GameUiState, NetCommand, PauseReason, Screen};
|
||||||
use crate::game::trictrac::backend::TrictracBackend;
|
use crate::game::trictrac::backend::TrictracBackend;
|
||||||
use crate::game::trictrac::bot_local::bot_decide;
|
use crate::game::trictrac::bot_local::bot_decide;
|
||||||
use crate::game::trictrac::types::{
|
use crate::game::trictrac::types::{JanEntry, ScoredEvent, SerStage, SerTurnStage, ViewState};
|
||||||
JanEntry, ScoredEvent, SerStage, SerTurnStage, ViewState,
|
use backbone_lib::platform::sleep_ms;
|
||||||
};
|
|
||||||
use trictrac_store::CheckerMove;
|
use trictrac_store::CheckerMove;
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
@ -124,6 +123,7 @@ async fn run_local_bot_game_loop(
|
||||||
match bot_decide(backend.get_game(), pgr.as_ref()) {
|
match bot_decide(backend.get_game(), pgr.as_ref()) {
|
||||||
None => break,
|
None => break,
|
||||||
Some(action) => {
|
Some(action) => {
|
||||||
|
sleep_ms(500).await;
|
||||||
backend.inform_rpc(1, action);
|
backend.inform_rpc(1, action);
|
||||||
for cmd in backend.drain_commands() {
|
for cmd in backend.drain_commands() {
|
||||||
if let BackendCommand::Delta(delta) = cmd {
|
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.
|
/// 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 prev_score = &prev.scores[player_id as usize];
|
||||||
let next_score = &next.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
|
/// Compares the previous and next ViewState to decide whether the transition
|
||||||
/// warrants a confirmation pause.
|
/// 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;
|
let opponent_id = 1 - player_id;
|
||||||
|
|
||||||
if next.stage == SerStage::PreGameRoll {
|
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 {
|
if next.dice != prev.dice {
|
||||||
return Some(PauseReason::AfterOpponentRoll);
|
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);
|
return Some(PauseReason::AfterOpponentGo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue