diff --git a/Cargo.lock b/Cargo.lock index 42fc19e..b19ee85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,7 +1397,6 @@ dependencies = [ "futures", "getrandom 0.3.4", "gloo-storage", - "gloo-timers", "leptos", "leptos_i18n", "rand 0.9.2", @@ -1405,7 +1404,6 @@ dependencies = [ "serde_json", "trictrac-store", "wasm-bindgen-futures", - "web-sys", ] [[package]] diff --git a/client_web/Cargo.toml b/client_web/Cargo.toml index db62e44..3e648ea 100644 --- a/client_web/Cargo.toml +++ b/client_web/Cargo.toml @@ -17,17 +17,9 @@ serde_json = "1" futures = "0.3" rand = "0.9" gloo-storage = "0.3" -gloo-timers = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" # getrandom 0.3 requires an explicit WASM backend; "wasm_js" uses window.crypto.getRandomValues. # Must be a direct dependency (not just transitive) for the feature to take effect. getrandom = { version = "0.3", features = ["wasm_js"] } -web-sys = { version = "0.3", features = [ - "Document", - "Element", - "HtmlElement", - "CssStyleDeclaration", - "DomTokenList", -] } diff --git a/client_web/assets/style.css b/client_web/assets/style.css index f14aa94..3752d4d 100644 --- a/client_web/assets/style.css +++ b/client_web/assets/style.css @@ -455,20 +455,20 @@ body { /* ── Die face (SVG) ─────────────────────────────────────────────────── */ -/* §5a — vigorous tumble: die bounces in from a random rotation */ +/* §5a — vigorous tumble: simulates the die being shaken and thrown (§5b cup tips first) */ @keyframes die-tumble { - 0% { transform: rotate(-45deg) scale(0.4) translateY(-8px); opacity: 0; } - 25% { transform: rotate(18deg) scale(1.22) translateY(0); opacity: 1; } - 45% { transform: rotate(-10deg) scale(0.91); } - 62% { transform: rotate(6deg) scale(1.06); } - 76% { transform: rotate(-3deg) scale(0.98); } - 88% { transform: rotate(1.5deg) scale(1.01); } + 0% { transform: rotate(-30deg) scale(0.55); opacity: 0; } + 20% { transform: rotate(14deg) scale(1.18); opacity: 1; } + 40% { transform: rotate(-8deg) scale(0.93); } + 60% { transform: rotate(5deg) scale(1.05); } + 78% { transform: rotate(-3deg) scale(0.99); } + 92% { transform: rotate(1deg) scale(1.01); } 100% { transform: rotate(0deg) scale(1); opacity: 1; } } .die-face { filter: drop-shadow(0 2px 3px rgba(0,0,0,0.3)); - animation: die-tumble 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both; + animation: die-tumble 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) 0.25s both; } .die-face rect { @@ -482,11 +482,47 @@ body { transition: fill 0.18s; } -/* Bar die slot — centered in the board bar */ -.bar-die-slot { +/* ── Dice cup (§5b) — lives in the center board bar ────────────────── */ +.bar-cup-slot { display: flex; + flex-direction: column; align-items: center; - justify-content: center; + gap: 8px; +} + +/* Cup shape: wider opening at top, narrower base (like a cornet/tumbler) */ +.bar-cup { + width: 36px; + height: 42px; + background: linear-gradient(160deg, #4a2810 0%, #1e0c04 100%); + clip-path: polygon(0% 0%, 100% 0%, 88% 100%, 12% 100%); + box-shadow: inset 0 -2px 4px rgba(0,0,0,0.5), inset 2px 0 3px rgba(255,255,255,0.04); + transform-origin: top center; + transition: transform 0.38s cubic-bezier(0.25, 0.46, 0.45, 0.94); + position: relative; + flex-shrink: 0; +} +/* Gilt rim at the cup opening */ +.bar-cup::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 4px; + background: linear-gradient(90deg, transparent, rgba(200,164,72,0.35), transparent); + clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); +} +/* Cup poured: tips 105° clockwise (opening rotates downward-right, dice fall out) */ +.bar-cup.cup-poured { + transform: rotate(105deg); +} + +/* Die slot: appears below the cup after it tips; animation delayed to sync with cup */ +@keyframes die-fall-in { + from { opacity: 0; transform: translateY(-12px); } + to { opacity: 1; transform: translateY(0); } +} +.bar-die-slot { + animation: die-fall-in 0.22s ease-out 0.18s both; } /* Double glow (§5c) */ @@ -876,46 +912,23 @@ body { justify-content: center; font-size: 0.78rem; font-weight: 600; + border: 2px solid var(--checker-ring); + box-shadow: + inset 0 2px 5px rgba(255,255,255,0.35), + inset 0 -1px 3px rgba(0,0,0,0.2), + 0 2px 4px rgba(0,0,0,0.35); flex-shrink: 0; } .checker + .checker { margin-top: -4px; } .checker.white { - background-image: - radial-gradient(ellipse 50% 35% at 36% 30%, - rgba(255,255,255,0.65) 0%, transparent 100%), - radial-gradient(circle, - transparent 68%, rgba(160,130,70,0.22) 68.5%, - rgba(160,130,70,0.22) 71.5%, transparent 72%), - radial-gradient(circle, - transparent 43%, rgba(160,130,70,0.17) 43.5%, - rgba(160,130,70,0.17) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, - #ffffff 0%, var(--checker-white) 52%, #c0b288 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: - 0 2px 6px rgba(0,0,0,0.4), - inset 0 -1px 3px rgba(0,0,0,0.15); + background: radial-gradient(circle at 38% 32%, #ffffff, var(--checker-white) 65%, #d8cdb0); color: #443322; } .checker.black { - background-image: - radial-gradient(ellipse 40% 28% at 36% 30%, - rgba(110,65,30,0.38) 0%, transparent 100%), - radial-gradient(circle, - transparent 68%, rgba(200,164,72,0.18) 68.5%, - rgba(200,164,72,0.18) 71.5%, transparent 72%), - radial-gradient(circle, - transparent 43%, rgba(200,164,72,0.13) 43.5%, - rgba(200,164,72,0.13) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, - #4a2e1a 0%, #1c1008 45%, var(--checker-black) 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: - 0 2px 6px rgba(0,0,0,0.55), - inset 0 -1px 3px rgba(0,0,0,0.4); + background: radial-gradient(circle at 38% 32%, #444444, #1c1008 65%, var(--checker-black)); color: #c8b898; } @@ -999,24 +1012,6 @@ body { letter-spacing: 0.14em; } -/* ── §4a — Checker slide animation ─────────────────────────────────── */ -@keyframes checker-slide-in { - from { transform: translate(var(--slide-dx, 0px), var(--slide-dy, 0px)); } - to { transform: none; } -} -.checker-stack.sliding { - animation: checker-slide-in 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} -/* Lift the field that owns a sliding stack above its siblings so the - checker doesn't slide under adjacent fields (isolation:isolate traps - z-index within each field's stacking context, so we must elevate the - field itself). */ -.field:has(.checker-stack.sliding) { - isolation: auto; - z-index: 10; - position: relative; -} - /* ── Checker lift on selected field (§4b) ───────────────────────────── */ .field.selected .checker-stack { transform: translateY(-5px); diff --git a/client_web/src/app.rs b/client_web/src/app.rs index 4ae4ad1..8f98559 100644 --- a/client_web/src/app.rs +++ b/client_web/src/app.rs @@ -13,7 +13,6 @@ use crate::i18n::I18nContextProvider; use crate::trictrac::backend::TrictracBackend; use crate::trictrac::bot_local::bot_decide; use crate::trictrac::types::{GameDelta, JanEntry, PlayerAction, ScoredEvent, SerTurnStage, ViewState}; -use trictrac_store::CheckerMove; use std::collections::VecDeque; @@ -36,8 +35,6 @@ pub struct GameUiState { /// Points scored by this player in the transition to this state (if any). pub my_scored_event: Option, pub opp_scored_event: Option, - /// Checker moves to animate on this render. None when board is unchanged. - pub last_moves: Option<(CheckerMove, CheckerMove)>, } /// Reason the UI is paused waiting for the player to click Continue. @@ -275,7 +272,6 @@ pub fn App() -> impl IntoView { pause_reason: None, my_scored_event: None, opp_scored_event: None, - last_moves: compute_last_moves(&prev_vs, &vs), }, pending, screen, @@ -342,7 +338,6 @@ async fn run_local_bot_game( pause_reason: None, my_scored_event: None, opp_scored_event: None, - last_moves: None, })); loop { @@ -366,7 +361,6 @@ async fn run_local_bot_game( pause_reason: None, my_scored_event: scored, opp_scored_event: opp_scored, - last_moves: compute_last_moves(&prev_vs, &vs), })); } Some(NetCommand::PlayVsBot) => return true, @@ -395,7 +389,6 @@ async fn run_local_bot_game( pause_reason: None, my_scored_event: None, opp_scored_event: None, - last_moves: compute_last_moves(&prev_vs, &vs), }, pending, screen, @@ -406,22 +399,6 @@ async fn run_local_bot_game( } } -/// Returns the checker moves to animate when the board changed between two ViewStates. -/// Returns `None` when the board is unchanged or no real moves were recorded. -fn compute_last_moves(prev: &ViewState, next: &ViewState) -> Option<(CheckerMove, CheckerMove)> { - if prev.board == next.board { - return None; - } - let (m1, m2) = next.dice_moves; - if m1 == CheckerMove::default() && m2 == CheckerMove::default() { - // Relies on the engine invariant: dice_moves is updated atomically with the board - // change in the Move event handler. Any future engine path that mutates the board - // without setting dice_moves would bypass this guard and replay stale animation. - return None; - } - Some((m1, m2)) -} - /// Computes a scoring event for `player_id` by comparing the previous and next /// ViewState. Returns `None` when no points changed for that player. fn compute_scored_event(prev: &ViewState, next: &ViewState, player_id: u16) -> Option { @@ -494,9 +471,7 @@ fn push_or_show( ..new_state.clone() }); }); - // Animation belongs to the buffered confirmation step; clear it on the - // fallback live state so it doesn't fire again after the queue drains. - screen.set(Screen::Playing(GameUiState { last_moves: None, ..new_state })); + screen.set(Screen::Playing(new_state)); } else { // No pause: show scoring directly on the live state. screen.set(Screen::Playing(GameUiState { @@ -553,7 +528,6 @@ mod tests { scores: [score(), score()], dice, dice_jans: Vec::new(), - dice_moves: (CheckerMove::default(), CheckerMove::default()), } } diff --git a/client_web/src/components/board.rs b/client_web/src/components/board.rs index 8483d1c..7eceb53 100644 --- a/client_web/src/components/board.rs +++ b/client_web/src/components/board.rs @@ -1,8 +1,8 @@ use leptos::prelude::*; use trictrac_store::CheckerMove; -use super::die::Die; use crate::trictrac::types::{SerTurnStage, ViewState}; +use super::die::Die; /// Field numbers in visual display order (left-to-right for each quarter), white's perspective. const TOP_LEFT_W: [u8; 6] = [13, 14, 15, 16, 17, 18]; @@ -20,21 +20,17 @@ const BOT_RIGHT_B: [u8; 6] = [18, 17, 16, 15, 14, 13]; /// Returns true when `field_num` is the rest corner for this perspective. #[allow(dead_code)] fn is_rest_corner(field_num: u8, is_white: bool) -> bool { - if is_white { - field_num == 12 - } else { - field_num == 13 - } + if is_white { field_num == 12 } else { field_num == 13 } } /// Zone CSS class for a field number (field coordinates are always White's 1-24). fn field_zone_class(field_num: u8) -> &'static str { match field_num { - 1..=6 => "zone-petit", - 7..=12 => "zone-grand", + 1..=6 => "zone-petit", + 7..=12 => "zone-grand", 13..=18 => "zone-retour", 19..=24 => "zone-dernier", - _ => "", + _ => "", } } @@ -43,20 +39,11 @@ fn bar_matched_dice_used(staged: &[(u8, u8)], dice: (u8, u8)) -> (bool, bool) { let mut d0 = false; let mut d1 = false; for &(from, to) in staged { - let dist = if from < to { - to.saturating_sub(from) - } else { - from.saturating_sub(to) - }; - if !d0 && dist == dice.0 { - d0 = true; - } else if !d1 && dist == dice.1 { - d1 = true; - } else if !d0 { - d0 = true; - } else { - d1 = true; - } + let dist = if from < to { to.saturating_sub(from) } else { from.saturating_sub(to) }; + if !d0 && dist == dice.0 { d0 = true; } + else if !d1 && dist == dice.1 { d1 = true; } + else if !d0 { d0 = true; } + else { d1 = true; } } (d0, d1) } @@ -85,8 +72,7 @@ fn displayed_value( /// Fields whose checkers may be selected as the next origin given already-staged moves. fn valid_origins_for(seqs: &[(CheckerMove, CheckerMove)], staged: &[(u8, u8)]) -> Vec { let mut v: Vec = match staged.len() { - 0 => seqs - .iter() + 0 => seqs.iter() .map(|(m1, _)| m1.get_from() as u8) .filter(|&f| f != 0) .collect(), @@ -117,46 +103,28 @@ fn field_center(f: usize, is_white: bool) -> Option<(f32, f32)> { match f { 13..=18 => (f - 13, false, true), 19..=24 => (f - 19, true, true), - 7..=12 => (12 - f, false, false), - 1..=6 => (6 - f, true, false), - _ => return None, + 7..=12 => (12 - f, false, false), + 1..=6 => (6 - f, true, false), + _ => return None, } } else { match f { - 1..=6 => (f - 1, false, true), - 7..=12 => (f - 7, true, true), + 1..=6 => (f - 1, false, true), + 7..=12 => (f - 7, true, true), 19..=24 => (24 - f, false, false), 13..=18 => (18 - f, true, false), - _ => return None, + _ => return None, } }; // Left-quarter field i center x: 4(pad) + i*62 + 30(half field) = 34 + 62i // Right-quarter: 4 + 370(quarter) + 4(gap) + 68(bar) + 4(gap) + i*62 + 30 = 480 + 62i - let x = if right { - 480.0 + qi as f32 * 62.0 - } else { - 34.0 + qi as f32 * 62.0 - }; + let x = if right { 480.0 + qi as f32 * 62.0 } else { 34.0 + qi as f32 * 62.0 }; // Top row triangle base (wide end) ≈ y=30; bot row triangle base ≈ y=358. // (Top base: 4pad + 4field-pad + 20half-checker ≈ 28; Bot base: 388 − 4pad − 4field-pad − 20 ≈ 360) let y = if top { 30.0 } else { 358.0 }; Some((x, y)) } -#[cfg(target_arch = "wasm32")] -fn apply_slide_animation(to_field: usize, dx: f32, dy: f32) { - use web_sys::wasm_bindgen::JsCast; - let Some(doc) = web_sys::window().and_then(|w| w.document()) else { return }; - let selector = format!("#field-{} .checker-stack", to_field); - let Ok(Some(el)) = doc.query_selector(&selector) else { return }; - let Ok(html) = el.dyn_into::() else { return }; - let style = html.style(); - style.set_property("--slide-dx", &format!("{:.1}px", dx)).ok(); - style.set_property("--slide-dy", &format!("{:.1}px", dy)).ok(); - html.class_list().add_1("sliding").ok(); -} - - /// SVG `` element drawing one arrow (shadow + gold) from `fp` to `tp`. fn arrow_svg(fp: (f32, f32), tp: (f32, f32)) -> AnyView { let (x1, y1) = fp; @@ -185,21 +153,15 @@ fn arrow_svg(fp: (f32, f32), tp: (f32, f32)) -> AnyView { let bary = y2 - ny * ah; let pts = format!( "{:.1},{:.1} {:.1},{:.1} {:.1},{:.1}", - x2, - y2, - bx + px * aw, - bary + py * aw, - bx - px * aw, - bary - py * aw, + x2, y2, + bx + px * aw, bary + py * aw, + bx - px * aw, bary - py * aw, ); let shadow_pts = format!( "{:.1},{:.1} {:.1},{:.1} {:.1},{:.1}", - x2, - y2, - bx + px * (aw + 1.5), - bary + py * (aw + 1.5), - bx - px * (aw + 1.5), - bary - py * (aw + 1.5), + x2, y2, + bx + px * (aw + 1.5), bary + py * (aw + 1.5), + bx - px * (aw + 1.5), bary - py * (aw + 1.5), ); view! { @@ -225,14 +187,9 @@ fn arrow_svg(fp: (f32, f32), tp: (f32, f32)) -> AnyView { /// Valid destinations for a selected origin given already-staged moves. /// May include 0 (exit); callers handle that case. -fn valid_dests_for( - seqs: &[(CheckerMove, CheckerMove)], - staged: &[(u8, u8)], - origin: u8, -) -> Vec { +fn valid_dests_for(seqs: &[(CheckerMove, CheckerMove)], staged: &[(u8, u8)], origin: u8) -> Vec { let mut v: Vec = match staged.len() { - 0 => seqs - .iter() + 0 => seqs.iter() .filter(|(m1, _)| m1.get_from() as u8 == origin) .map(|(m1, _)| m1.get_to() as u8) .collect(), @@ -265,17 +222,11 @@ pub fn Board( /// All valid two-move sequences for this turn (empty when not in move stage). valid_sequences: Vec<(CheckerMove, CheckerMove)>, /// Dice to display in the center bars; None means dice not yet rolled (cups shown upright). - #[prop(default = None)] - bar_dice: Option<(u8, u8)>, + #[prop(default = None)] bar_dice: Option<(u8, u8)>, /// Whether we're in the move stage (determines used/unused die appearance). - #[prop(default = false)] - bar_is_move: bool, + #[prop(default = false)] bar_is_move: bool, /// Whether the dice are a double (golden glow). - #[prop(default = false)] - bar_is_double: bool, - /// Checker moves to animate on mount (None when board unchanged). - #[prop(default = None)] - last_moves: Option<(CheckerMove, CheckerMove)>, + #[prop(default = false)] bar_is_double: bool, ) -> impl IntoView { let board = view_state.board; let is_move_stage = view_state.active_mp_player == Some(player_id) @@ -294,12 +245,12 @@ pub fn Board( let exit_field_test: fn(u8) -> bool; if is_white { let in_exit: i8 = board_snapshot[18..24].iter().map(|&v| v.max(0)).sum(); - let total: i8 = board_snapshot.iter().map(|&v| v.max(0)).sum(); + let total: i8 = board_snapshot.iter().map(|&v| v.max(0)).sum(); all_in_exit = total > 0 && in_exit == total; exit_field_test = |f| matches!(f, 19..=24); } else { let in_exit: i8 = board_snapshot[0..6].iter().map(|&v| (-v).max(0)).sum(); - let total: i8 = board_snapshot.iter().map(|&v| (-v).max(0)).sum(); + let total: i8 = board_snapshot.iter().map(|&v| (-v).max(0)).sum(); all_in_exit = total > 0 && in_exit == total; exit_field_test = |f| matches!(f, 1..=6); } @@ -319,7 +270,6 @@ pub fn Board( }; view! {
AnyView { - match bar_dice { - None => view! {
}.into_any(), - Some(dice_vals) => { - let die_val = if die_idx == 0 { - dice_vals.0 - } else { - dice_vals.1 - }; - view! { -
- {move || { - let staged = staged_moves.get(); - let (u0, u1) = if bar_is_move { - bar_matched_dice_used(&staged, dice_vals) - } else { - (true, true) - }; - let used = if die_idx == 0 { u0 } else { u1 }; - view! { } - }} -
- } - .into_any() - } + let poured = bar_dice.is_some(); + let cup_cls = if poured { "bar-cup cup-poured" } else { "bar-cup" }; + view! { +
+
+ {bar_dice.map(|dice_vals| { + let die_val = if die_idx == 0 { dice_vals.0 } else { dice_vals.1 }; + view! { +
+ {move || { + let staged = staged_moves.get(); + let (u0, u1) = if bar_is_move { + bar_matched_dice_used(&staged, dice_vals) + } else { + (true, true) + }; + let used = if die_idx == 0 { u0 } else { u1 }; + view! { } + }} +
+ } + })} +
} + .into_any() }; - // §4a — animate checker moves. Deferred to a macrotask so Leptos has time - // to mount the Board's field divs before we query the DOM. - Effect::new(move |_| { - let Some((m1, m2)) = last_moves else { return }; - - // Collect the (to_field, dx, dy) pairs we need before moving into spawn_local. - let mut animations: Vec<(usize, f32, f32)> = Vec::new(); - for m in [m1, m2] { - if m.get_from() == 0 && m.get_to() == 0 { continue; } - let Some((fx, fy)) = field_center(m.get_from(), is_white) else { continue }; - let Some((tx, ty)) = field_center(m.get_to(), is_white) else { continue }; - let dx = fx - tx; - let dy = fy - ty; - if dx.abs() < 1.0 && dy.abs() < 1.0 { continue; } - animations.push((m.get_to(), dx, dy)); - } - - #[cfg(target_arch = "wasm32")] - { - if let Some((to_field, dx, dy)) = animations.first().copied() { - gloo_timers::callback::Timeout::new(0, move || { - apply_slide_animation(to_field, dx, dy); - }).forget(); - } - if let Some((to_field, dx, dy)) = animations.get(1).copied() { - gloo_timers::callback::Timeout::new(300, move || { - apply_slide_animation(to_field, dx, dy); - }).forget(); - } - } - }); - let (tl, tr, bl, br) = if is_white { (&TOP_LEFT_W, &TOP_RIGHT_W, &BOT_LEFT_W, &BOT_RIGHT_W) } else { diff --git a/client_web/src/components/game_screen.rs b/client_web/src/components/game_screen.rs index f5c08b8..6657d94 100644 --- a/client_web/src/components/game_screen.rs +++ b/client_web/src/components/game_screen.rs @@ -119,8 +119,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let is_double_dice = dice.0 == dice.1 && dice.0 != 0; - let last_moves = state.last_moves; - // ── Capture for closures ─────────────────────────────────────────────────── let stage = vs.stage.clone(); let turn_stage = vs.turn_stage.clone(); @@ -216,7 +214,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { bar_dice=show_dice.then_some(dice) bar_is_move=is_move_stage bar_is_double=is_double_dice - last_moves=last_moves /> // ── Side panel (scoring panels only) ───────────────────────── diff --git a/client_web/src/components/scoring.rs b/client_web/src/components/scoring.rs index ab44ec4..d686bf8 100644 --- a/client_web/src/components/scoring.rs +++ b/client_web/src/components/scoring.rs @@ -11,8 +11,12 @@ use super::score_panel::jan_label; fn scoring_jan_row(entry: JanEntry) -> impl IntoView { let i18n = use_i18n(); let hovered = use_context::>>(); - let jan = entry.jan; - let is_double = entry.is_double; + let label = jan_label(&entry.jan); + let double_tag = if entry.is_double { + t_string!(i18n, jan_double).to_owned() + } else { + t_string!(i18n, jan_simple).to_owned() + }; let ways_tag = format!("×{}", entry.ways); let pts_str = format!("+{}", entry.total); let moves_hover = entry.moves.clone(); @@ -31,12 +35,8 @@ fn scoring_jan_row(entry: JanEntry) -> impl IntoView { } } > - {move || jan_label(&jan)} - {move || if is_double { - t_string!(i18n, jan_double).to_owned() - } else { - t_string!(i18n, jan_simple).to_owned() - }} + {label} + {double_tag} {ways_tag} {pts_str}
diff --git a/client_web/src/trictrac/types.rs b/client_web/src/trictrac/types.rs index 82f9a2d..2c5cdd2 100644 --- a/client_web/src/trictrac/types.rs +++ b/client_web/src/trictrac/types.rs @@ -41,8 +41,6 @@ pub struct ViewState { pub dice: (u8, u8), /// Jans (scoring events) triggered by the last dice roll. pub dice_jans: Vec, - /// Last two checker moves played; default when no move has occurred yet. - pub dice_moves: (CheckerMove, CheckerMove), } /// One scoring event from a dice roll. @@ -75,7 +73,6 @@ impl ViewState { ], dice: (0, 0), dice_jans: Vec::new(), - dice_moves: (CheckerMove::default(), CheckerMove::default()), } } @@ -169,7 +166,6 @@ impl ViewState { scores: [score_for(host_store_id), score_for(guest_store_id)], dice: (gs.dice.values.0, gs.dice.values.1), dice_jans, - dice_moves: gs.dice_moves, } } }