diff --git a/bot/src/lib.rs b/bot/src/lib.rs index 4fc5bda..6b39bda 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -100,10 +100,6 @@ impl Default for Bot { impl Bot { /// new initialize a bot - /// # Examples - /// ```let mut bot = Bot::new(Color::Black); - /// assert_eq!(bot.game.stage, Stage::PreGame); - /// ``` // pub fn new(mut strategy: Box, color: Color, schools_enabled: bool) -> Self { pub fn new(mut strategy: Box, color: Color) -> Self { // let game = strategy.get_mut_game(); diff --git a/client_cli/src/app.rs b/client_cli/src/app.rs index ccc45da..b3f6f23 100644 --- a/client_cli/src/app.rs +++ b/client_cli/src/app.rs @@ -286,7 +286,10 @@ Player :: holes :: points ---------------------------------------------------------------- 12 11 10 9 8 7 6 5 4 3 2 1 "; - let mut app = App::new(AppArgs { seed: Some(1327) }); + let mut app = App::new(AppArgs { + seed: Some(1327), + bot: None, + }); app.input("roll"); app.input("1 3"); app.input("1 4"); diff --git a/server/src/main.rs b/server/src/main.rs index c2b88c4..e2923b5 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,4 +1,3 @@ -use bincode; use log::{info, trace, warn}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; use std::thread; @@ -92,7 +91,10 @@ fn main() { trace!("The game gas begun"); } } - ServerEvent::ClientDisconnected { client_id, reason: _ } => { + ServerEvent::ClientDisconnected { + client_id, + reason: _, + } => { // First consume a disconnect event let event = store::GameEvent::PlayerDisconnected { player_id: client_id, diff --git a/store/src/board.rs b/store/src/board.rs index 6e6599b..630a3a5 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -6,6 +6,7 @@ use std::fmt; /// field (aka 'point') position on the board (from 0 to 24, 0 being 'outside') pub type Field = usize; +pub type FieldWithCount = (Field, i8); #[derive(Debug, Copy, Clone, Serialize, PartialEq, Deserialize)] pub struct CheckerMove { @@ -143,9 +144,9 @@ impl Board { .iter() .filter(|count| { if color == Color::White { - **count > 0 as i8 + **count > 0 } else { - **count < 0 as i8 + **count < 0 } }) .sum::() @@ -352,7 +353,7 @@ impl Board { } pub fn is_field_in_small_jan(field: Field) -> bool { - field < 7 || field > 18 + !(7..=18).contains(&field) } /// returns the list of Fields containing Checkers of the Color diff --git a/store/src/game.rs b/store/src/game.rs index 6e8fa34..9fa8fa8 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -106,7 +106,7 @@ impl GameState { fn get_opponent_id(&self) -> Option { self.players .keys() - .map(|k| *k) + .copied() .filter(|k| k != &self.active_player_id) .collect::>() .first() @@ -262,7 +262,10 @@ impl GameState { return false; } } - Mark { player_id, points } => { + Mark { + player_id, + points: _, + } => { // Check player exists if !self.players.contains_key(player_id) { return false; @@ -353,6 +356,7 @@ impl GameState { Some(player_id as PlayerId) } + #[cfg(test)] fn add_player(&mut self, player_id: PlayerId, player: Player) { self.players.insert(player_id, player); } @@ -418,7 +422,7 @@ impl GameState { self.turn_stage = TurnStage::RollWaiting; } } - RollResult { player_id, dice } => { + RollResult { player_id: _, dice } => { self.dice = *dice; self.inc_roll_count(self.active_player_id); self.turn_stage = TurnStage::MarkPoints; @@ -458,7 +462,7 @@ impl GameState { }; } } - Go { player_id } => self.new_pick_up(), + Go { player_id: _ } => self.new_pick_up(), Move { player_id, moves } => { let player = self.players.get(player_id).unwrap(); self.board.move_checker(&player.color, moves.0).unwrap(); @@ -505,8 +509,8 @@ impl GameState { // A player has won if he has got 12 holes self.players .iter() - .filter(|(id, p)| p.holes > 11) - .map(|(id, p)| *id) + .filter(|(_, p)| p.holes > 11) + .map(|(id, _)| *id) .next() } diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index a537d03..249dd10 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -153,7 +153,7 @@ impl MoveRules { /// ---- moves_allowed : Third of three checks for moves pub fn moves_allowed(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> { - self.check_corner_rules(&moves)?; + self.check_corner_rules(moves)?; if self.is_move_by_puissance(moves) { if self.can_take_corner_by_effect() { @@ -169,7 +169,7 @@ impl MoveRules { let mut possible_moves_sequences = self.get_possible_moves_sequences(true); possible_moves_sequences.retain(|moves| self.check_exit_rules(moves).is_ok()); // possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok()); - if !possible_moves_sequences.contains(&moves) && !possible_moves_sequences.is_empty() { + if !possible_moves_sequences.contains(moves) && !possible_moves_sequences.is_empty() { if *moves == (EMPTY_MOVE, EMPTY_MOVE) { return Err(MoveError::MustPlayAllDice); } @@ -336,7 +336,7 @@ impl MoveRules { pub fn get_scoring_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> { let all_seqs = self.get_quarter_filling_moves_sequences(); - if all_seqs.len() == 0 { + if all_seqs.is_empty() { return vec![]; } let missing_fields = self.board.get_quarter_filling_candidate(Color::White); @@ -441,7 +441,7 @@ impl MoveRules { moves_seqs } - fn get_direct_exit_moves(&self, state: &GameState) -> Vec { + fn _get_direct_exit_moves(&self, state: &GameState) -> Vec { let mut moves = Vec::new(); let (dice1, dice2) = state.dice.values; @@ -918,7 +918,7 @@ mod tests { #[test] fn moves_possible() { - let mut state = MoveRules::default(); + let state = MoveRules::default(); // Chained moves let moves = ( diff --git a/store/src/game_rules_points.rs b/store/src/game_rules_points.rs index 653cc59..485e3b9 100644 --- a/store/src/game_rules_points.rs +++ b/store/src/game_rules_points.rs @@ -1,4 +1,4 @@ -use crate::board::{Board, Field, EMPTY_MOVE}; +use crate::board::{Board, FieldWithCount, EMPTY_MOVE}; use crate::dice::Dice; use crate::game_rules_moves::MoveRules; use crate::player::Color; @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use std::cmp; use std::collections::HashMap; +#[allow(clippy::enum_variant_names)] #[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)] pub enum Jan { FilledQuarter, @@ -163,17 +164,16 @@ impl PointsRules { } fn get_jans(&self, board_ini: &Board, dice_rolls_count: u8) -> PossibleJans { - let dices = &vec![self.dice.values.0, self.dice.values.1]; - let dices_reversed = &vec![self.dice.values.1, self.dice.values.0]; + let dices = &[self.dice.values.0, self.dice.values.1]; + let dices_reversed = &[self.dice.values.1, self.dice.values.0]; let dice1 = self.dice.values.0 as usize; let dice2 = self.dice.values.1 as usize; // « JAN DE RÉCOMPENSE » // Battre à vrai une dame située dans la table des grands jans // Battre à vrai une dame située dans la table des petits jans - let mut jans = self.get_jans_by_ordered_dice(board_ini, dices, None, false); - let jans_revert_dices = - self.get_jans_by_ordered_dice(board_ini, dices_reversed, None, false); + let mut jans = get_jans_by_ordered_dice(board_ini, dices, None, false); + let jans_revert_dices = get_jans_by_ordered_dice(board_ini, dices_reversed, None, false); jans.merge(jans_revert_dices); // Battre à vrai le coin de repos de l'adversaire @@ -184,8 +184,8 @@ impl PointsRules { let from0 = adv_corner_field - self.dice.values.0 as usize; let from1 = adv_corner_field - self.dice.values.1 as usize; - let (from0_count, from0_color) = board_ini.get_field_checkers(from0).unwrap(); - let (from1_count, from1_color) = board_ini.get_field_checkers(from1).unwrap(); + let (from0_count, _from0_color) = board_ini.get_field_checkers(from0).unwrap(); + let (from1_count, _from1_color) = board_ini.get_field_checkers(from1).unwrap(); let hit_moves = vec![( CheckerMove::new(from0, adv_corner_field).unwrap(), CheckerMove::new(from1, adv_corner_field).unwrap(), @@ -253,9 +253,9 @@ impl PointsRules { if dice_rolls_count == 3 { // on devrait avoir 4 cases occupées par une dame chacune let fields_with_single: Vec<&(usize, i8)> = - checkers.iter().filter(|(f, c)| c == &1).collect(); + checkers.iter().filter(|(_, c)| c == &1).collect(); if fields_with_single.len() == 4 { - let checkers_fields: Vec = checkers.iter().map(|(f, c)| *f).collect(); + let checkers_fields: Vec = checkers.iter().map(|(f, _)| *f).collect(); let mut missing_for_6tables: Vec = Vec::from([2, 3, 4, 5, 6, 7]) .into_iter() .filter(|f| !checkers_fields.contains(f)) @@ -281,8 +281,8 @@ impl PointsRules { } // Jans nécessitant que deux dames uniquement soient sorties du talon - let (talon, candidates): (Vec<(usize, i8)>, Vec<(usize, i8)>) = - checkers.iter().partition(|(field, count)| field == &1); + let (talon, candidates): (Vec, Vec) = + checkers.iter().partition(|(field, _count)| field == &1); let candidates_fields = candidates.iter().fold(vec![], |mut acc, (f, c)| { acc.extend_from_slice(&vec![*f; *c as usize]); acc @@ -353,97 +353,6 @@ impl PointsRules { jans } - fn get_jans_by_ordered_dice( - &self, - board_ini: &Board, - dices: &Vec, - only_from: Option, - only_false_hit: bool, - ) -> PossibleJans { - let mut jans = PossibleJans::default(); - let mut dices = dices.clone(); - if let Some(dice) = dices.pop() { - let color = Color::White; - let mut board = board_ini.clone(); - let corner_field = board.get_color_corner(&color); - let adv_corner_field = board.get_color_corner(&Color::Black); - let froms = if let Some(from) = only_from { - vec![from] - } else { - board - .get_color_fields(color) - .iter() - .map(|cf| cf.0) - .collect() - }; - for from in froms { - // for (from, _) in board.get_color_fields(color) { - let to = if from + dice as usize > 24 { - 0 - } else { - from + dice as usize - }; - if let Ok(cmove) = CheckerMove::new(from, to) { - // print!( - // " ", - // dice, from, to - // ); - // On vérifie que le mouvement n'est pas interdit par les règles des coins de - // repos : - // - on ne va pas sur le coin de l'adversaire - // - ni sur son propre coin de repos avec une seule dame - // - règle non prise en compte pour le battage des dames : on ne sort pas de son coin de repos s'il n'y reste que deux dames - let (corner_count, _color) = board.get_field_checkers(corner_field).unwrap(); - if to != adv_corner_field && (to != corner_field || corner_count > 1) { - // si only_false_hit est vrai, on est déja dans une tentative tout d'une - let mut can_try_toutdune = !only_false_hit; - let mut only_falsehit = false; - match board.move_checker(&color, cmove) { - Err(Error::FieldBlockedByOne) => { - let jan = match (Board::is_field_in_small_jan(to), only_false_hit) { - (true, false) => Jan::TrueHitSmallJan, - (true, true) => Jan::FalseHitSmallJan, - (false, false) => Jan::TrueHitBigJan, - (false, true) => Jan::FalseHitBigJan, - }; - jans.push(jan, (cmove, EMPTY_MOVE)); - } - Err(Error::FieldBlocked) => { - only_falsehit = true; - } - Err(_) => { - can_try_toutdune = false; - // let next_dice_jan = self.get_jans(&board, &dices); - // jans possibles en tout d'une après un battage à vrai : - // truehit - } - Ok(()) => {} - } - if can_try_toutdune { - // Try tout d'une : - // - use original board before first die move - // - use a virtual dice by adding current dice to remaining dice - // - limit the checker to the current one - let next_dice_jan = self.get_jans_by_ordered_dice( - &board_ini, - &dices.iter().map(|d| d + dice).collect(), - Some(from), - only_falsehit, - ); - jans.merge(next_dice_jan); - } - } - // Second die - let next_dice_jan = - self.get_jans_by_ordered_dice(&board_ini, &dices, None, false); - jans.merge(next_dice_jan); - } - } - } - - jans - } - pub fn get_jans_points(&self, jans: HashMap>) -> (u8, u8) { let (points, adv_points) = jans .into_iter() @@ -477,6 +386,95 @@ impl PointsRules { } } +fn get_jans_by_ordered_dice( + board_ini: &Board, + // dices: &Vec, + dices: &[u8], + only_from: Option, + only_false_hit: bool, +) -> PossibleJans { + let mut jans = PossibleJans::default(); + let mut dices: Vec = dices.to_vec(); + if let Some(dice) = dices.pop() { + let color = Color::White; + let mut board = board_ini.clone(); + let corner_field = board.get_color_corner(&color); + let adv_corner_field = board.get_color_corner(&Color::Black); + let froms = if let Some(from) = only_from { + vec![from] + } else { + board + .get_color_fields(color) + .iter() + .map(|cf| cf.0) + .collect() + }; + for from in froms { + // for (from, _) in board.get_color_fields(color) { + let to = if from + dice as usize > 24 { + 0 + } else { + from + dice as usize + }; + if let Ok(cmove) = CheckerMove::new(from, to) { + // print!( + // " ", + // dice, from, to + // ); + // On vérifie que le mouvement n'est pas interdit par les règles des coins de + // repos : + // - on ne va pas sur le coin de l'adversaire + // - ni sur son propre coin de repos avec une seule dame + // - règle non prise en compte pour le battage des dames : on ne sort pas de son coin de repos s'il n'y reste que deux dames + let (corner_count, _color) = board.get_field_checkers(corner_field).unwrap(); + if to != adv_corner_field && (to != corner_field || corner_count > 1) { + // si only_false_hit est vrai, on est déja dans une tentative tout d'une + let mut can_try_toutdune = !only_false_hit; + let mut only_falsehit = false; + match board.move_checker(&color, cmove) { + Err(Error::FieldBlockedByOne) => { + let jan = match (Board::is_field_in_small_jan(to), only_false_hit) { + (true, false) => Jan::TrueHitSmallJan, + (true, true) => Jan::FalseHitSmallJan, + (false, false) => Jan::TrueHitBigJan, + (false, true) => Jan::FalseHitBigJan, + }; + jans.push(jan, (cmove, EMPTY_MOVE)); + } + Err(Error::FieldBlocked) => { + only_falsehit = true; + } + Err(_) => { + can_try_toutdune = false; + // let next_dice_jan = self.get_jans(&board, &dices); + // jans possibles en tout d'une après un battage à vrai : + // truehit + } + Ok(()) => {} + } + if can_try_toutdune { + // Try tout d'une : + // - use original board before first die move + // - use a virtual dice by adding current dice to remaining dice + // - limit the checker to the current one + let next_dice_jan = get_jans_by_ordered_dice( + board_ini, + &dices.iter().map(|d| d + dice).collect::>(), + Some(from), + only_falsehit, + ); + jans.merge(next_dice_jan); + } + } + // Second die + let next_dice_jan = get_jans_by_ordered_dice(board_ini, &dices, None, false); + jans.merge(next_dice_jan); + } + } + } + + jans +} #[cfg(test)] mod tests { use super::*; @@ -487,11 +485,11 @@ mod tests { 2, 0, -1, -1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(3, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 2], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[2, 2], None, false); assert_eq!(1, jans.len()); assert_eq!(1, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -501,9 +499,8 @@ mod tests { 2, 0, -1, -2, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); - let jans_revert_dices = - rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 2], None, false); + let mut jans = get_jans_by_ordered_dice(&rules.board, &[2, 3], None, false); + let jans_revert_dices = get_jans_by_ordered_dice(&rules.board, &[3, 2], None, false); assert_eq!(2, jans.len()); assert_eq!(1, jans_revert_dices.len()); jans.merge(jans_revert_dices); @@ -513,7 +510,7 @@ mod tests { 2, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(2, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -521,7 +518,7 @@ mod tests { 2, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(1, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -529,7 +526,7 @@ mod tests { 2, 0, 1, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(3, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -540,7 +537,7 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); // le premier dé traité est le dernier du vecteur : 1 - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 1], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[2, 1], None, false); // println!("jans (dés bloqués) : {:?}", jans.get(&Jan::TrueHit)); assert_eq!(0, jans.len()); @@ -548,16 +545,15 @@ mod tests { rules.board.set_positions([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 3], None, false); + let jans = get_jans_by_ordered_dice(&rules.board, &[3, 3], None, false); assert_eq!(1, jans.len()); // premier dé bloqué, mais tout d'une possible en commençant par le second rules.board.set_positions([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 1], None, false); - let jans_revert_dices = - rules.get_jans_by_ordered_dice(&rules.board, &vec![1, 3], None, false); + let mut jans = get_jans_by_ordered_dice(&rules.board, &[3, 1], None, false); + let jans_revert_dices = get_jans_by_ordered_dice(&rules.board, &[1, 3], None, false); assert_eq!(1, jans_revert_dices.len()); jans.merge(jans_revert_dices); diff --git a/store/src/player.rs b/store/src/player.rs index 1254955..54f8cf6 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -76,6 +76,7 @@ pub enum CurrentPlayer { impl CurrentPlayer { /// Returns the other player, i.e. the player who is not the current player. + #[cfg(test)] pub fn other(&self) -> Self { match *self { CurrentPlayer::Nobody => CurrentPlayer::Nobody,