Skip to content

Commit 05830d6

Browse files
authored
game: isolation (#14)
Isolation is a 2-player game in which each player tries to isolate the other player's pawn. https://en.wikipedia.org/wiki/Isolation_(board_game)
1 parent 48cd33d commit 05830d6

File tree

10 files changed

+681
-1
lines changed

10 files changed

+681
-1
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ description = "A game engine Swiss-army knife"
1010
repository = "https://github.com/raklaptudirm/tetka"
1111

1212
[dependencies]
13+
strum = "0.26"
1314
tetka-uxi = { package = "uxi", path = "./uxi" }
1415
tetka-games = { path = "./games" }
1516

games/src/interface/move.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub trait MoveStore<M>: Default {
4242
/// current limitations in the Rust type system, the current max capacity is
4343
/// capped at 256, which can be problematic for games which can have more moves
4444
/// in a position and might require a custom type.
45-
pub type MoveList<M> = ArrayVec<M, 256>;
45+
pub type MoveList<M> = ArrayVec<M, 500>;
4646

4747
// MoveStore implementation for MoveList.
4848
impl<M> MoveStore<M> for MoveList<M> {

games/src/isolation/bitboard.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright © 2024 Rak Laptudirm <[email protected]>
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
use crate::interface::bitboard_type;
15+
16+
use super::Square;
17+
18+
bitboard_type! {
19+
/// A set of Squares implemented as a bitset where the `1 << sq.into()` bit
20+
/// represents whether `sq` is in the BitBoard or not.
21+
struct BitBoard : u64 {
22+
// The BitBoard's Square type.
23+
Square = Square;
24+
25+
// BitBoards representing the null and the universe sets.
26+
Empty = Self(0);
27+
Universe = Self(0xffffffffffff);
28+
29+
// BitBoards containing the squares of the first file and the first rank.
30+
FirstFile = Self(0x0000010101010101);
31+
FirstRank = Self(0x00000000000000ff);
32+
}
33+
}
34+
35+
use crate::interface::{BitBoardType, RepresentableType};
36+
37+
impl BitBoard {
38+
/// singles returns the targets of all singular moves from all the source
39+
/// squares given in the provided BitBoard.
40+
pub fn singles(bb: BitBoard) -> BitBoard {
41+
let bar = bb | bb.east() | bb.west();
42+
(bar | bar.north() | bar.south()) ^ bb
43+
}
44+
}

games/src/isolation/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Make the contents of the non-namespaced
2+
// modules public, so they can be accessed
3+
// without their parent namespace.
4+
pub use self::bitboard::*;
5+
pub use self::piece::*;
6+
pub use self::position::*;
7+
pub use self::r#move::*;
8+
pub use self::square::*;
9+
10+
// Non-namespaced modules.
11+
mod bitboard;
12+
mod r#move;
13+
mod piece;
14+
mod position;
15+
mod square;
16+
17+
#[cfg(test)]
18+
mod tests;

games/src/isolation/move.rs

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright © 2024 Rak Laptudirm <[email protected]>
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
use std::fmt;
15+
use std::str::FromStr;
16+
17+
use thiserror::Error;
18+
19+
use super::Square;
20+
use crate::interface::{MoveType, RepresentableType, TypeParseError};
21+
22+
/// Move represents an Isolation move which can be played on the Board.
23+
#[derive(Copy, Clone, PartialEq, Eq, Default)]
24+
pub struct Move(u16);
25+
26+
impl MoveType for Move {
27+
const NULL: Self = Move(1 << 15);
28+
const MAX_IN_GAME: usize = 48;
29+
const MAX_IN_POSITION: usize = 352;
30+
}
31+
32+
impl From<u16> for Move {
33+
fn from(value: u16) -> Self {
34+
Move(value)
35+
}
36+
}
37+
38+
impl From<Move> for u16 {
39+
fn from(value: Move) -> Self {
40+
value.0
41+
}
42+
}
43+
44+
impl Move {
45+
// Bit-widths of fields.
46+
const PAWN_WIDTH: u16 = 6;
47+
const TILE_WIDTH: u16 = 6;
48+
49+
// Bit-masks of fields.
50+
const PAWN_MASK: u16 = (1 << Move::PAWN_WIDTH) - 1;
51+
const TILE_MASK: u16 = (1 << Move::TILE_WIDTH) - 1;
52+
53+
// Bit-offsets of fields.
54+
const PAWN_OFFSET: u16 = 0;
55+
const TILE_OFFSET: u16 = Move::PAWN_OFFSET + Move::PAWN_WIDTH;
56+
57+
/// new returns a new jump Move from the given pawn Square to the given
58+
/// tile Square. These Squares can be recovered with the [`Move::pawn`] and
59+
/// [`Move::tile`] methods respectively.
60+
/// ```
61+
/// use tetka_games::isolation::*;
62+
///
63+
/// let mov = Move::new(Square::A1, Square::A3);
64+
///
65+
/// assert_eq!(mov.pawn(), Square::A1);
66+
/// assert_eq!(mov.tile(), Square::A3);
67+
/// ```
68+
#[inline(always)]
69+
#[rustfmt::skip]
70+
pub fn new(pawn: Square, tile: Square) -> Move {
71+
Move(
72+
(pawn as u16) << Move::PAWN_OFFSET |
73+
(tile as u16) << Move::TILE_OFFSET
74+
)
75+
}
76+
77+
/// Source returns the pawn Square of the moving piece. This is equal to the
78+
/// tile Square if the given Move is of singular type.
79+
/// ```
80+
/// use tetka_games::isolation::*;
81+
///
82+
/// let mov = Move::new(Square::A1, Square::A3);
83+
///
84+
/// assert_eq!(mov.pawn(), Square::A1);
85+
/// ```
86+
pub fn pawn(self) -> Square {
87+
unsafe {
88+
Square::unsafe_from((self.0 >> Move::PAWN_OFFSET) & Move::PAWN_MASK)
89+
}
90+
}
91+
92+
/// Target returns the tile Square of the moving piece.
93+
/// ```
94+
/// use tetka_games::isolation::*;
95+
///
96+
/// let mov = Move::new(Square::A1, Square::A3);
97+
///
98+
/// assert_eq!(mov.tile(), Square::A3);
99+
/// ```
100+
pub fn tile(self) -> Square {
101+
unsafe {
102+
Square::unsafe_from((self.0 >> Move::TILE_OFFSET) & Move::TILE_MASK)
103+
}
104+
}
105+
}
106+
107+
#[derive(Error, Debug)]
108+
pub enum MoveParseError {
109+
#[error("length of move string should be 2 or 4, not {0}")]
110+
BadLength(usize),
111+
#[error("bad pawn square string \"{0}\"")]
112+
BadSquare(#[from] TypeParseError),
113+
}
114+
115+
impl FromStr for Move {
116+
type Err = MoveParseError;
117+
118+
/// from_str converts the given string representation of a Move into a [Move].
119+
/// The format supported is `<pawn><tile>`. For how `<pawn>` and `<tile>` are
120+
/// parsed, take a look at [`Square::FromStr`](Square::from_str). This function
121+
/// can be treated as the inverse of the [`fmt::Display`] trait for [Move].
122+
/// ```
123+
/// use tetka_games::isolation::*;
124+
/// use std::str::FromStr;
125+
///
126+
/// let jump = Move::new(Square::A1, Square::A3);
127+
/// assert_eq!(Move::from_str(&jump.to_string()).unwrap(), jump);
128+
/// ```
129+
fn from_str(s: &str) -> Result<Self, Self::Err> {
130+
if s.len() != 4 {
131+
return Err(MoveParseError::BadLength(s.len()));
132+
}
133+
134+
let pawn = Square::from_str(&s[..2])?;
135+
let tile = Square::from_str(&s[2..])?;
136+
137+
Ok(Move::new(pawn, tile))
138+
}
139+
}
140+
141+
impl fmt::Display for Move {
142+
/// Display formats the given Move in a human-readable manner. The format used
143+
/// for displaying moves is `<pawn><tile>`. For the formatting of `<pawn>` and
144+
/// `<tile>`, refer to `Square::Display`. [`Move::NULL`] is formatted as `null`.
145+
/// ```
146+
/// use tetka_games::isolation::*;
147+
///
148+
/// let null = Move::NULL;
149+
/// let jump = Move::new(Square::A1, Square::A3);
150+
///
151+
/// assert_eq!(null.to_string(), "null");
152+
/// assert_eq!(jump.to_string(), "a1a3");
153+
/// ```
154+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155+
if *self == Move::NULL {
156+
write!(f, "null")
157+
} else {
158+
write!(f, "{}{}", self.pawn(), self.tile())
159+
}
160+
}
161+
}
162+
163+
impl fmt::Debug for Move {
164+
/// Debug formats the given Move into a human-readable debug string. It uses
165+
/// `Move::Display` trait under the hood for formatting the Move.
166+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167+
write!(f, "{}", self)
168+
}
169+
}

games/src/isolation/piece.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright © 2024 Rak Laptudirm <[email protected]>
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
use std::fmt;
15+
use std::ops;
16+
use std::str::FromStr;
17+
18+
use crate::interface::representable_type;
19+
use crate::interface::ColoredPieceType;
20+
use crate::interface::RepresentableType;
21+
22+
representable_type!(
23+
/// Color represents all the possible colors that an ataxx piece can have,
24+
/// specifically, Black and White.
25+
enum Color: u8 { White "w", Black "b", }
26+
);
27+
28+
impl ops::Not for Color {
29+
type Output = Color;
30+
31+
/// not implements the not unary operator (!) which switches the current Color
32+
/// to its opposite, i.e. [`Color::Black`] to [`Color::White`] and vice versa.
33+
fn not(self) -> Self::Output {
34+
unsafe { Color::unsafe_from(self as usize ^ 1) }
35+
}
36+
}
37+
38+
representable_type!(
39+
/// Piece represents the types of pieces in ataxx, namely Piece and Block.
40+
enum Piece: u8 { Pawn "p", Tile "-", }
41+
);
42+
43+
representable_type!(
44+
/// Piece represents all the possible ataxx pieces.
45+
enum ColoredPiece: u8 { WhitePawn "P", BlackPawn "p", Tile "-", }
46+
);
47+
48+
impl ColoredPieceType for ColoredPiece {
49+
type Piece = Piece;
50+
type Color = Color;
51+
52+
fn piece(self) -> Piece {
53+
match self {
54+
ColoredPiece::WhitePawn | ColoredPiece::BlackPawn => Piece::Pawn,
55+
ColoredPiece::Tile => Piece::Tile,
56+
}
57+
}
58+
59+
fn color(self) -> Color {
60+
match self {
61+
ColoredPiece::WhitePawn => Color::White,
62+
ColoredPiece::BlackPawn => Color::Black,
63+
_ => panic!("Piece::color() called on Piece::Tile"),
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)