diff --git a/src/logic/board.rs b/src/logic/board.rs index a550b7b..ad81ce7 100644 --- a/src/logic/board.rs +++ b/src/logic/board.rs @@ -1,11 +1,11 @@ use crate::logic::{ - pawn::Pawn, + pawn::{Pawn, PawnState}, player::Player, utils::{Field, Position}, }; -use std::ops::Index; +use std::ops::{Index, IndexMut}; -use log::{debug, info}; +use log::{debug, info, warn}; pub struct Board { board: Vec, @@ -13,11 +13,16 @@ pub struct Board { } impl Board { - /// Board is always a square. Size parameter is a side of this rectangle. Defaults to 8 - pub fn new(player1: &Player, player2: &Player, size: Option) -> Self { + /// Board is always a square. Size parameter is a side of this square. Defaults to 8 + pub fn new(player1: &mut Player, player2: &mut Player, size: Option) -> Self { let size = size.unwrap_or(8); info!("Creating new board of size: {}", size); + player1.end_row = size - 1; + player2.end_row = 0; + + let p1_ref: &Player = &*player1; + let p2_ref: &Player = &*player2; let board: Vec = (0..size) .flat_map(|row| { @@ -29,17 +34,17 @@ impl Board { // first player's pawns if row < 3 && is_valid_place_for_a_pawn { - debug!("Placing new pawn at: {}, owned by: {}", position, player1); + debug!("Placing new pawn at: {}, owned by: {}", position, &p1_ref); Field { position, - pawn: Some(Pawn::new(position, &player1)), + pawn: Some(Pawn::new(position, &p1_ref)), } } else if row >= size - 3 && is_valid_place_for_a_pawn { - debug!("Placing new pawn at: {}, owned by: {}", position, player2); + debug!("Placing new pawn at: {}, owned by: {}", position, &p2_ref); Field { position, - pawn: Some(Pawn::new(position, &player2)), + pawn: Some(Pawn::new(position, &p2_ref)), } } else { // no pawn @@ -55,6 +60,51 @@ impl Board { Board { board, size } } + + /// Checks if move from position to position is valid. + /// It returns true if you want to move pawn from position to position where there is no pawn + /// + /// Doesn't check turn + /// + /// Not meant for capturing + fn is_move_valid(&self, from: &Position, to: &Position) -> bool { + if let Some(_) = &self[from.row][from.column].pawn { + self[to.row][to.column].pawn.is_none() + } else { + false + } + } + + fn is_player_owner_of_the_pawn(&self, player: &Player, pawn: &Pawn) -> bool { + player.id == pawn.owner + } + + /// Returns true if move was successful + pub fn move_pawn(&mut self, player: &Player, from: Position, to: Position) -> bool { + if !self.is_move_valid(&from, &to) { + warn!("Tried invalid move: {} -> {}", from, to); + return false; + } + + if !self.is_player_owner_of_the_pawn(&player, self[from].pawn.as_ref().unwrap()) { + warn!("Player {} tried moving a pawn which he doesn own!", player); + debug!("Tried moving from {} to {}", from, to); + return false; + } + + // we can safely unwrap, because if there's not pawn function returns false earlier + let mut mowing_pawn: Pawn = self[from].pawn.take().unwrap(); + + if to.row == player.end_row || matches!(mowing_pawn.state, PawnState::Dame(_)) { + mowing_pawn.state = super::pawn::PawnState::Dame(to); + } else { + mowing_pawn.state = super::pawn::PawnState::Man(to); + } + + self[to.row][to.column].pawn = Some(mowing_pawn); + + true + } } impl Index for Board { @@ -67,10 +117,32 @@ impl Index for Board { } } +impl Index for Board { + type Output = Field; + + fn index(&self, index: Position) -> &Self::Output { + let index = index.row * &self.size + index.column; + &self.board[index] + } +} + +impl IndexMut for Board { + fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut [Field] { + let start = i * &self.size; + let end: usize = start + &self.size; + &mut self.board[start..end] + } +} + +impl IndexMut for Board { + fn index_mut(&mut self, index: Position) -> &mut Self::Output { + let index = index.row * &self.size + index.column; + &mut self.board[index] + } +} + #[cfg(test)] mod tests { - use uuid::Uuid; - use crate::logic::pawn::PawnState; use super::*; @@ -83,17 +155,10 @@ mod tests { fn new_board_test() { init_logger(); - let player1: Player = Player { - name: String::from("Morbius"), - id: Uuid::new_v4(), - }; + let mut player1: Player = Player::new("Morbius".to_string()); + let mut player2: Player = Player::new("Milo".to_string()); - let player2: Player = Player { - name: String::from("Milo"), - id: Uuid::new_v4(), - }; - - let test_board: Board = Board::new(&player1, &player2, None); + let test_board: Board = Board::new(&mut player1, &mut player2, None); let size: usize = test_board.size; for row in 0..size { @@ -136,4 +201,71 @@ mod tests { } } } + + #[test] + fn test_overloaded_operators() { + init_logger(); + + let mut player1: Player = Player::new("Morbius".to_string()); + let mut player2: Player = Player::new("Milo".to_string()); + + let test_board: Board = Board::new(&mut player1, &mut player2, None); + + let row1 = 0; + let column1 = 0; + + let position1: Position = Position { + row: row1, + column: column1, + }; + + let row2 = row1; + let column2 = 1; + + let position2: Position = Position { + row: row2, + column: column2, + }; + + assert_eq!(test_board[row1][column1], test_board[position1]); + assert_eq!(test_board[row2][column2], test_board[position2]); + + assert!(test_board[position1].pawn.is_none()); + assert!(test_board[position2].pawn.is_some()); + } + + #[test] + fn test_moving_pawn() { + init_logger(); + + let mut player1: Player = Player::new("Morbius".to_string()); + let mut player2: Player = Player::new("Milo".to_string()); + + let mut test_board: Board = Board::new(&mut player1, &mut player2, None); + + // we cant move pawn into another pawn; this should fail + assert!(!test_board.move_pawn( + &player1, + Position { row: 0, column: 1 }, + Position { row: 1, column: 0 }, + )); + + // player can't move pawn that he doesn't own; this should fail + assert!(!test_board.move_pawn( + &player2, + Position { row: 2, column: 1 }, + Position { row: 3, column: 0 }, + )); + + // walid move + assert!(test_board.move_pawn( + &player1, + Position { row: 2, column: 1 }, + Position { row: 3, column: 0 } + )); + + assert!(test_board[3][0].pawn.is_some()); + assert_eq!(test_board[3][0].pawn.as_ref().unwrap().owner, player1.id); + assert!(test_board[2][1].pawn.is_none()); + } } diff --git a/src/logic/pawn.rs b/src/logic/pawn.rs index f5f9a3e..6b71162 100644 --- a/src/logic/pawn.rs +++ b/src/logic/pawn.rs @@ -2,7 +2,7 @@ use uuid::Uuid; use crate::logic::{player::Player, utils::Position}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug)] pub enum PawnState { Man(Position), Dame(Position), @@ -10,6 +10,7 @@ pub enum PawnState { } /// Basic pawn +#[derive(Debug, PartialEq, Eq)] pub struct Pawn { pub state: PawnState, pub owner: Uuid, diff --git a/src/logic/player.rs b/src/logic/player.rs index 38d2f87..040f00c 100644 --- a/src/logic/player.rs +++ b/src/logic/player.rs @@ -5,10 +5,26 @@ use uuid::Uuid; pub struct Player { pub name: String, pub id: Uuid, + /// Row where players pawns turn into dames + pub end_row: usize, +} + +impl Player { + pub fn new(name: String) -> Self { + Player { + name, + id: Uuid::new_v4(), + end_row: 0, + } + } } impl Display for Player { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "(name: {}, id, {})", self.name, self.id) + write!( + f, + "(name: {}, id: {}, end_row: {})", + self.name, self.id, self.end_row + ) } } diff --git a/src/logic/utils.rs b/src/logic/utils.rs index fd53edd..69490ed 100644 --- a/src/logic/utils.rs +++ b/src/logic/utils.rs @@ -2,7 +2,7 @@ use core::fmt; use crate::logic::pawn::Pawn; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Position { pub row: usize, pub column: usize, @@ -14,7 +14,22 @@ impl fmt::Display for Position { } } +#[derive(Debug, PartialEq, Eq)] pub struct Field { pub position: Position, pub pawn: Option, } + +impl fmt::Display for Field { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(pawn) = &self.pawn { + write!( + f, + "Field at pos: {}. Pawn here owned by: {}", + self.position, pawn.owner + ) + } else { + write!(f, "Field at pos: {}. No pawn here.", self.position) + } + } +}