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 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 query_pos(&self, pos: &Position) -> Option<&Stack> { self.board.get(self.pos_to_idx(pos)) } fn is_legal_place(&self, player: Player, pos: &Position, stone: Stone) -> Result<(), String> { /* 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 { return Err(format!("{} is already occupied.", pos)); } else if self.remaining_pieces(player, stone) == 0 { return Err(format!( "{} has no more remaining {} pieces.", player, stone )); } else { return Ok(()); } } else { return Err(format!( "Internal error: lookup for {} failed in is_legal_place.", pos )); } } else { return Err(format!("Position {} is not within bounds.", pos)); } } fn place_stone( &self, player: Player, pos: &Position, stone: Stone, ) -> Result<(Vec, GameState), String> { if let Err(e) = self.is_legal_place(player, pos, stone) { return Err(e); } // 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; } } } Ok((vec![pos.clone()], copy)) } fn is_legal_move( &self, player: Player, pos: &Position, direction: Direction, drops: &Vec, ) -> Result<(), String> { if self.within_bounds(pos) { /* Rules for moving a stack: - There are stones - Drops have been specified - Must actually move at least one stone - Top stone belongs to player - Zero or more dropped on starting square - One or more on each subsequent square - Total number of stones moved does not exceed the carry capacity - Moved the entire stack (up to the carry capacity) - Direction does not contain a capstone - Wall may only appear on last spot if it's capstone alone that covers - 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)) { let stack_len = stack.len(); let drops_len = drops.len(); if stack_len == 0 { return Err(format!("{} has no stones to move.", pos)); }; if drops_len == 0 { return Err(String::from( "A drop sequence for must be specified for a move.", )); }; if drops_len == 1 && drops[0] == 1 { return Err(String::from( "A valid move must change the position of at least a single stone.", )); } if stack[stack_len - 1].player != player { return Err(format!( "{} may not move the stack at {} as it belongs to {}.", player, pos, stack[stack_len - 1].player )); }; for i in 0..drops_len { if i > 0 && drops[i] == 0 { return Err(String::from( "A move may not drop 0 stones past the first square.", )); } } let num_dropped = drops.iter().map(|&d| d as usize).sum::(); let carry_capacity = self.size as usize; if num_dropped > carry_capacity { return Err(format!( "A move may not exceed the carry capacity of {} stones.", self.size )); } if num_dropped != std::cmp::max(carry_capacity, stack_len) { return Err(String::from( "A move must effect the whole stack, up to the carry limit.", )); } 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 >= self.size, Direction::Down => pos_new.y == 0, Direction::Left => pos_new.x == 0, Direction::Right => pos_new.x + 1 >= self.size, } } { return Err(String::from("A move may not extend past the board.")); } 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].stone { Stone::Capstone => { return Err(String::from( "A move may not cover a capstone.", )); } Stone::Standing => { if (steps > 1) || (!cap) { return Err(String::from( "A move may not cover a standing stone.", )); } } Stone::Flat => (), } } } else { return Err(format!( "Internal error: lookup for {} failed in is_legal_move (1).", pos_new )); } } } return Ok(()); } else { return Err(format!( "Internal error: lookup for {} failed in is_legal_move (2).", pos )); } } else { return Err(format!("Position {} is not within bounds.", pos)); } } fn move_stack( &self, player: Player, pos: &Position, direction: Direction, drops: &Vec, ) -> Result<(Vec, GameState), String> { if let Err(e) = self.is_legal_move(player, pos, direction, drops) { return Err(e); } let mut copy = self.copy(); let mut pos_vec: Vec = vec![pos.clone()]; let idx = copy.pos_to_idx(&pos); let stack: &Stack = &self.board[self.pos_to_idx(pos)]; copy.board[idx].clear(); let num_drops = drops.len(); let mut offset = 0; for drop_idx in 0..num_drops { let pos = pos_vec[pos_vec.len() - 1]; let idx = copy.pos_to_idx(&pos); let num = drops[drop_idx]; for i in 0..num { copy.board[idx].push(stack[(offset + i) as usize]); } offset += num; if drop_idx + 1 < num_drops { pos_vec.push(match direction { Direction::Up => Position { x: pos.x, y: pos.y + 1, }, Direction::Down => Position { x: pos.x, y: pos.y - 1, }, Direction::Left => Position { x: pos.x - 1, y: pos.y, }, Direction::Right => Position { x: pos.x + 1, y: pos.y, }, }); } } Ok((pos_vec, copy)) } } enum TurnOrder { BlackPlacesWhite, WhitePlacesBlack, Normal, } pub struct Game { size: u8, current_player: Player, turn_order: TurnOrder, white_player_name: String, black_player_name: String, actions: Vec, states: Vec, } impl Game { pub fn new(size: u8, white_player_name: &str, black_player_name: &str) -> (Game, String) { let (size, warning) = if (size <= 3) || (size >= 8) { (5, format!("Warning: the requested game size of {}x{} is not supported, defaulting to 5x5.", size, size)) } else { (size, String::new()) }; ( 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, }, warning, ) } // 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> { self.last_state().query_pos(pos) } 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_stone_owner(&self) -> Player { match self.turn_order { TurnOrder::BlackPlacesWhite => Player::White, TurnOrder::WhitePlacesBlack => Player::Black, TurnOrder::Normal => 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 = true; result += "0. "; for i in 0..self.actions.len() { if i > 1 && newline { // TODO: Is placing the opponent's first stone the zeroeth action? result += &format!("{}. ", i); } result += &format!("{} ", self.actions[i]); if i > 0 && !newline { result.push('\n'); } newline = !newline; } result } pub fn perform_action(&mut self, act: Action) -> Result, String> { let maybe_state = self.states.last(); if maybe_state.is_none() { return Err(String::from("Internal error: cannot find last game state")); } let state = maybe_state.unwrap(); let new_state_either = 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) { state.place_stone(*player, pos, *stone) } else { Err(String::from( "At the start of the game, B must place a W flat.", )) } } TurnOrder::WhitePlacesBlack => { if (self.current_player == Player::White) && (*player == Player::Black) && (*stone == Stone::Flat) { state.place_stone(*player, pos, *stone) } else { Err(String::from( "At the start of the game, W must place a B flat.", )) } } TurnOrder::Normal => state.place_stone(*player, pos, *stone), }, Action::Move(player, pos, direction, drops) => match self.turn_order { TurnOrder::Normal => { if *player == self.current_player { state.move_stack(*player, pos, *direction, drops) } else { Err(format!( "{} may not take actions on {}'s turn.", player, self.current_player )) } } _ => Err(String::from( "At the start of the game only placing flats is allowed.", )), }, }; match new_state_either { Err(e) => return Err(e), Ok((pos_vec, new_state)) => { self.states.push(new_state); self.actions.push(act); self.current_player = match self.turn_order { TurnOrder::BlackPlacesWhite => Player::White, TurnOrder::WhitePlacesBlack => Player::White, TurnOrder::Normal => 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, }; return Ok(pos_vec); } } } } 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() ) } }