From bf220606149b46d57f5805f300dba8831412a7a6 Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Sat, 2 May 2026 20:02:47 +0200 Subject: [PATCH] fix(web client): exit dice used --- Cargo.lock | 50 ++++++++++++++++++++++++ clients/web/Cargo.toml | 3 ++ clients/web/src/game/components/board.rs | 41 ++++++++++++++----- clients/web/src/game/trictrac/backend.rs | 4 +- justfile | 28 ++----------- store/src/game_rules_moves.rs | 35 ++++++++--------- 6 files changed, 106 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 517e14f..07b7830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5339,6 +5339,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -8707,6 +8717,7 @@ dependencies = [ "trictrac-store", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-bindgen-test", "web-sys", ] @@ -9158,6 +9169,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-bindgen-test" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb55e2540ad1c56eec35fd63e2aea15f83b11ce487fd2de9ad11578dfc047ea" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf0ca1bd612b988616bac1ab34c4e4290ef18f7148a1d8b7f31c150080e9295" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cda5ecc67248c48d3e705d3e03e00af905769b78b9d2a1678b663b8b9d4472" + [[package]] name = "wasm-encoder" version = "0.244.0" diff --git a/clients/web/Cargo.toml b/clients/web/Cargo.toml index 71af23b..f184a28 100644 --- a/clients/web/Cargo.toml +++ b/clients/web/Cargo.toml @@ -43,3 +43,6 @@ web-sys = { version = "0.3", features = [ "Navigator", "Location", ] } + +[dev-dependencies] +wasm-bindgen-test = "0.3" diff --git a/clients/web/src/game/components/board.rs b/clients/web/src/game/components/board.rs index b058ed0..b5f6c8f 100644 --- a/clients/web/src/game/components/board.rs +++ b/clients/web/src/game/components/board.rs @@ -43,7 +43,13 @@ 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 { + let dist = if to == 0 { + if from > 18 { + (25 as u8).saturating_sub(from) + } else { + from.saturating_sub(0) + } + } else if from < to { to.saturating_sub(from) } else { from.saturating_sub(to) @@ -52,7 +58,7 @@ fn bar_matched_dice_used(staged: &[(u8, u8)], dice: (u8, u8)) -> (bool, bool) { d0 = true; } else if !d1 && dist == dice.1 { d1 = true; - } else if !d0 { + } else if !d0 && dist <= dice.0 && dice.0 <= dice.1 { d0 = true; } else { d1 = true; @@ -280,7 +286,7 @@ pub fn Board( let is_white = player_id == 0; let hovered_moves = use_context::>>(); - // Exit-eligible (§8c): all the player's checkers are in their last jan. + // Exit-eligible: all the player's checkers are in their last jan. // White last jan = fields 19-24 (board indices 18-23, positive values). // Black last jan = fields 1-6 (board indices 0-5, negative values). let board_snapshot = view_state.board; @@ -447,13 +453,14 @@ pub fn Board( } else { let origins = valid_origins_for(&seqs_k, &staged); if origins.iter().any(|&o| o == field_num) { - let dests = valid_dests_for(&seqs_k, &staged, field_num); - if !dests.is_empty() && dests.iter().all(|&d| d == 0) { - // All destinations are exits: auto-stage - staged_moves.update(|v| v.push((field_num, 0))); - } else { - selected_origin.set(Some(field_num)); - } + selected_origin.set(Some(field_num)); + // let dests = valid_dests_for(&seqs_k, &staged, field_num); + // if !dests.is_empty() && dests.iter().all(|&d| d == 0) { + // // All destinations are exits: auto-stage + // staged_moves.update(|v| v.push((field_num, 0))); + // } else { + // selected_origin.set(Some(field_num)); + // } } } } @@ -677,3 +684,17 @@ pub fn Board( } } + +#[cfg(test)] +mod tests { + use super::*; + use wasm_bindgen_test::wasm_bindgen_test; + + #[wasm_bindgen_test] + fn test_bar_matched_dice_used() { + assert_eq!((true, false), bar_matched_dice_used(&[(22, 24)], (2, 3))); + assert_eq!((false, true), bar_matched_dice_used(&[(22, 0)], (2, 3))); + assert_eq!((false, true), bar_matched_dice_used(&[(24, 0)], (5, 1))); + assert_eq!((true, false), bar_matched_dice_used(&[(24, 0)], (1, 5))); + } +} diff --git a/clients/web/src/game/trictrac/backend.rs b/clients/web/src/game/trictrac/backend.rs index ca28204..3581057 100644 --- a/clients/web/src/game/trictrac/backend.rs +++ b/clients/web/src/game/trictrac/backend.rs @@ -1,7 +1,7 @@ use backbone_lib::traits::{BackEndArchitecture, BackendCommand}; use trictrac_store::{Dice, DiceRoller, GameEvent, GameState, TurnStage}; -use super::types::{GameDelta, PlayerAction, PreGameRollState, SerStage, ViewState}; +use super::types::{GameDelta, PlayerAction, PreGameRollState, SerStage, SerTurnStage, ViewState}; // Store PlayerId (u64) values used for the two players. const HOST_PLAYER_ID: u64 = 1; @@ -289,7 +289,7 @@ impl BackEndArchitecture for TrictracBackend #[cfg(test)] mod tests { use super::*; - use super::types::{SerStage, SerTurnStage}; + use super::{SerStage, SerTurnStage}; use backbone_lib::traits::BackEndArchitecture; fn make_backend() -> TrictracBackend { diff --git a/justfile b/justfile index 507fe00..b308095 100644 --- a/justfile +++ b/justfile @@ -13,6 +13,9 @@ runcli: dev: trunk serve +test-web: + wasm-pack test --node clients/web + [working-directory: 'clients/web'] build: trunk build --release @@ -25,31 +28,6 @@ build: run-relay: ./relay-server -# Legacy targets kept for reference during transition -[working-directory: 'clients/web-game'] -dev-game: - trunk serve - -[working-directory: 'clients/web-game'] -build-game: - trunk build --release - cp dist/index.html ../../deploy/trictrac.html - cp dist/*.wasm ../../deploy/ - cp dist/*.js ../../deploy/ - cp dist/*.css ../../deploy/ - -[working-directory: 'clients/web-user-portal'] -dev-portal: - trunk serve - -[working-directory: 'clients/web-user-portal'] -build-portal: - trunk build --release - cp dist/index.html ../../deploy/portal.html - cp dist/*.wasm ../../deploy/ - cp dist/*.js ../../deploy/ - cp dist/*.css ../../deploy/ - build-relay: CARGO_PROFILE_RELEASE_OPT_LEVEL=3 cargo build -p relay-server --release mkdir -p deploy diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 660acb7..e5af72b 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -260,8 +260,7 @@ impl MoveRules { // A chained move (tout d'une): the first destination is a resting field. // Exception: a resting field in the opponent's big jan (13-18) is allowed // during a chained move to pass into the return jan. - let is_chained = - moves.1.get_from() != 0 && moves.0.get_to() == moves.1.get_from(); + let is_chained = moves.1.get_from() != 0 && moves.0.get_to() == moves.1.get_from(); if !is_chained { let to0 = moves.0.get_to(); @@ -756,6 +755,8 @@ impl MoveRules { #[cfg(test)] mod tests { + use anyhow::Ok; + use super::*; #[test] @@ -887,6 +888,20 @@ mod tests { state.moves_allowed(&moves) ); + // on peut sortir une dame avec un nombre exact, même si on peut en jouer une avec un nombre défaillant + state.board.set_positions( + &Color::White, + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 2, 0, + ], + ); + state.dice.values = (2, 5); + let moves = ( + CheckerMove::new(20, 0).unwrap(), + CheckerMove::new(23, 0).unwrap(), + ); + assert!(state.moves_allowed(&moves).is_ok()); + // on doit jouer le nombre excédant le plus éloigné state.board.set_positions( &Color::White, @@ -1489,22 +1504,6 @@ mod tests { state.get_possible_moves_sequences(true, vec![]) ); - state.board.set_positions( - &Color::White, - [ - -8, -4, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 2, 2, 2, - ], - ); - state.dice.values = (1, 4); - let moves = ( - CheckerMove::new(21, 22).unwrap(), - CheckerMove::new(22, 0).unwrap(), - ); - assert_eq!( - vec![moves], - state.get_possible_moves_sequences(true, vec![]) - ); - state.dice.values = (5, 3); state.board.set_positions( &crate::Color::White,