diff --git a/store/src/board.rs b/store/src/board.rs index 220121a..0bf6336 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -3,7 +3,7 @@ use crate::Error; use serde::{Deserialize, Serialize}; use std::fmt; -/// field (aka 'point') position on the board (from 1 to 24) +/// field (aka 'point') position on the board (from 0 to 24, 0 being 'outside') pub type Field = usize; #[derive(Debug, Copy, Clone, Serialize, PartialEq, Deserialize)] @@ -14,12 +14,22 @@ pub struct CheckerMove { impl CheckerMove { pub fn new(from: Field, to: Field) -> Result { - if from < 1 || 24 < from || to < 1 || 24 < to { + // check if the field is on the board + // we allow 0 for 'to', which represents the exit of a checker + if from < 1 || 24 < from || 24 < to{ return Err(Error::FieldInvalid); } + // check that the destination is after the origin field + if to < from && to != 0 { + return Err(Error::MoveInvalid); + } Ok(CheckerMove { from, to }) } + pub fn get_from(&self) -> Field { + self.from + } + pub fn get_to(&self) -> Field { self.to } @@ -106,6 +116,11 @@ impl Board { return Err(Error::FieldInvalid); } + // the exit : no checker added to the board + if field == 0 { + return Ok(()) + } + if self.blocked(color, field)? { return Err(Error::FieldBlocked); } @@ -134,10 +149,15 @@ impl Board { /// Check if a field is blocked for a player pub fn blocked(&self, color: &Color, field: Field) -> Result { - if field < 1 || 24 < field { + if 24 < field { return Err(Error::FieldInvalid); } + // the exit is never 'blocked' + if field == 0 { + return Ok(false) + } + // the square is blocked on the opponent rest corner or if there are opponent's men on the square match color { Color::White => { @@ -157,11 +177,11 @@ impl Board { } } - pub fn get_checkers_color(&self, field: Field) -> Result, Error> { + pub fn get_field_checkers(&self, field: Field) -> Result<(u8, Option<&Color>), Error> { if field < 1 || field > 24 { return Err(Error::FieldInvalid); } - let checkers_count = self.positions[field - 1]; + let checkers_count = self.positions[field - 1]; let color = if checkers_count < 0 { Some(&Color::Black) } else if checkers_count > 0 { @@ -169,7 +189,16 @@ impl Board { } else { None }; - Ok(color) + Ok((checkers_count.abs() as u8, color)) + } + + pub fn get_checkers_color(&self, field: Field) -> Result, Error> { + self.get_field_checkers(field).map(|(count, color)| color) + } + + // Get the corner field for the color + pub fn get_color_corner(&self, color: &Color) -> Field { + if color == &Color::White { 12 } else { 13 } } pub fn move_possible(&self, color: &Color, cmove: CheckerMove) -> bool { @@ -231,7 +260,7 @@ mod tests { #[test] fn blocked_outofrange() -> Result<(), Error> { let board = Board::new(); - assert!(board.blocked( &Color::White, 0).is_err()); + assert!(!board.blocked( &Color::White, 0).is_err()); assert!(board.blocked( &Color::White, 28).is_err()); Ok(()) } @@ -255,7 +284,7 @@ mod tests { fn set_field_blocked() { let mut board = Board::new(); assert!( - board.set( &Color::White, 0, 24) + board.set( &Color::White, 24, 2) .is_err() ); } diff --git a/store/src/game.rs b/store/src/game.rs index cfc5164..488caf8 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -229,13 +229,16 @@ impl GameState { } // Check move is physically possible - if !self.board.move_possible(&self.players[player_id].color, moves.0){ - return false; - } - if !self.board.move_possible(&self.players[player_id].color, moves.1){ - return false; - } + let color = &self.players[player_id].color; + if !self.board.move_possible(color, moves.0) || + !self.board.move_possible(color, moves.1) { + return false; + } + // Check move is allowed by the rules (to desactivate when playing with schools) + if !self.moves_allowed(color, moves) { + return false; + } } } @@ -243,6 +246,29 @@ impl GameState { true } + fn moves_allowed(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool { + // ------- corner rules ---------- + let corner_field: Field = self.board.get_color_corner(color); + let (corner_count, _color) = self.board.get_field_checkers(corner_field).unwrap(); + let (from0, to0, from1, to1) = (moves.0.get_from(), moves.0.get_to(), moves.1.get_from(), moves.1.get_to()); + // 2 checkers must go at the same time on an empty corner + if (to0 == corner_field || to1 == corner_field) && + (to0 != to1) && corner_count == 0 { + return false; + } + + // the lat 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; + } + + // ------- exit rules ---------- + + // no rule was broken + true + } + /// Consumes an event, modifying the GameState and adding the event to its history /// NOTE: consume assumes the event to have already been validated and will accept *any* event passed to it pub fn consume(&mut self, valid_event: &GameEvent) {