-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
chore: add the game isolation
1 parent
48cd33d
commit 5b3e6bb
Showing
8 changed files
with
706 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright © 2024 Rak Laptudirm <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use crate::interface::bitboard_type; | ||
|
||
use super::Square; | ||
|
||
bitboard_type! { | ||
/// A set of Squares implemented as a bitset where the `1 << sq.into()` bit | ||
/// represents whether `sq` is in the BitBoard or not. | ||
struct BitBoard : u64 { | ||
// The BitBoard's Square type. | ||
Square = Square; | ||
|
||
// BitBoards representing the null and the universe sets. | ||
Empty = Self(0); | ||
Universe = Self(0x1ffffffffffff); | ||
|
||
// BitBoards containing the squares of the first file and the first rank. | ||
FirstFile = Self(0x0040810204081); | ||
FirstRank = Self(0x000000000007f); | ||
} | ||
} | ||
|
||
use crate::interface::{BitBoardType, RepresentableType}; | ||
|
||
impl BitBoard { | ||
/// singles returns the targets of all singular moves from all the source | ||
/// squares given in the provided BitBoard. | ||
pub fn singles(bb: BitBoard) -> BitBoard { | ||
let bar = bb | bb.east() | bb.west(); | ||
(bar | bar.north() | bar.south()) ^ bb | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Make the contents of the non-namespaced | ||
// modules public, so they can be accessed | ||
// without their parent namespace. | ||
pub use self::bitboard::*; | ||
pub use self::piece::*; | ||
pub use self::position::*; | ||
pub use self::r#move::*; | ||
pub use self::square::*; | ||
|
||
// Non-namespaced modules. | ||
mod bitboard; | ||
mod r#move; | ||
mod piece; | ||
mod position; | ||
mod square; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
// Copyright © 2024 Rak Laptudirm <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use std::fmt; | ||
use std::str::FromStr; | ||
|
||
use thiserror::Error; | ||
|
||
use super::Square; | ||
use crate::interface::{MoveType, RepresentableType, TypeParseError}; | ||
|
||
/// Move represents an Ataxx move which can be played on the Board. | ||
#[derive(Copy, Clone, PartialEq, Eq, Default)] | ||
pub struct Move(u16); | ||
|
||
impl MoveType for Move { | ||
const NULL: Self = Move(1 << 15); | ||
const MAX_IN_GAME: usize = 48; | ||
const MAX_IN_POSITION: usize = 352; | ||
} | ||
|
||
impl From<u16> for Move { | ||
fn from(value: u16) -> Self { | ||
Move(value) | ||
} | ||
} | ||
|
||
impl From<Move> for u16 { | ||
fn from(value: Move) -> Self { | ||
value.0 | ||
} | ||
} | ||
|
||
impl Move { | ||
// Bit-widths of fields. | ||
const PAWN_WIDTH: u16 = 6; | ||
const TILE_WIDTH: u16 = 6; | ||
|
||
// Bit-masks of fields. | ||
const PAWN_MASK: u16 = (1 << Move::PAWN_WIDTH) - 1; | ||
const TILE_MASK: u16 = (1 << Move::TILE_WIDTH) - 1; | ||
|
||
// Bit-offsets of fields. | ||
const PAWN_OFFSET: u16 = 0; | ||
const TILE_OFFSET: u16 = Move::PAWN_OFFSET + Move::PAWN_WIDTH; | ||
|
||
/// NULL Move represents an invalid move. | ||
pub const NULL: Move = Move(1 << 15); | ||
|
||
/// new_single returns a new singular Move, where a piece is cloned to its | ||
/// target Square. For a singular Move, [`Move::source`] and [`Move::target`] | ||
/// are equal since the source Square is irrelevant to the Move. | ||
/// ``` | ||
/// use tetka_games::ataxx::*; | ||
/// | ||
/// let mov = Move::new_single(Square::A1); | ||
/// | ||
/// assert_eq!(mov.source(), mov.target()); | ||
/// assert_eq!(mov.target(), Square::A1); | ||
/// ``` | ||
#[inline(always)] | ||
pub fn new_single(square: Square) -> Move { | ||
Move::new(square, square) | ||
} | ||
|
||
/// new returns a new jump Move from the given source Square to the given | ||
/// target Square. These Squares can be recovered with the [`Move::source`] and | ||
/// [`Move::target`] methods respectively. | ||
/// ``` | ||
/// use tetka_games::ataxx::*; | ||
/// | ||
/// let mov = Move::new(Square::A1, Square::A3); | ||
/// | ||
/// assert_eq!(mov.source(), Square::A1); | ||
/// assert_eq!(mov.target(), Square::A3); | ||
/// ``` | ||
#[inline(always)] | ||
#[rustfmt::skip] | ||
pub fn new(source: Square, target: Square) -> Move { | ||
Move( | ||
(source as u16) << Move::PAWN_OFFSET | | ||
(target as u16) << Move::TILE_OFFSET | ||
) | ||
} | ||
|
||
/// Source returns the source Square of the moving piece. This is equal to the | ||
/// target Square if the given Move is of singular type. | ||
/// ``` | ||
/// use tetka_games::ataxx::*; | ||
/// | ||
/// let mov = Move::new(Square::A1, Square::A3); | ||
/// | ||
/// assert_eq!(mov.source(), Square::A1); | ||
/// ``` | ||
pub fn pawn(self) -> Square { | ||
unsafe { | ||
Square::unsafe_from((self.0 >> Move::PAWN_OFFSET) & Move::PAWN_MASK) | ||
} | ||
} | ||
|
||
/// Target returns the target Square of the moving piece. | ||
/// ``` | ||
/// use tetka_games::ataxx::*; | ||
/// | ||
/// let mov = Move::new(Square::A1, Square::A3); | ||
/// | ||
/// assert_eq!(mov.target(), Square::A3); | ||
/// ``` | ||
pub fn tile(self) -> Square { | ||
unsafe { | ||
Square::unsafe_from((self.0 >> Move::TILE_OFFSET) & Move::TILE_MASK) | ||
} | ||
} | ||
} | ||
|
||
#[derive(Error, Debug)] | ||
pub enum MoveParseError { | ||
#[error("length of move string should be 2 or 4, not {0}")] | ||
BadLength(usize), | ||
#[error("bad source square string \"{0}\"")] | ||
BadSquare(#[from] TypeParseError), | ||
} | ||
|
||
impl FromStr for Move { | ||
type Err = MoveParseError; | ||
|
||
/// from_str converts the given string representation of a Move into a [Move]. | ||
/// The formats supported are '0000' for a [Move::PASS], `<target>` for a | ||
/// singular Move, and `<source><target>` for a jump Move. For how `<source>` | ||
/// and `<target>` are parsed, take a look at | ||
/// [`Square::FromStr`](Square::from_str). This function can be treated as the | ||
/// inverse of the [`fmt::Display`] trait for [Move]. | ||
/// ``` | ||
/// use tetka_games::ataxx::*; | ||
/// use std::str::FromStr; | ||
/// | ||
/// let pass = Move::PASS; | ||
/// let sing = Move::new_single(Square::A1); | ||
/// let jump = Move::new(Square::A1, Square::A3); | ||
/// | ||
/// assert_eq!(Move::from_str(&pass.to_string()).unwrap(), pass); | ||
/// assert_eq!(Move::from_str(&sing.to_string()).unwrap(), sing); | ||
/// assert_eq!(Move::from_str(&jump.to_string()).unwrap(), jump); | ||
/// ``` | ||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
if s.len() != 2 && s.len() != 4 { | ||
return Err(MoveParseError::BadLength(s.len())); | ||
} | ||
|
||
let source = &s[..2]; | ||
let source = Square::from_str(source)?; | ||
|
||
if s.len() < 4 { | ||
return Ok(Move::new_single(source)); | ||
} | ||
|
||
let target = &s[2..]; | ||
let target = Square::from_str(target)?; | ||
|
||
Ok(Move::new(source, target)) | ||
} | ||
} | ||
|
||
impl fmt::Display for Move { | ||
/// Display formats the given Move in a human-readable manner. The format used | ||
/// for displaying jump moves is `<source><target>`, while a singular Move is | ||
/// formatted as `<target>`. For the formatting of `<source>` and `<target>`, | ||
/// refer to `Square::Display`. [`Move::NULL`] is formatted as `null`, while | ||
/// [`Move::PASS`] is formatted as `0000`. | ||
/// ``` | ||
/// use tetka_games::ataxx::*; | ||
/// | ||
/// let null = Move::NULL; | ||
/// let pass = Move::PASS; | ||
/// let sing = Move::new_single(Square::A1); | ||
/// let jump = Move::new(Square::A1, Square::A3); | ||
/// | ||
/// assert_eq!(null.to_string(), "null"); | ||
/// assert_eq!(pass.to_string(), "0000"); | ||
/// assert_eq!(sing.to_string(), "a1"); | ||
/// assert_eq!(jump.to_string(), "a1a3"); | ||
/// ``` | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
if *self == Move::NULL { | ||
write!(f, "null") | ||
} else { | ||
write!(f, "{}{}", self.pawn(), self.tile()) | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Debug for Move { | ||
/// Debug formats the given Move into a human-readable debug string. It uses | ||
/// `Move::Display` trait under the hood for formatting the Move. | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write!(f, "{}", self) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// Copyright © 2024 Rak Laptudirm <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use std::fmt; | ||
use std::ops; | ||
use std::str::FromStr; | ||
|
||
use crate::interface::representable_type; | ||
use crate::interface::ColoredPieceType; | ||
use crate::interface::RepresentableType; | ||
|
||
representable_type!( | ||
/// Color represents all the possible colors that an ataxx piece can have, | ||
/// specifically, Black and White. | ||
enum Color: u8 { White "w", Black "b", } | ||
); | ||
|
||
impl ops::Not for Color { | ||
type Output = Color; | ||
|
||
/// not implements the not unary operator (!) which switches the current Color | ||
/// to its opposite, i.e. [`Color::Black`] to [`Color::White`] and vice versa. | ||
fn not(self) -> Self::Output { | ||
unsafe { Color::unsafe_from(self as usize ^ 1) } | ||
} | ||
} | ||
|
||
representable_type!( | ||
/// Piece represents the types of pieces in ataxx, namely Piece and Block. | ||
enum Piece: u8 { Pawn "p", Tile "-", } | ||
); | ||
|
||
representable_type!( | ||
/// Piece represents all the possible ataxx pieces. | ||
enum ColoredPiece: u8 { WhitePawn "P", BlackPawn "p", Tile "-", } | ||
); | ||
|
||
impl ColoredPieceType for ColoredPiece { | ||
type Piece = Piece; | ||
type Color = Color; | ||
|
||
fn piece(self) -> Piece { | ||
match self { | ||
ColoredPiece::WhitePawn | ColoredPiece::BlackPawn => Piece::Pawn, | ||
ColoredPiece::Tile => Piece::Tile, | ||
} | ||
} | ||
|
||
fn color(self) -> Color { | ||
match self { | ||
ColoredPiece::WhitePawn => Color::White, | ||
ColoredPiece::BlackPawn => Color::Black, | ||
_ => panic!("Piece::color() called on Piece::Tile"), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
// Copyright © 2024 Rak Laptudirm <rak@laptudirm.com> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use std::cmp; | ||
use std::fmt; | ||
use std::num::ParseIntError; | ||
use std::str::FromStr; | ||
|
||
use strum::IntoEnumIterator; | ||
|
||
use crate::interface; | ||
use crate::interface::PiecePlacementParseError; | ||
use crate::interface::PositionType; | ||
use crate::interface::TypeParseError; | ||
use crate::interface::{BitBoardType, Hash, RepresentableType, SquareType}; | ||
|
||
use thiserror::Error; | ||
|
||
#[rustfmt::skip] | ||
use super::{ | ||
BitBoard, ColoredPiece, File, Move, | ||
Rank, Square, Color, Piece | ||
}; | ||
use crate::interface::MoveStore; | ||
|
||
/// Position represents the snapshot of an Ataxx Board, the state of the an | ||
/// ataxx game at a single point in time. It also provides all of the methods | ||
/// necessary to manipulate such a snapshot. | ||
#[derive(Copy, Clone)] | ||
pub struct Position { | ||
/// bitboards stores [BitBoard]s for the piece configuration of each piece. | ||
pub bitboards: [BitBoard; ColoredPiece::N], | ||
/// checksum stores the semi-unique [struct@Hash] of the current Position. | ||
pub checksum: Hash, | ||
/// side_to_move stores the piece whose turn to move it currently is. | ||
pub side_to_move: Color, | ||
pub ply_count: u16, | ||
} | ||
|
||
impl PositionType for Position { | ||
type BitBoard = BitBoard; | ||
type ColoredPiece = ColoredPiece; | ||
type Move = Move; | ||
|
||
fn insert(&mut self, sq: Square, piece: ColoredPiece) { | ||
self.bitboards[piece as usize].insert(sq); | ||
} | ||
|
||
fn remove(&mut self, sq: Square) -> Option<ColoredPiece> { | ||
match self.at(sq) { | ||
Some(piece) => { | ||
self.bitboards[piece as usize].remove(sq); | ||
Some(piece) | ||
} | ||
None => None, | ||
} | ||
} | ||
|
||
fn at(&self, sq: Square) -> Option<ColoredPiece> { | ||
ColoredPiece::iter() | ||
.find(|piece| self.colored_piece_bb(*piece).contains(sq)) | ||
} | ||
|
||
fn piece_bb(&self, piece: Piece) -> BitBoard { | ||
self.bitboards[piece as usize] | ||
} | ||
|
||
fn color_bb(&self, color: Color) -> BitBoard { | ||
self.bitboards[color as usize] | ||
} | ||
|
||
fn colored_piece_bb(&self, piece: ColoredPiece) -> BitBoard { | ||
self.bitboards[piece as usize] | ||
} | ||
|
||
fn hash(&self) -> Hash { | ||
self.checksum | ||
} | ||
|
||
fn is_game_over(&self) -> bool { | ||
let black = self.colored_piece_bb(ColoredPiece::WhitePawn); | ||
let white = self.colored_piece_bb(ColoredPiece::BlackPawn); | ||
let block = self.colored_piece_bb(ColoredPiece::Tile); | ||
|
||
white | black | block == BitBoard::UNIVERSE || // All squares occupied | ||
white == BitBoard::EMPTY || black == BitBoard::EMPTY // No pieces left | ||
} | ||
|
||
fn winner(&self) -> Option<Color> { | ||
let black = self.colored_piece_bb(ColoredPiece::WhitePawn); | ||
let white = self.colored_piece_bb(ColoredPiece::BlackPawn); | ||
let block = self.colored_piece_bb(ColoredPiece::Tile); | ||
|
||
if black == BitBoard::EMPTY { | ||
// Black lost all its pieces, White won. | ||
return Some(Color::White); | ||
} else if white == BitBoard::EMPTY { | ||
// White lost all its pieces, Black won. | ||
return Some(Color::Black); | ||
} | ||
|
||
debug_assert!(black | white | block == BitBoard::UNIVERSE); | ||
|
||
// All the squares are occupied by pieces. Victory is decided by | ||
// which Piece has the most number of pieces on the Board. | ||
|
||
let black_n = black.len(); | ||
let white_n = white.len(); | ||
|
||
match black_n.cmp(&white_n) { | ||
cmp::Ordering::Less => Some(Color::White), | ||
cmp::Ordering::Greater => Some(Color::Black), | ||
// Though there can't be an equal number of black and white pieces | ||
// on an empty ataxx board, it is possible with an odd number of | ||
// blocker pieces. | ||
cmp::Ordering::Equal => None, | ||
} | ||
} | ||
|
||
fn after_move<const UPDATE_HASH: bool>(&self, m: Move) -> Position { | ||
let stm = self.side_to_move; | ||
|
||
macro_rules! update_hash { | ||
($e:expr) => { | ||
if UPDATE_HASH { | ||
$e | ||
} else { | ||
Default::default() | ||
} | ||
}; | ||
} | ||
|
||
let xtm_pieces = self.color_bb(!stm); | ||
let new_stm = BitBoard::from(m.pawn()); | ||
let new_tiles = self.colored_piece_bb(ColoredPiece::Tile) | ||
^ BitBoard::from(m.tile()); | ||
|
||
let (white, black) = if stm == Color::White { | ||
(new_stm, xtm_pieces) | ||
} else { | ||
(xtm_pieces, new_stm) | ||
}; | ||
|
||
Position { | ||
bitboards: [white, black, new_tiles], | ||
checksum: update_hash!(Self::get_hash(black, white, !stm)), | ||
side_to_move: !stm, | ||
ply_count: self.ply_count + 1, | ||
} | ||
} | ||
|
||
fn generate_moves_into< | ||
const ALLOW_ILLEGAL: bool, | ||
const QUIET: bool, | ||
const NOISY: bool, | ||
T: MoveStore<Move>, | ||
>( | ||
&self, | ||
movelist: &mut T, | ||
) { | ||
let stm = self.color_bb(self.side_to_move); | ||
let xtm = self.color_bb(!self.side_to_move); | ||
|
||
let tiles = self.colored_piece_bb(ColoredPiece::Tile); | ||
|
||
// Pieces can only move to unoccupied Squares. | ||
let allowed = tiles ^ xtm; | ||
|
||
for target in BitBoard::singles(stm) & allowed { | ||
for tile in allowed ^ BitBoard::from(target) { | ||
movelist.push(Move::new(target, tile)); | ||
} | ||
} | ||
} | ||
|
||
fn count_moves<const QUIET: bool, const NOISY: bool>(&self) -> usize { | ||
let stm = self.color_bb(self.side_to_move); | ||
let xtm = self.color_bb(!self.side_to_move); | ||
|
||
let tiles = self.colored_piece_bb(ColoredPiece::Tile); | ||
|
||
// Pieces can only move to unoccupied Squares. | ||
let allowed = tiles ^ xtm; | ||
|
||
(BitBoard::singles(stm) & allowed).count() * (allowed.count() - 1) | ||
} | ||
} | ||
|
||
impl Position { | ||
fn get_hash(black: BitBoard, white: BitBoard, stm: Color) -> Hash { | ||
let a = black.into(); | ||
let b = white.into(); | ||
|
||
// Currently, an 2^-63-almost delta universal hash function, based on | ||
// https://eprint.iacr.org/2011/116.pdf by Long Hoang Nguyen and Andrew | ||
// William Roscoe is used to create the Hash. This may change in the future. | ||
|
||
// 3 64-bit integer constants used in the hash function. | ||
const X: u64 = 6364136223846793005; | ||
const Y: u64 = 1442695040888963407; | ||
const Z: u64 = 2305843009213693951; | ||
|
||
// xa + yb + floor(ya/2^64) + floor(zb/2^64) | ||
// floor(pq/2^64) is essentially getting the top 64 bits of p*q. | ||
let part_1 = X.wrapping_mul(a); // xa | ||
let part_2 = Y.wrapping_mul(b); // yb | ||
let part_3 = (Y as u128 * a as u128) >> 64; // floor(ya/2^64) = ya >> 64 | ||
let part_4 = (Z as u128 * b as u128) >> 64; // floor(zb/2^64) = zb >> 64 | ||
|
||
// add the parts together and return the resultant hash. | ||
let hash = part_1 | ||
.wrapping_add(part_2) | ||
.wrapping_add(part_3 as u64) | ||
.wrapping_add(part_4 as u64); | ||
|
||
// The Hash is bitwise complemented if the given side to move is Black. | ||
// Therefore, if two Positions only differ in side to move, | ||
// `a.Hash == !b.Hash`. | ||
if stm == Color::Black { | ||
Hash::new(!hash) | ||
} else { | ||
Hash::new(hash) | ||
} | ||
} | ||
} | ||
|
||
/// PositionParseErr represents an error encountered while parsing | ||
/// the given FEN position field into a valid Position. | ||
#[derive(Error, Debug)] | ||
pub enum PositionParseError { | ||
#[error("expected 3 fields, found {0}")] | ||
TooManyFields(usize), | ||
|
||
#[error("parsing piece placement: {0}")] | ||
BadPiecePlacement(#[from] PiecePlacementParseError), | ||
|
||
#[error("parsing side to move: {0}")] | ||
BadSideToMove(#[from] TypeParseError), | ||
#[error("parsing half-move clock: {0}")] | ||
BadHalfMoveClock(#[from] ParseIntError), | ||
} | ||
|
||
// FromStr implements parsing of the position field in a FEN. | ||
impl FromStr for Position { | ||
type Err = PositionParseError; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
let parts = s.split(' ').collect::<Vec<&str>>(); | ||
|
||
if parts.len() != 3 { | ||
return Err(PositionParseError::TooManyFields(parts.len())); | ||
} | ||
|
||
let pos = parts[0]; | ||
let stm = parts[1]; | ||
let fmc = parts[2]; | ||
|
||
let mut position = Position { | ||
bitboards: [BitBoard::EMPTY; ColoredPiece::N], | ||
checksum: Default::default(), | ||
side_to_move: Color::Black, | ||
ply_count: 0, | ||
}; | ||
|
||
interface::parse_piece_placement(&mut position, pos)?; | ||
|
||
position.side_to_move = Color::from_str(stm)?; | ||
position.ply_count = fmc.parse::<u16>()? * 2 - 1; | ||
if position.side_to_move == Color::Black { | ||
position.ply_count -= 1; | ||
} | ||
|
||
// Calculate the Hash value for the Position. | ||
position.checksum = Self::get_hash( | ||
position.colored_piece_bb(ColoredPiece::WhitePawn), | ||
position.colored_piece_bb(ColoredPiece::BlackPawn), | ||
position.side_to_move, | ||
); | ||
|
||
Ok(position) | ||
} | ||
} | ||
|
||
// Display implements displaying a Position using ASCII art. | ||
impl fmt::Display for Position { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let board = self; | ||
let mut string_rep = String::from(" "); | ||
|
||
for rank in Rank::iter().rev() { | ||
for file in File::iter() { | ||
let square = Square::new(file, rank); | ||
let square_str = match board.at(square) { | ||
Some(piece) => format!("{} ", piece), | ||
None => ". ".to_string(), | ||
}; | ||
string_rep += &square_str; | ||
} | ||
|
||
// Append the rank marker. | ||
string_rep += &format!(" {} \n ", rank); | ||
} | ||
|
||
// Append the file markers. | ||
string_rep += "a b c d e f g\n"; | ||
|
||
writeln!(f, "{}", string_rep).unwrap(); | ||
writeln!(f, "Side To Move: {}", self.side_to_move) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright © 2024 Rak Laptudirm <rak@laptudirm.com> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use std::fmt; | ||
use std::str::FromStr; | ||
|
||
use crate::interface::{representable_type, RepresentableType, SquareType}; | ||
|
||
representable_type!( | ||
/// Square represents all the squares present on an Ataxx Board. | ||
/// The index of each Square is equal to `rank-index * 8 + file-index`. | ||
enum Square: u8 { | ||
A1 "a1", B1 "b1", C1 "c1", D1 "d1", E1 "e1", F1 "f1", | ||
A2 "a2", B2 "b2", C2 "c2", D2 "d2", E2 "e2", F2 "f2", | ||
A3 "a3", B3 "b3", C3 "c3", D3 "d3", E3 "e3", F3 "f3", | ||
A4 "a4", B4 "b4", C4 "c4", D4 "d4", E4 "e4", F4 "f4", | ||
A5 "a5", B5 "b5", C5 "c5", D5 "d5", E5 "e5", F5 "f5", | ||
A6 "a6", B6 "b6", C6 "c6", D6 "d6", E6 "e6", F6 "f6", | ||
A7 "a7", B7 "b7", C7 "c7", D7 "d7", E7 "e7", F7 "f7", | ||
A8 "a8", B8 "b8", C8 "c8", D8 "d8", E8 "e8", F8 "f8", | ||
} | ||
); | ||
|
||
impl SquareType for Square { | ||
type File = File; | ||
type Rank = Rank; | ||
} | ||
|
||
representable_type!( | ||
/// File represents a file on the Ataxx Board. Each vertical column of Squares | ||
/// on an Ataxx Board is known as a File. There are 7 of them in total. | ||
enum File: u8 { A "a", B "b", C "c", D "d", E "e", F "f", } | ||
); | ||
|
||
representable_type!( | ||
/// Rank represents a rank on the Ataxx Board. Each horizontal row of Squares | ||
/// on an Ataxx Board is known as a Rank. There are 7 of them in total. | ||
enum Rank: u8 { | ||
First "1", Second "2", Third "3", Fourth "4", Fifth "5", Sixth "6", Seventh "7", Eighth "8", | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
pub mod ataxx; | ||
pub mod interface; | ||
pub mod isolation; | ||
|
||
use interface::PositionType; | ||
|
||
|