diff --git a/src/agent.rs b/src/agent.rs index 0ed7a1b..cfa197b 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -4,11 +4,15 @@ use std::io; use std::io::prelude::*; pub trait Agent { + /// Returns the move of an [`Agent`] fn next_move(&mut self, board: &Board) -> Option<(usize, usize)>; + /// Returns the name of the [`Agent`] fn name(&self) -> &'static str; + /// Returns the color the [`Agent`] is playing fn color(&self) -> Piece; } +/// An [`Agent`] which a user controls pub struct ManualAgent { color: Piece, } @@ -60,6 +64,7 @@ impl ManualAgent { } } +/// An [`Agent`] that just makes a random move that is legal pub struct RandomAgent { color: Piece, } diff --git a/src/board.rs b/src/board.rs index cbefd62..5d2233c 100644 --- a/src/board.rs +++ b/src/board.rs @@ -7,7 +7,10 @@ use arrayvec::ArrayVec; use lazy_static::lazy_static; use std::{cmp::Ordering, fmt}; +/// Size of each dim of the board pub const BOARD_SIZE: usize = 8; + +/// Area of the board pub const BOARD_AREA: usize = BOARD_SIZE * BOARD_SIZE; /// A chain of positions across the board @@ -21,6 +24,7 @@ type ChainCollection = ArrayVec; /// with each coordinate type PosMap = ArrayVec, BOARD_SIZE>; +/// Creates a lookup map for adjacencies and chains from each position on the board fn gen_adj_lookup() -> PosMap { (0..BOARD_SIZE) .map(|i| { @@ -60,9 +64,12 @@ lazy_static! { pub static ref ADJ_LOOKUP: PosMap = gen_adj_lookup(); } +/// Repersents a Othello game board at a certain space #[derive(Copy, Clone, PartialEq, Eq)] pub struct Board { + /// [`BitBoard`] containing all white pieces white_board: BitBoard, + /// [`BitBoard`] containing all black pieces black_board: BitBoard, } @@ -70,7 +77,10 @@ impl fmt::Display for Board { #[allow(clippy::repeat_once)] // clippy gets mad about when PADDING == 1 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let horiz_sep_line = "-".repeat(BOARD_SIZE * 2 + 1); + + // basically calculates the # of digits BOARD_SIZE needs const PADDING: usize = (BOARD_SIZE - 1).ilog10() as usize + 1; + let space_padding = " ".repeat(PADDING); // Print numbers at top so the board can be read more easier @@ -109,6 +119,7 @@ impl fmt::Display for Board { } impl Board { + /// Create a new empty board pub const fn new() -> Self { Self { white_board: BitBoard::new(), @@ -116,6 +127,7 @@ impl Board { } } + /// Starting position pub fn starting_pos(mut self) -> Self { self.place_unchecked((BOARD_SIZE / 2) - 1, (BOARD_SIZE / 2) - 1, Piece::White); self.place_unchecked(BOARD_SIZE / 2, (BOARD_SIZE / 2) - 1, Piece::Black); @@ -124,28 +136,28 @@ impl Board { self } + /// Provides an iterator of all possible positions on the board pub fn all_positions() -> impl Iterator { (0..BOARD_SIZE).flat_map(|i| (0..BOARD_SIZE).map(move |j| (i, j))) } + /// Returns an iterator of all possible moves a `color` can make pub fn possible_moves(&self, color: Piece) -> impl Iterator + use<'_> { - Self::all_positions().filter(move |(i, j)| self.would_prop(*i, *j, color)) + Self::all_positions().filter(move |&(i, j)| self.would_prop(i, j, color)) } - /// Returns a reference to a place on the [`Board`] - /// at (i, j) + /// Returns the color of a place on the [`Board`] at a position pub fn get(&self, i: usize, j: usize) -> Option { - let white = self.white_board.get(i, j); - let black = self.black_board.get(i, j); - if white { + if self.white_board.get(i, j) { Some(Piece::White) - } else if black { + } else if self.black_board.get(i, j) { Some(Piece::Black) } else { None } } + /// Place a piece without checking for propegation of validity fn place_unchecked(&mut self, i: usize, j: usize, piece: Piece) { match piece { Piece::Black => { @@ -159,6 +171,8 @@ impl Board { } } + /// Return a modified [`Board`] with the piece placed at a position + /// Returns None if the move was invalid pub fn what_if(&self, i: usize, j: usize, piece: Piece) -> Option { if self.get(i, j).is_some() { return None; @@ -174,6 +188,7 @@ impl Board { Some(self_copy) } + /// Returns a bool which represents whether or not a move would propegate and be valid pub fn would_prop(&self, i: usize, j: usize, piece: Piece) -> bool { self.get(i, j).is_none() && !self.propegate_from_dry(i, j, piece).is_empty() } @@ -186,6 +201,7 @@ impl Board { Err("move would not propegate") } + /// Propegate the board and captures starting from a specific position fn propegate_from(&mut self, i: usize, j: usize) -> usize { let Some(starting_color) = self.get(i, j) else { return 0; @@ -215,14 +231,15 @@ impl Board { }; if piece == starting_color { - // get history of this chain + // Chain is only ever added to `fill` if it is completed + if let Some(history) = chain.get(..chain_length) { // fill all opposite colors with this color fill.extend(history); } // either the other pieces were replaced, or this was an invalid chain, - // in both cases, the loop needs to be breaked + // in both cases, the loop needs to be broken break; } } @@ -230,6 +247,7 @@ impl Board { fill } + /// Count the number of a type of [`Piece`] on the board pub fn count(&self, piece: Piece) -> usize { match piece { Piece::Black => self.black_board.count(), @@ -242,6 +260,7 @@ impl Board { (self.count(Piece::White), self.count(Piece::Black)) } + /// Returns the winner of the board (if any) pub fn game_winner(&self, turn: Piece) -> Option { // Wikipedia: `Players take alternate turns. If one player cannot make a valid move, play passes back to the other player. The game ends when the grid has filled up or if neither player can make a valid move.` diff --git a/src/main.rs b/src/main.rs index 37db162..a0b4902 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,8 @@ mod piece; fn main() { let player1 = complexagent::ComplexAgent::new(Piece::Black); // let player2 = complexagent::ComplexAgent::new(Piece::White); - // let player2 = agent::ManualAgent::new(Piece::White); - let player2 = agent::RandomAgent::new(Piece::White); + let player2 = agent::ManualAgent::new(Piece::White); + // let player2 = agent::RandomAgent::new(Piece::White); let mut game = Game::new(Box::new(player1), Box::new(player2)); game.game_loop(); }