use std::fmt; #[derive(Clone, Copy)] pub struct Position { pub x: u8, pub y: u8, } impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:x}{}", 10 + self.x, self.y + 1) } } #[derive(PartialEq)] pub enum Player { Black, White, } impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", match self { Player::Black => "B", Player::White => "W", } ) } } #[derive(PartialEq)] pub enum Stone { Flat, Standing, Capstone, } impl fmt::Display for Stone { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", match self { Stone::Flat => "", Stone::Standing => "S", Stone::Capstone => "C", } ) } } // #[derive(Clone, Copy)] pub enum Direction { Up, Down, Left, Right, } impl fmt::Display for Direction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", match self { Direction::Up => "+", Direction::Down => "-", Direction::Left => "<", Direction::Right => ">", } ) } } pub enum Action { Place(Player, Position, Stone), Move(Player, Position, Direction, Vec), } impl fmt::Display for Action { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Action::Place(player, pos, stone) => write!(f, "{}{}{}", pos, player, stone), Action::Move(_, pos, direction, drops) => write!( f, "{}{}{}", pos, direction, drops.into_iter().map(|q| q.to_string()).collect::() ), } } } type Stack = Vec<(Player, Stone)>; pub struct Board where L: Fn(String), { size: u8, black_flats: u8, white_flats: u8, black_capstones: u8, white_capstones: u8, board: Vec, log: L, } // How to solve code duplication between is_legal_action and // perform_action? // TODO: Generate all legal actions for a given player impl Board { pub fn new(size: u8, log: L) -> Option> { if size >= 3 && size <= 8 { let (flats, caps) = match size { 3 => (10, 0), 4 => (15, 0), 5 => (21, 1), 6 => (30, 1), 7 => (40, 2), 8 => (50, 2), _ => return None, }; Some(Board { size: size, black_flats: flats, white_flats: flats, black_capstones: caps, white_capstones: caps, board: { let mut v: Vec = Vec::new(); for _i in 0..size * size { let t: Stack = Vec::new(); v.push(t); } v }, log: log, }) } else { None } } fn remaining_pieces(&self, player: &Player, stone: &Stone) -> u8 { match player { Player::Black => { if *stone == Stone::Capstone { self.black_capstones } else { self.black_flats } } Player::White => { if *stone == Stone::Capstone { self.white_capstones } else { self.white_flats } } } } fn within_bounds(&self, pos: &Position) -> bool { if (pos.x < self.size) && (pos.y < self.size) { true } else { false } } fn pos_to_idx(&self, pos: &Position) -> usize { let y: usize = pos.y as usize; let x: usize = pos.x as usize; x + y * (self.size as usize) } fn is_legal_place(&self, player: &Player, pos: &Position, stone: &Stone) -> bool { /* In order to legally place a piece: 1. The desired square must be empty 2. The player must have sufficient pieces */ if self.within_bounds(pos) { if let Some(stack) = &self.board.get(self.pos_to_idx(pos)) { if stack.len() > 0 { (self.log)(format!("{} is already occupied.", pos)); false } else if self.remaining_pieces(&player, &stone) == 0 { (self.log)(format!( "{} has no more remaining {} pieces.", player, stone )); false } else { true } } else { (self.log)(format!( "Internal error: lookup for {} failed in is_legal_place.", pos )); false } } else { (self.log)(format!("Position {} is not within bounds.", pos)); false } } fn is_legal_move( &self, player: &Player, pos: &Position, direction: &Direction, drops: &Vec, ) -> bool { if self.within_bounds(pos) { /* Rules for moving a stack: 0. There are stones 1. Top stone belongs to player 2. Zero or One stones dropped on starting square 3. Total number of stones moved does not exceed the carry capacity 4. Direction does not contain a capstone 5. Wall may only appear on last spot if it's capstone alone that covers 6. All stones are used up before then end of the board is met */ if let Some(stack) = &self.board.get(self.pos_to_idx(pos)) { if stack.len() == 0 { (self.log)(format!("{} has no stones to move.", pos)); return false; } else if drops.len() == 0 { (self.log)(format!("A drop sequence for must be specified for a move.")); return false; } else if stack[0].0 != *player { (self.log)(format!( "{} may not move the stack at {} as it belongs to {}.", player, pos, stack[0].0 )); return false; } else if drops[0] > 1 { (self.log)(format!( "A move may only drop 0 or 1 stones at it's origin square." )); return false; // Do the sum in u32 just in case ? } else if drops.iter().map(|&d| d as u32).sum::() > self.size as u32 { (self.log)(format!( "A move may not exceed the carry limit of {} stones.", self.size )); return false; } let mut steps: usize = drops.len() - 1; let cap: bool = stack[0].1 == Stone::Capstone; let mut pos_new = Position { x: pos.x, y: pos.y }; while steps > 0 { steps -= 1; if { match direction { Direction::Up => pos_new.y + 1 >= self.size, Direction::Down => pos_new.y == 0, Direction::Left => pos_new.x + 1 >= self.size, Direction::Right => pos_new.x == 0, } } { (self.log)(format!("A move may not extend past the board.")); return false; } else { match direction { Direction::Up => pos_new.y += 1, Direction::Down => pos_new.y -= 1, Direction::Left => pos_new.x += 1, Direction::Right => pos_new.x -= 1, } if let Some(stack) = &self.board.get(self.pos_to_idx(&pos_new)) { if stack.len() > 0 { match stack[0].1 { Stone::Capstone => { (self.log)(format!("A move may not cover a capstone.")); return false; } Stone::Standing => { if (steps > 1) || (!cap) { (self.log)(format!( "A move may not cover a standing stone." )); return false; } } Stone::Flat => (), } } } else { (self.log)(format!( "Internal error: lookup for {} failed in is_legal_move (1).", pos_new )); return false; } } } return true; } else { (self.log)(format!( "Internal error: lookup for {} failed in is_legal_move (2).", pos )); return false; } } else { (self.log)(format!("Position {} is not within bounds.", pos)); false } } pub fn is_legal_action(&self, act: &Action) -> bool { match act { Action::Place(player, pos, stone) => self.is_legal_place(player, pos, stone), Action::Move(player, pos, direction, drops) => { self.is_legal_move(player, pos, direction, drops) } } } // Unconditional and so could lead to invalid state, but private fn place_stone(&mut self, player: Player, pos: Position, stone: Stone) { let idx = self.pos_to_idx(&pos); self.board[idx].push((player, stone)); } fn move_stack(&mut self, player: Player, pos: Position, direction: Direction, drops: Vec) {} pub fn perform_action(&mut self, act: Action) -> bool { if self.is_legal_action(&act) { match act { Action::Place(player, pos, stone) => self.place_stone(player, pos, stone), Action::Move(player, pos, direction, drops) => { self.move_stack(player, pos, direction, drops) } } true } else { false } } }