Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

game: isolation #14

Merged
merged 10 commits into from
Dec 16, 2024
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description = "A game engine Swiss-army knife"
repository = "https://github.com/raklaptudirm/tetka"

[dependencies]
strum = "0.26"
tetka-uxi = { package = "uxi", path = "./uxi" }
tetka-games = { path = "./games" }

Expand Down
2 changes: 1 addition & 1 deletion games/src/interface/move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub trait MoveStore<M>: Default {
/// current limitations in the Rust type system, the current max capacity is
/// capped at 256, which can be problematic for games which can have more moves
/// in a position and might require a custom type.
pub type MoveList<M> = ArrayVec<M, 256>;
pub type MoveList<M> = ArrayVec<M, 500>;

// MoveStore implementation for MoveList.
impl<M> MoveStore<M> for MoveList<M> {
Expand Down
44 changes: 44 additions & 0 deletions games/src/isolation/bitboard.rs
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(0xffffffffffff);

// BitBoards containing the squares of the first file and the first rank.
FirstFile = Self(0x0000010101010101);
FirstRank = Self(0x00000000000000ff);
}
}

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
}
}
18 changes: 18 additions & 0 deletions games/src/isolation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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;

#[cfg(test)]
mod tests;
169 changes: 169 additions & 0 deletions games/src/isolation/move.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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 Isolation 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;

/// new returns a new jump Move from the given pawn Square to the given
/// tile Square. These Squares can be recovered with the [`Move::pawn`] and
/// [`Move::tile`] methods respectively.
/// ```
/// use tetka_games::isolation::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(mov.pawn(), Square::A1);
/// assert_eq!(mov.tile(), Square::A3);
/// ```
#[inline(always)]
#[rustfmt::skip]
pub fn new(pawn: Square, tile: Square) -> Move {
Move(
(pawn as u16) << Move::PAWN_OFFSET |
(tile as u16) << Move::TILE_OFFSET
)
}

/// Source returns the pawn Square of the moving piece. This is equal to the
/// tile Square if the given Move is of singular type.
/// ```
/// use tetka_games::isolation::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(mov.pawn(), Square::A1);
/// ```
pub fn pawn(self) -> Square {
unsafe {
Square::unsafe_from((self.0 >> Move::PAWN_OFFSET) & Move::PAWN_MASK)
}
}

/// Target returns the tile Square of the moving piece.
/// ```
/// use tetka_games::isolation::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(mov.tile(), 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 pawn 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 format supported is `<pawn><tile>`. For how `<pawn>` and `<tile>` 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::isolation::*;
/// use std::str::FromStr;
///
/// let jump = Move::new(Square::A1, Square::A3);
/// assert_eq!(Move::from_str(&jump.to_string()).unwrap(), jump);
/// ```
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 4 {
return Err(MoveParseError::BadLength(s.len()));
}

let pawn = Square::from_str(&s[..2])?;
let tile = Square::from_str(&s[2..])?;

Ok(Move::new(pawn, tile))
}
}

impl fmt::Display for Move {
/// Display formats the given Move in a human-readable manner. The format used
/// for displaying moves is `<pawn><tile>`. For the formatting of `<pawn>` and
/// `<tile>`, refer to `Square::Display`. [`Move::NULL`] is formatted as `null`.
/// ```
/// use tetka_games::isolation::*;
///
/// let null = Move::NULL;
/// let jump = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(null.to_string(), "null");
/// 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)
}
}
66 changes: 66 additions & 0 deletions games/src/isolation/piece.rs
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"),
}
}
}
Loading
Loading