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, Clone, Copy)] 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, Clone, Copy)] 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::() ), } } } #[derive(Clone, Copy)] pub struct Piece { pub player: Player, pub stone: Stone, } pub type Stack = Vec; pub struct GameState { size: u8, black_flats: u8, white_flats: u8, black_capstones: u8, white_capstones: u8, board: Vec, } // TODO: Generate all legal actions for a given player impl GameState { pub fn get_size(&self) -> u8 { self.size } pub fn copy(&self) -> GameState { let mut copy = GameState { size: self.size, black_flats: self.black_flats, white_flats: self.white_flats, black_capstones: self.black_capstones, white_capstones: self.white_capstones, board: Vec::new(), }; for i in 0..self.board.len() { for j in 0..self.board[i].len() { copy.board[i].push(self.board[i][j]); } } copy } // Defaults to 5x5 if requested things are out of range pub fn new(size: u8) -> GameState { let (flats, caps) = match size { 3 => (10, 0), 4 => (15, 0), 6 => (30, 1), 7 => (40, 2), 8 => (50, 2), _ => (21, 1), }; GameState { 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 { v.push(Vec::new()); } v }, } } 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 } } pub fn query_square(&self, pos: &Position) -> Option<&Stack> { if self.within_bounds(pos) { self.board.get(self.pos_to_idx(pos)) } else { None } } 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) } // Unconditional and so could lead to invalid state, but private fn place_stone(&self, player: Player, pos: &Position, stone: Stone) -> GameState { let mut copy = self.copy(); let idx = self.pos_to_idx(&pos); copy.board[idx].push(Piece { player: player, stone: stone, }); copy } fn move_stack(&self, pos: &Position, direction: Direction, drops: &Vec) -> GameState { let mut copy = self.copy(); let mut pos_new = Position { x: pos.x, y: pos.y }; let stack: &Stack = &self.board[self.pos_to_idx(pos)]; let mut k = 0; for &d in drops { let idx = copy.pos_to_idx(&pos_new); for i in 0..d { copy.board[idx].push(stack[(k + i) as usize]); } k += d; 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, }; } copy } } pub struct Game where L: Fn(String), { size: u8, white_player_name: String, black_player_name: String, actions: Vec, states: Vec, log: L, } impl Game { fn new(size: u8, white_player_name: &str, black_player_name: &str, log: L) -> Game { let size = if (size <= 3) || (size >= 8) { (log)(format!( "Warning: the requested game size of {}x{} is not supported, defaulting to 5x5.", size, size )); 5 } else { size }; Game { size: size, white_player_name: white_player_name.to_string(), black_player_name: black_player_name.to_string(), turn: 0, states: vec![GameState::new(size)], actions: Vec::new(), log: log, } } pub fn get_size(&self) -> u8 { self.size } fn is_legal_place(&self, player: &Player, pos: &Position, stone: &Stone) -> bool { if let Some(state) = self.states.last() { /* In order to legally place a piece: 1. The desired square must be empty 2. The player must have sufficient pieces */ if state.within_bounds(pos) { if let Some(stack) = &state.board.get(state.pos_to_idx(pos)) { if stack.len() > 0 { (self.log)(format!("{} is already occupied.", pos)); false } else if state.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 } } else { (self.log)(format!( "Internal error: cannot check action with no game state." )); false } } fn is_legal_move( &self, player: &Player, pos: &Position, direction: &Direction, drops: &Vec, ) -> bool { if let Some(state) = self.states.last() { if state.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) = &state.board.get(state.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].player != *player { (self.log)(format!( "{} may not move the stack at {} as it belongs to {}.", player, pos, stack[0].player )); 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::() > state.size as u32 { (self.log)(format!( "A move may not exceed the carry limit of {} stones.", state.size )); return false; } let mut steps: usize = drops.len() - 1; let cap: bool = stack[0].stone == 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 >= state.size, Direction::Down => pos_new.y == 0, Direction::Left => pos_new.x + 1 >= state.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) = &state.board.get(state.pos_to_idx(&pos_new)) { if stack.len() > 0 { match stack[0].stone { 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 } } else { (self.log)(format!( "Internal error: cannot check action with no game state." )); 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) } } } pub fn perform_action(&mut self, act: &Action) -> bool { if self.is_legal_action(act) { let state = &self.states[self.states.len() - 1]; let new_state = match act { Action::Place(player, pos, stone) => state.place_stone(*player, pos, *stone), Action::Move(_, pos, direction, drops) => state.move_stack(pos, *direction, drops), }; self.states.push(new_state); true } else { false } } } impl fmt::Display for Game { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "[Date \"\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n{}", self.white_player_name, self.black_player_name, self.size, ptn ) } }