diff --git a/client_web/locales/en.json b/client_web/locales/en.json index f390ce4..94c2baa 100644 --- a/client_web/locales/en.json +++ b/client_web/locales/en.json @@ -7,7 +7,7 @@ "waiting_for_opponent": "Waiting for opponent…", "your_turn_roll": "Your turn — roll the dice", "hold_or_go": "Hold or Go?", - "select_move": "Move a checker ({{ n }} of 2)", + "select_move": "Select move {{ n }} of 2", "your_turn": "Your turn", "opponent_turn": "Opponent's turn", "room_label": "Room: {{ id }}", diff --git a/client_web/locales/fr.json b/client_web/locales/fr.json index 910d7c0..397713d 100644 --- a/client_web/locales/fr.json +++ b/client_web/locales/fr.json @@ -7,7 +7,7 @@ "waiting_for_opponent": "En attente de l'adversaire…", "your_turn_roll": "À votre tour — lancez les dés", "hold_or_go": "Tenir ou s'en aller ?", - "select_move": "Déplacez une dame ({{ n }} sur 2)", + "select_move": "Sélectionner le coup {{ n }} sur 2", "your_turn": "Votre tour", "opponent_turn": "Tour de l'adversaire", "room_label": "Salle : {{ id }}", diff --git a/client_web/src/app.rs b/client_web/src/app.rs index 5ade2c3..4ae4ad1 100644 --- a/client_web/src/app.rs +++ b/client_web/src/app.rs @@ -12,9 +12,7 @@ use crate::components::{ConnectingScreen, GameScreen, LoginScreen}; 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 crate::trictrac::types::{GameDelta, JanEntry, PlayerAction, ScoredEvent, SerTurnStage, ViewState}; use trictrac_store::CheckerMove; use std::collections::VecDeque; @@ -196,9 +194,7 @@ pub fn App() -> impl IntoView { if remote_config.is_none() { loop { let restart = run_local_bot_game(screen, &mut cmd_rx, pending).await; - if !restart { - break; - } + if !restart { break; } } pending.update(|q| q.clear()); screen.set(Screen::Login { error: None }); @@ -332,12 +328,8 @@ async fn run_local_bot_game( let mut vs = ViewState::default_with_names("You", "Bot"); for cmd in backend.drain_commands() { match cmd { - BackendCommand::ResetViewState => { - vs = backend.get_view_state().clone(); - } - BackendCommand::Delta(delta) => { - vs.apply_delta(&delta); - } + BackendCommand::ResetViewState => { vs = backend.get_view_state().clone(); } + BackendCommand::Delta(delta) => { vs.apply_delta(&delta); } _ => {} } } @@ -448,21 +440,15 @@ fn compute_scored_event(prev: &ViewState, next: &ViewState, player_id: u16) -> O && prev.active_mp_player == Some(player_id) { // My own roll: positive totals are mine. - next.dice_jans - .iter() - .filter(|e| e.total > 0) - .cloned() - .collect() - } else if next.active_mp_player == Some(player_id) && prev.active_mp_player != Some(player_id) { + next.dice_jans.iter().filter(|e| e.total > 0).cloned().collect() + } else if next.active_mp_player == Some(player_id) + && prev.active_mp_player != Some(player_id) + { // Opponent just moved: negative totals (their penalty) are scored for me. next.dice_jans .iter() .filter(|e| e.total < 0) - .map(|e| JanEntry { - total: -e.total, - points_per: -e.points_per, - ..e.clone() - }) + .map(|e| JanEntry { total: -e.total, points_per: -e.points_per, ..e.clone() }) .collect() } else { return None; @@ -510,10 +496,7 @@ fn push_or_show( }); // 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(GameUiState { last_moves: None, ..new_state })); } else { // No pause: show scoring directly on the live state. screen.set(Screen::Playing(GameUiState { @@ -536,7 +519,8 @@ fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) -> Opt return Some(PauseReason::AfterOpponentRoll); } // Was at HoldOrGoChoice, now Move, opponent still active → opponent went. - 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); } @@ -550,18 +534,14 @@ fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) -> Opt None } + #[cfg(test)] mod tests { use super::*; use crate::trictrac::types::{PlayerScore, SerStage, SerTurnStage}; fn score() -> PlayerScore { - PlayerScore { - name: String::new(), - points: 0, - holes: 0, - can_bredouille: false, - } + PlayerScore { name: String::new(), points: 0, holes: 0, can_bredouille: false } } fn vs(dice: (u8, u8), turn_stage: SerTurnStage, active: Option) -> ViewState { @@ -574,7 +554,6 @@ mod tests { dice, dice_jans: Vec::new(), dice_moves: (CheckerMove::default(), CheckerMove::default()), - message: "".into(), } } @@ -582,30 +561,21 @@ mod tests { fn dice_change_is_after_roll() { let prev = vs((0, 0), SerTurnStage::RollDice, Some(1)); let next = vs((3, 5), SerTurnStage::Move, Some(1)); - assert_eq!( - infer_pause_reason(&prev, &next, 0), - Some(PauseReason::AfterOpponentRoll) - ); + assert_eq!(infer_pause_reason(&prev, &next, 0), Some(PauseReason::AfterOpponentRoll)); } #[test] fn hold_to_move_is_after_go() { let prev = vs((3, 5), SerTurnStage::HoldOrGoChoice, Some(1)); let next = vs((3, 5), SerTurnStage::Move, Some(1)); - assert_eq!( - infer_pause_reason(&prev, &next, 0), - Some(PauseReason::AfterOpponentGo) - ); + assert_eq!(infer_pause_reason(&prev, &next, 0), Some(PauseReason::AfterOpponentGo)); } #[test] fn turn_switch_is_after_move() { let prev = vs((3, 5), SerTurnStage::Move, Some(1)); let next = vs((3, 5), SerTurnStage::RollDice, Some(0)); - assert_eq!( - infer_pause_reason(&prev, &next, 0), - Some(PauseReason::AfterOpponentMove) - ); + assert_eq!(infer_pause_reason(&prev, &next, 0), Some(PauseReason::AfterOpponentMove)); } #[test] diff --git a/client_web/src/components/game_screen.rs b/client_web/src/components/game_screen.rs index 8bf9a5b..24042be 100644 --- a/client_web/src/components/game_screen.rs +++ b/client_web/src/components/game_screen.rs @@ -18,7 +18,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let i18n = use_i18n(); let vs = state.view_state.clone(); - let message = format!("{}", vs.message); let player_id = state.player_id; let is_my_turn = vs.active_mp_player == Some(player_id); let is_move_stage = is_my_turn @@ -352,9 +351,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { // ── Player score (below board) ──────────────────────────────────── -
- {format!("{message}")} -
// ── Game-over overlay ───────────────────────────────────────────── {stage_is_ended.then(|| { diff --git a/client_web/src/components/login_screen.rs b/client_web/src/components/login_screen.rs index 1f70887..689d0d7 100644 --- a/client_web/src/components/login_screen.rs +++ b/client_web/src/components/login_screen.rs @@ -40,7 +40,9 @@ pub fn LoginScreen(error: Option) -> impl IntoView {

"Trictrac"

diff --git a/client_web/src/trictrac/backend.rs b/client_web/src/trictrac/backend.rs index 7d60873..e96f080 100644 --- a/client_web/src/trictrac/backend.rs +++ b/client_web/src/trictrac/backend.rs @@ -167,7 +167,6 @@ impl BackEndArchitecture for TrictracBackend moves: (m1, m2), }; if self.game.validate(&event) { - self.game.debug_message = format!("Event {:?} validated", event); let _ = self.game.consume(&event); self.drive_automatic_stages(); } diff --git a/client_web/src/trictrac/bot_local.rs b/client_web/src/trictrac/bot_local.rs index 9b379c8..8941a09 100644 --- a/client_web/src/trictrac/bot_local.rs +++ b/client_web/src/trictrac/bot_local.rs @@ -15,7 +15,7 @@ pub fn bot_decide(game: &GameState) -> Option { } match game.turn_stage { TurnStage::RollDice => Some(PlayerAction::Roll), - TurnStage::HoldOrGoChoice => Some(PlayerAction::Mark), + TurnStage::HoldOrGoChoice => Some(PlayerAction::Go), TurnStage::Move => { let rules = MoveRules::new(&Color::Black, &game.board, game.dice); let sequences = rules.get_possible_moves_sequences(true, vec![]); diff --git a/client_web/src/trictrac/types.rs b/client_web/src/trictrac/types.rs index 927584e..82f9a2d 100644 --- a/client_web/src/trictrac/types.rs +++ b/client_web/src/trictrac/types.rs @@ -43,7 +43,6 @@ pub struct ViewState { pub dice_jans: Vec, /// Last two checker moves played; default when no move has occurred yet. pub dice_moves: (CheckerMove, CheckerMove), - pub message: String, } /// One scoring event from a dice roll. @@ -71,23 +70,12 @@ impl ViewState { turn_stage: SerTurnStage::RollDice, active_mp_player: None, scores: [ - PlayerScore { - name: host_name.to_string(), - points: 0, - holes: 0, - can_bredouille: false, - }, - PlayerScore { - name: guest_name.to_string(), - points: 0, - holes: 0, - can_bredouille: false, - }, + PlayerScore { name: host_name.to_string(), points: 0, holes: 0, can_bredouille: false }, + PlayerScore { name: guest_name.to_string(), points: 0, holes: 0, can_bredouille: false }, ], dice: (0, 0), dice_jans: Vec::new(), dice_moves: (CheckerMove::default(), CheckerMove::default()), - message: "".into(), } } @@ -98,21 +86,25 @@ impl ViewState { /// Convert a store `GameState` to a `ViewState`. /// `host_store_id` and `guest_store_id` are the trictrac `PlayerId`s assigned /// to the host (mp player 0) and guest (mp player 1) respectively. - pub fn from_game_state(gs: &GameState, host_store_id: u64, guest_store_id: u64) -> Self { + pub fn from_game_state( + gs: &GameState, + host_store_id: u64, + guest_store_id: u64, + ) -> Self { let board_vec = gs.board.to_vec(); let board: [i8; 24] = board_vec.try_into().expect("board is always 24 fields"); let stage = match gs.stage { Stage::PreGame => SerStage::PreGame, - Stage::InGame => SerStage::InGame, - Stage::Ended => SerStage::Ended, + Stage::InGame => SerStage::InGame, + Stage::Ended => SerStage::Ended, }; let turn_stage = match gs.turn_stage { - TurnStage::RollDice => SerTurnStage::RollDice, - TurnStage::RollWaiting => SerTurnStage::RollWaiting, - TurnStage::MarkPoints => SerTurnStage::MarkPoints, + TurnStage::RollDice => SerTurnStage::RollDice, + TurnStage::RollWaiting => SerTurnStage::RollWaiting, + TurnStage::MarkPoints => SerTurnStage::MarkPoints, TurnStage::HoldOrGoChoice => SerTurnStage::HoldOrGoChoice, - TurnStage::Move => SerTurnStage::Move, + TurnStage::Move => SerTurnStage::Move, TurnStage::MarkAdvPoints => SerTurnStage::MarkAdvPoints, }; @@ -133,12 +125,7 @@ impl ViewState { holes: p.holes, can_bredouille: p.can_bredouille, }) - .unwrap_or_else(|| PlayerScore { - name: String::new(), - points: 0, - holes: 0, - can_bredouille: false, - }) + .unwrap_or_else(|| PlayerScore { name: String::new(), points: 0, holes: 0, can_bredouille: false }) }; // is_double for scoring: dice show the same value (both dice identical). @@ -147,16 +134,13 @@ impl ViewState { // Build JanEntry list from the PossibleJans map. let empty_move = CheckerMove::new(0, 0).unwrap_or_default(); - let mut dice_jans: Vec = gs - .dice_jans + let mut dice_jans: Vec = gs.dice_jans .iter() .map(|(jan, moves)| { // HelplessMan: is_double = true only when *both* dice are unplayable // (the moves list contains a single (empty, empty) sentinel). let is_double = if *jan == Jan::HelplessMan { - moves - .first() - .map(|&(m1, m2)| m1 == empty_move && m2 == empty_move) + moves.first().map(|&(m1, m2)| m1 == empty_move && m2 == empty_move) .unwrap_or(false) } else { dice_are_double @@ -186,7 +170,6 @@ impl ViewState { dice: (gs.dice.values.0, gs.dice.values.1), dice_jans, dice_moves: gs.dice_moves, - message: gs.get_debug_message(), } } } diff --git a/store/src/game.rs b/store/src/game.rs index da074b9..9c5233f 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -80,7 +80,6 @@ pub struct GameState { roll_first: bool, // NOTE: add to a Setting struct if other fields needed pub schools_enabled: bool, - pub debug_message: String, } // implement Display trait @@ -120,7 +119,6 @@ impl Default for GameState { dice_jans: PossibleJans::default(), roll_first: true, schools_enabled: false, - debug_message: "".into(), } } } @@ -149,11 +147,6 @@ impl GameState { game } - pub fn get_debug_message(&self) -> String { - // format!("{:?}", self.history.last()) - format!("{:?}", self.debug_message) - } - pub fn mirror(&self) -> GameState { let mirrored_active_player = if self.active_player_id == 1 { 2 } else { 1 }; let mut mirrored_players = HashMap::new(); @@ -178,7 +171,6 @@ impl GameState { dice_jans: self.dice_jans.mirror(), roll_first: self.roll_first, schools_enabled: self.schools_enabled, - debug_message: self.debug_message.clone(), } } @@ -602,9 +594,8 @@ impl GameState { dice_points: (0, 0), dice_moves: (CheckerMove::default(), CheckerMove::default()), dice_jans: PossibleJans::default(), - roll_first: false, // Assume not first roll - schools_enabled: false, // Assume disabled - debug_message: "".into(), // Assume disabled + roll_first: false, // Assume not first roll + schools_enabled: false, // Assume disabled }) } diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 7554034..1759c62 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -955,15 +955,15 @@ mod tests { ); state.board.set_positions( - &Color::Black, + &Color::White, [ - 10, 0, 0, 0, -1, 0, 2, 0, 0, 0, 1, 2, 0, -1, -1, 0, 2, 0, 0, 0, 0, 0, 0, -10, + 6, 0, 0, 0, 0, 0, 2, 2, 1, 2, 0, 2, 0, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], ); - state.dice.values = (4, 1); + state.dice.values = (3, 3); let moves = ( - CheckerMove::new(15, 14).unwrap().mirror(), - CheckerMove::new(14, 10).unwrap().mirror(), + CheckerMove::new(14, 11).unwrap(), + CheckerMove::new(14, 11).unwrap(), ); assert_eq!( Err(MoveError::OpponentCanFillQuarter), @@ -1277,7 +1277,6 @@ mod tests { ); assert!(!state.moves_possible(&moves)); - // Chaned moves: can't rest on a field occupied by one opponent's checker state.board.set_positions( &Color::White, [ @@ -1289,7 +1288,7 @@ mod tests { CheckerMove::new(10, 15).unwrap(), CheckerMove::new(15, 20).unwrap(), ); - assert!(!state.moves_possible(&moves)); + assert!(state.moves_possible(&moves)); // black moves let state = MoveRules::new(&Color::Black, &Board::default(), Dice::default());