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(_, pos, stone) => write!(f, "{}{}", stone, pos), Action::Move(_, pos, direction, drops) => write!( f, "{}{}{}{}", drops.iter().map(|&d| d as u32).sum::(), 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; 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 { 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() { copy.board.push(Vec::new()); 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 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 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 query_pos(&self, pos: &Position) -> Option<&Stack> { self.board.get(self.pos_to_idx(pos)) } // Unconditional and so could lead to invalid state, but private fn place_stone(&self, player: Player, pos: &Position, stone: Stone) -> GameState { // Place stone let mut copy = self.copy(); let idx = self.pos_to_idx(&pos); copy.board[idx].push(Piece { player: player, stone: stone, }); // Decrease count match player { Player::Black => { if stone == Stone::Capstone { copy.black_capstones -= 1; } else { copy.black_flats -= 1; } } Player::White => { if stone == Stone::Capstone { copy.white_capstones -= 1; } else { copy.white_flats -= 1; } } } 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, Direction::Down => pos_new.y -= 1, Direction::Left => pos_new.x -= 1, Direction::Right => pos_new.x += 1, }; } copy } } enum TurnOrder { BlackPlacesWhite, WhitePlacesBlack, Normal, } // TODO: Redo this logging story with Result<...,String> instead pub struct Game where L: Fn(String), { size: u8, current_player: Player, turn_order: TurnOrder, white_player_name: String, black_player_name: String, actions: Vec, states: Vec, log: L, } impl Game { pub 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(), states: vec![GameState::new(size)], actions: Vec::new(), current_player: Player::Black, turn_order: TurnOrder::BlackPlacesWhite, log: log, } } fn within_bounds(&self, pos: &Position) -> bool { if (pos.x < self.size) && (pos.y < self.size) { true } else { false } } // Relying on only ::new(...) being used to make instances fn last_state(&self) -> &GameState { &self.states[self.states.len() - 1] } pub fn query_square(&self, pos: &Position) -> Option<&Stack> { if self.within_bounds(pos) { self.last_state().query_pos(pos) } else { None } } pub fn query_pieces(&self, player: Player, stone: Stone) -> u8 { self.last_state().remaining_pieces(player, stone) } pub fn query_current_player(&self) -> Player { self.current_player } pub fn query_action(&self, turn: u16) -> Option<&Action> { self.actions.get(turn as usize) } pub fn get_size(&self) -> u8 { self.size } pub fn query_action_lines(&self) -> String { let mut result = String::new(); let mut newline = false; result += "0. "; for i in 0..self.actions.len() { if newline { // TODO: Is placing the opponent's first stone the zeroeth action? result += &format!("{}. ", i); } result += &format!("{} ", self.actions[i]); if !newline { result.push('\n'); } newline = !newline; } result } 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 self.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 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) = &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)(String::from( "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)(String::from("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)(String::from( "A move may not cover a capstone.", )); return false; } Stone::Standing => { if (steps > 1) || (!cap) { (self.log)(String::from( "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)(String::from( "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) => match self.turn_order { TurnOrder::BlackPlacesWhite => { if (self.current_player == Player::Black) && (*player == Player::White) && (*stone == Stone::Flat) { self.is_legal_place(player, pos, stone) } else { (self.log)(String::from( "At the start of the game, B must place a W flat.", )); false } } TurnOrder::WhitePlacesBlack => { if (self.current_player == Player::White) && (*player == Player::Black) && (*stone == Stone::Flat) { self.is_legal_place(player, pos, stone) } else { (self.log)(String::from( "At the start of the game, W must place a B flat.", )); false } } TurnOrder::Normal => self.is_legal_place(player, pos, stone), }, Action::Move(player, pos, direction, drops) => match self.turn_order { TurnOrder::Normal => { if *player == self.current_player { self.is_legal_move(player, pos, direction, drops) } else { (self.log)(format!( "{} may not take actions on {}'s turn.", player, self.current_player )); false } } _ => { (self.log)(String::from( "At the start of the game only placing flats is allowed.", )); false } }, } } 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); self.actions.push(act); self.current_player = match self.current_player { Player::Black => Player::White, Player::White => Player::Black, }; self.turn_order = match self.turn_order { TurnOrder::BlackPlacesWhite => TurnOrder::WhitePlacesBlack, TurnOrder::WhitePlacesBlack => TurnOrder::Normal, TurnOrder::Normal => TurnOrder::Normal, }; 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, self.query_action_lines() ) } }