diff --git a/bot/src/strategy/default.rs b/bot/src/strategy/default.rs index 22482eb..98e8322 100644 --- a/bot/src/strategy/default.rs +++ b/bot/src/strategy/default.rs @@ -56,7 +56,7 @@ impl BotStrategy for DefaultStrategy { fn choose_move(&self) -> (CheckerMove, CheckerMove) { let rules = MoveRules::new(&self.color, &self.game.board, self.game.dice); - let possible_moves = rules.get_possible_moves_sequences(true); + let possible_moves = rules.get_possible_moves_sequences(true, vec![]); let choosen_move = *possible_moves .first() .unwrap_or(&(CheckerMove::default(), CheckerMove::default())); diff --git a/client_cli/src/game_runner.rs b/client_cli/src/game_runner.rs index 2d9dbef..f68ea5e 100644 --- a/client_cli/src/game_runner.rs +++ b/client_cli/src/game_runner.rs @@ -99,6 +99,14 @@ impl GameRunner { }; } } + + if let Some(winner) = self.state.determine_winner() { + // panic!("WE HAVE A WINNER!"); + next_event = Some(store::GameEvent::EndGame { + reason: store::EndGameReason::PlayerWon { winner }, + }); + } + next_event } diff --git a/doc/refs/journal.md b/doc/refs/journal.md index 75b028a..dd6d99c 100644 --- a/doc/refs/journal.md +++ b/doc/refs/journal.md @@ -10,6 +10,10 @@ Organisation store / server / client selon cf. l.15 - check_exit_rules - - get_possible_moves_sequences -> cf l.15 + - get_possible_moves_sequences(without exedents) -> cf l.15 - get_quarter_filling_moves_sequences - get_possible_moves_sequences -> cf l.15 - state.consume (RollResult) (ok) diff --git a/store/src/board.rs b/store/src/board.rs index 630a3a5..ced30e4 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -441,7 +441,7 @@ impl Board { let blocked = self.blocked(color, cmove.to).unwrap_or(true); // Check if there is a player's checker on the 'from' square let has_checker = self.get_checkers_color(cmove.from).unwrap_or(None) == Some(color); - has_checker && !blocked + (has_checker && !blocked) || cmove == &EMPTY_MOVE } /// Return if there is a quarter filled by the color @@ -651,6 +651,12 @@ mod tests { assert!(board.set(&Color::White, 23, -3).is_err()); } + #[test] + fn move_possible() { + let board = Board::new(); + assert!(board.move_possible(&Color::White, &EMPTY_MOVE)); + } + #[test] fn get_color_fields() { let board = Board::new(); diff --git a/store/src/game.rs b/store/src/game.rs index b0dfd0f..43bda3e 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -4,7 +4,7 @@ use crate::dice::Dice; use crate::game_rules_moves::MoveRules; use crate::game_rules_points::{PointsRules, PossibleJans}; use crate::player::{Color, Player, PlayerId}; -use log::error; +use log::{error, info}; // use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -170,7 +170,7 @@ impl GameState { } pub fn who_plays(&self) -> Option<&Player> { - self.players.get(&self.active_player_id) + self.get_active_player() } pub fn get_white_player(&self) -> Option<&Player> { @@ -392,7 +392,9 @@ impl GameState { self.stage = Stage::InGame; self.turn_stage = TurnStage::RollDice; } - EndGame { reason: _ } => self.stage = Stage::Ended, + EndGame { reason: _ } => { + self.stage = Stage::Ended; + } PlayerJoined { player_id, name } => { let color = if !self.players.is_empty() { Color::White @@ -542,6 +544,13 @@ impl GameState { } p.points = sum_points % 12; p.holes += holes; + + if points > 0 && p.holes > 15 { + info!( + "player {:?} holes : {:?} added points : {:?}", + player_id, p.holes, points + ) + } p }); diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index a68d8a0..1a67340 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -33,11 +33,11 @@ pub enum MoveError { MustPlayStrongerDie, } -#[derive(std::cmp::PartialEq, Debug)] +#[derive(std::cmp::PartialEq, Debug, Clone)] pub enum TricTracRule { - ExitRule, - MustFillQuarterRule, - CornerRule, + Exit, + MustFillQuarter, + Corner, } /// MoveRules always consider that the current player is White @@ -72,13 +72,14 @@ impl MoveRules { pub fn moves_follow_rules( &self, moves: &(CheckerMove, CheckerMove), - ignored_rules: Vec, + // ignored_rules: Vec, ) -> bool { // Check moves possibles on the board // Check moves conforms to the dice // Check move is allowed by the rules (to desactivate when playing with schools) self.moves_possible(moves) && self.moves_follows_dices(moves) && { - let is_allowed = self.moves_allowed(moves, ignored_rules); + let is_allowed = self.moves_allowed(moves); + // let is_allowed = self.moves_allowed(moves, ignored_rules); if is_allowed.is_err() { info!("Move not allowed : {:?}", is_allowed.unwrap_err()); false @@ -179,7 +180,7 @@ impl MoveRules { pub fn moves_allowed( &self, moves: &(CheckerMove, CheckerMove), - ignored_rules: Vec, + // ignored_rules: Vec, ) -> Result<(), MoveError> { self.check_corner_rules(moves)?; @@ -194,7 +195,7 @@ impl MoveRules { // Si possible, les deux dés doivent être joués if moves.0.get_from() == 0 || moves.1.get_from() == 0 { - let mut possible_moves_sequences = self.get_possible_moves_sequences(true); + let mut possible_moves_sequences = self.get_possible_moves_sequences(true, vec![]); 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() { @@ -212,23 +213,42 @@ impl MoveRules { } // check exit rules - if !ignored_rules.contains(&TricTracRule::ExitRule) { - self.check_exit_rules(moves)?; - } + // if !ignored_rules.contains(&TricTracRule::Exit) { + self.check_exit_rules(moves)?; + // } // --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ---- + self.check_opponent_can_fill_quarter_rule(moves)?; + + // --- remplir cadran si possible & conserver cadran rempli si possible ---- + // if !ignored_rules.contains(&TricTracRule::MustFillQuarter) { + self.check_must_fill_quarter_rule(moves)?; + // } + // no rule was broken + Ok(()) + } + + // --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ---- + fn check_opponent_can_fill_quarter_rule( + &self, + moves: &(CheckerMove, CheckerMove), + ) -> Result<(), MoveError> { let farthest = cmp::max(moves.0.get_to(), moves.1.get_to()); let in_opponent_side = farthest > 12; if in_opponent_side && self.board.is_quarter_fillable(Color::Black, farthest) { return Err(MoveError::OpponentCanFillQuarter); } + Ok(()) + } - // --- remplir cadran si possible & conserver cadran rempli si possible ---- + fn check_must_fill_quarter_rule( + &self, + moves: &(CheckerMove, CheckerMove), + ) -> Result<(), MoveError> { let filling_moves_sequences = self.get_quarter_filling_moves_sequences(); if !filling_moves_sequences.contains(moves) && !filling_moves_sequences.is_empty() { return Err(MoveError::MustFillQuarter); } - // no rule was broken Ok(()) } @@ -284,7 +304,9 @@ impl MoveRules { } // toutes les sorties directes sont autorisées, ainsi que les nombres défaillants - let possible_moves_sequences_without_excedent = self.get_possible_moves_sequences(false); + let ignored_rules = vec![TricTracRule::Exit]; + let possible_moves_sequences_without_excedent = + self.get_possible_moves_sequences(false, ignored_rules); if possible_moves_sequences_without_excedent.contains(moves) { return Ok(()); } @@ -337,6 +359,7 @@ impl MoveRules { pub fn get_possible_moves_sequences( &self, with_excedents: bool, + ignored_rules: Vec, ) -> Vec<(CheckerMove, CheckerMove)> { let (dice1, dice2) = self.dice.values; let (dice_max, dice_min) = if dice1 > dice2 { @@ -344,8 +367,13 @@ impl MoveRules { } else { (dice2, dice1) }; - let mut moves_seqs = - self.get_possible_moves_sequences_by_dices(dice_max, dice_min, with_excedents, false); + let mut moves_seqs = self.get_possible_moves_sequences_by_dices( + dice_max, + dice_min, + with_excedents, + false, + ignored_rules.clone(), + ); // if we got valid sequences with the highest die, we don't accept sequences using only the // lowest die let ignore_empty = !moves_seqs.is_empty(); @@ -354,6 +382,7 @@ impl MoveRules { dice_max, with_excedents, ignore_empty, + ignored_rules, ); moves_seqs.append(&mut moves_seqs_order2); let empty_removed = moves_seqs @@ -418,7 +447,8 @@ impl MoveRules { pub fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> { let mut moves_seqs = Vec::new(); let color = &Color::White; - for moves in self.get_possible_moves_sequences(true) { + let ignored_rules = vec![TricTracRule::Exit, TricTracRule::MustFillQuarter]; + for moves in self.get_possible_moves_sequences(true, ignored_rules) { let mut board = self.board.clone(); board.move_checker(color, moves.0).unwrap(); board.move_checker(color, moves.1).unwrap(); @@ -436,6 +466,7 @@ impl MoveRules { dice2: u8, with_excedents: bool, ignore_empty: bool, + ignored_rules: Vec, ) -> Vec<(CheckerMove, CheckerMove)> { let mut moves_seqs = Vec::new(); let color = &Color::White; @@ -457,24 +488,37 @@ impl MoveRules { board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits) { if self.check_corner_rules(&(first_move, second_move)).is_ok() + && self + .check_opponent_can_fill_quarter_rule(&(first_move, second_move)) + .is_ok() && !(self.is_move_by_puissance(&(first_move, second_move)) && self.can_take_corner_by_effect()) + && (ignored_rules.contains(&TricTracRule::Exit) + || self.check_exit_rules(&(first_move, second_move)).is_ok()) + && (ignored_rules.contains(&TricTracRule::MustFillQuarter) + || self + .check_must_fill_quarter_rule(&(first_move, second_move)) + .is_ok()) { moves_seqs.push((first_move, second_move)); has_second_dice_move = true; } - // TODO : autres règles à vérifier (cf. moves_allowed) - // - check_exit_rules -> utilise get_possible_moves_sequences ! - // - get_quarter_filling_moves_sequences -> utilise get_possible_moves_sequences ! } if !has_second_dice_move && with_excedents && !ignore_empty && self.check_corner_rules(&(first_move, EMPTY_MOVE)).is_ok() - // TODO : autres règles à vérifier (cf. moves_allowed) - // - can_take_corner_by_effect - // - check_exit_rules - // - get_quarter_filling_moves_sequences + && self + .check_opponent_can_fill_quarter_rule(&(first_move, EMPTY_MOVE)) + .is_ok() + && !(self.is_move_by_puissance(&(first_move, EMPTY_MOVE)) + && self.can_take_corner_by_effect()) + && (ignored_rules.contains(&TricTracRule::Exit) + || self.check_exit_rules(&(first_move, EMPTY_MOVE)).is_ok()) + && (ignored_rules.contains(&TricTracRule::MustFillQuarter) + || self + .check_must_fill_quarter_rule(&(first_move, EMPTY_MOVE)) + .is_ok()) { // empty move moves_seqs.push((first_move, EMPTY_MOVE)); @@ -1096,6 +1140,9 @@ mod tests { CheckerMove::new(9, 11).unwrap(), CheckerMove::new(11, 14).unwrap(), ); - assert_eq!(vec![moves], state.get_possible_moves_sequences(true)); + assert_eq!( + vec![moves], + state.get_possible_moves_sequences(true, vec![]) + ); } } diff --git a/store/src/game_rules_points.rs b/store/src/game_rules_points.rs index 485e3b9..8656b54 100644 --- a/store/src/game_rules_points.rs +++ b/store/src/game_rules_points.rs @@ -331,7 +331,7 @@ impl PointsRules { } // Jan qui ne peut : dés non jouables - let poss = self.move_rules.get_possible_moves_sequences(true); + let poss = self.move_rules.get_possible_moves_sequences(true, vec![]); let moves = poss.iter().fold(vec![], |mut acc, (m1, m2)| { acc.push(*m1); acc.push(*m2);