From a62e91a23358bde234d604aa3d013959f8c5e04d Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Wed, 8 May 2024 21:17:13 +0200 Subject: [PATCH] prise de coin par puissance --- store/src/board.rs | 5 ++ store/src/game.rs | 144 ++++++++++++++++++++++++++++++++++++++++++-- store/src/player.rs | 9 +++ 3 files changed, 153 insertions(+), 5 deletions(-) diff --git a/store/src/board.rs b/store/src/board.rs index 5b1f44b..3b03281 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -93,6 +93,11 @@ impl Board { Board::default() } + /// Globally set pieces on board ( for tests ) + pub fn set_positions(&mut self, positions: [i8; 24]) { + self.positions = positions; + } + // maybe todo : operate on bits (cf. https://github.com/bungogood/bkgm/blob/a2fb3f395243bcb0bc9f146df73413f73f5ea1e0/src/position.rs#L217) pub fn to_gnupg_pos_id(&self) -> String { // Pieces placement -> 77bits (24 + 23 + 30 max) diff --git a/store/src/game.rs b/store/src/game.rs index 1b2294a..2a76745 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -294,22 +294,72 @@ impl GameState { fn moves_follows_dices(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool { let (dice1, dice2) = self.dice.values; - let (move1, move2): &(CheckerMove, CheckerMove) = moves.into(); + let (move1, move2): &(CheckerMove, CheckerMove) = moves; let dist1 = (move1.get_to() as i8 - move1.get_from() as i8).abs() as u8; let dist2 = (move2.get_to() as i8 - move2.get_from() as i8).abs() as u8; // print!("{}, {}, {}, {}", dist1, dist2, dice1, dice2); - // basic : same number + // exceptions + // - prise de coin par puissance + if self.is_move_by_puissance(color, moves) { + return true; + } + // - sorties + // default : must be same number if cmp::min(dist1, dist2) != cmp::min(dice1, dice2) || cmp::max(dist1, dist2) != cmp::max(dice1, dice2) { return false; } - // prise de coin par puissance - // sorties // no rule was broken true } + fn is_move_by_puissance(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool { + let (dice1, dice2) = self.dice.values; + let (move1, move2): &(CheckerMove, CheckerMove) = moves.into(); + let dist1 = (move1.get_to() as i8 - move1.get_from() as i8).abs() as u8; + let dist2 = (move2.get_to() as i8 - move2.get_from() as i8).abs() as u8; + + // Both corners must be empty + let (count1, _color) = self.board.get_field_checkers(12).unwrap(); + let (count2, _color2) = self.board.get_field_checkers(13).unwrap(); + if count1 > 0 || count2 > 0 { + return false; + } + + move1.get_to() == move2.get_to() + && move1.get_to() == self.board.get_color_corner(color) + && ((*color == Color::White + && cmp::min(dist1, dist2) == cmp::min(dice1, dice2) - 1 + && cmp::max(dist1, dist2) == cmp::max(dice1, dice2) - 1) + || (*color == Color::Black + && cmp::min(dist1, dist2) == cmp::min(dice1, dice2) + 1 + && cmp::max(dist1, dist2) == cmp::max(dice1, dice2) + 1)) + } + + fn can_take_corner_by_effect(&self, color: &Color) -> bool { + // return false if corner already taken + let corner_field: Field = self.board.get_color_corner(color); + let (count, _col) = self.board.get_field_checkers(corner_field).unwrap(); + if count > 0 { + return false; + } + + let (dice1, dice2) = self.dice.values; + let (field1, field2) = match color { + Color::White => (12 - dice1, 12 - dice2), + Color::Black => (13 + dice1, 13 + dice2), + }; + let res1 = self.board.get_field_checkers(field1.into()); + let res2 = self.board.get_field_checkers(field2.into()); + if res1.is_err() || res2.is_err() { + return false; + } + let (count1, opt_color1) = res1.unwrap(); + let (count2, opt_color2) = res2.unwrap(); + count1 > 0 && count2 > 0 && opt_color1 == Some(color) && opt_color2 == Some(color) + } + fn moves_allowed(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool { // ------- corner rules ---------- let corner_field: Field = self.board.get_color_corner(color); @@ -325,12 +375,16 @@ impl GameState { return false; } - // the lat 2 checkers of a corner must leave at the same time + // the last 2 checkers of a corner must leave at the same time if (from0 == corner_field || from1 == corner_field) && (from0 != from1) && corner_count == 2 { return false; } + if self.is_move_by_puissance(color, moves) && self.can_take_corner_by_effect(color) { + return false; + } + // ------- exit rules ---------- // -- toutes les dames doivent être dans le jan de retour // -- si on peut sortir, on doit sortir @@ -579,4 +633,84 @@ mod tests { ); assert!(!state.moves_follows_dices(&Color::White, &badmoves)); } + + #[test] + fn test_can_take_corner_by_effect() { + let mut state = GameState::default(); + let player1 = Player::new("player1".into(), Color::White); + let player_id = 1; + state.add_player(player_id, player1); + state.add_player(2, Player::new("player2".into(), Color::Black)); + state.consume(&GameEvent::BeginGame { + goes_first: player_id, + }); + state.consume(&GameEvent::Roll { player_id }); + + state.board.set_positions([ + 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, + ]); + state.dice.values = (4, 4); + assert!(state.can_take_corner_by_effect(&Color::White)); + + state.dice.values = (5, 5); + assert!(!state.can_take_corner_by_effect(&Color::White)); + + state.board.set_positions([ + 10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, + ]); + state.dice.values = (4, 4); + assert!(!state.can_take_corner_by_effect(&Color::White)); + + state.board.set_positions([ + 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, -13, + ]); + state.dice.values = (1, 1); + assert!(state.can_take_corner_by_effect(&Color::Black)); + } + + #[test] + fn test_prise_en_puissance() { + let mut state = GameState::default(); + let player1 = Player::new("player1".into(), Color::White); + let player_id = 1; + state.add_player(player_id, player1); + state.add_player(2, Player::new("player2".into(), Color::Black)); + state.consume(&GameEvent::BeginGame { + goes_first: player_id, + }); + state.consume(&GameEvent::Roll { player_id }); + + // prise par puissance ok + state.board.set_positions([ + 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, + ]); + state.dice.values = (5, 5); + let moves = ( + CheckerMove::new(8, 12).unwrap(), + CheckerMove::new(8, 12).unwrap(), + ); + assert!(state.is_move_by_puissance(&Color::White, &moves)); + assert!(state.moves_follows_dices(&Color::White, &moves)); + assert!(state.moves_allowed(&Color::White, &moves)); + + // opponent corner must be empty + state.board.set_positions([ + 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -13, + ]); + assert!(!state.is_move_by_puissance(&Color::White, &moves)); + assert!(!state.moves_follows_dices(&Color::White, &moves)); + + // Si on a la possibilité de prendre son coin à la fois par effet, c'est à dire naturellement, et aussi par puissance, on doit le prendre par effet + state.board.set_positions([ + 5, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, + ]); + assert!(!state.moves_allowed(&Color::White, &moves)); + + // on a déjà pris son coin : on ne peux plus y deplacer des dames par puissance + state.board.set_positions([ + 8, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, + ]); + assert!(!state.is_move_by_puissance(&Color::White, &moves)); + assert!(!state.moves_follows_dices(&Color::White, &moves)); + } } diff --git a/store/src/player.rs b/store/src/player.rs index d728c66..1e7d062 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -10,6 +10,15 @@ pub enum Color { Black, } +impl Color { + pub fn opponent_color(&self) -> Self { + match self { + Self::White => Self::Black, + Self::Black => Self::White, + } + } +} + /// Struct for storing player related data. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Player {