Skip to content

Commit

Permalink
introduce trait Enum to reduce boilerplate
Browse files Browse the repository at this point in the history
  • Loading branch information
brunocodutra committed Feb 3, 2024
1 parent 11655fc commit dbce683
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 392 deletions.
68 changes: 8 additions & 60 deletions lib/chess/color.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::util::Enum;
use cozy_chess as cc;
use derive_more::Display;
use std::ops::Not;
use std::ops::{Not, RangeInclusive};

/// The color of a chess [`Piece`][`crate::Piece`].
#[derive(Debug, Display, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
Expand All @@ -13,35 +14,19 @@ pub enum Color {
Black,
}

impl Color {
pub const ALL: [Self; 2] = [Color::White, Color::Black];
unsafe impl Enum for Color {
const RANGE: RangeInclusive<Self> = Color::White..=Color::Black;

/// Constructs [`Color`] from index.
///
/// # Panics
///
/// Panics if `i` is not in the range (0..=1).
pub fn from_index(i: u8) -> Self {
Self::ALL[i as usize]
}

/// This colors's index in the range (0..=1).
pub fn index(&self) -> u8 {
#[inline(always)]
fn repr(&self) -> u8 {
*self as _
}

/// Mirrors this color.
pub fn mirror(&self) -> Self {
match self {
Color::White => Color::Black,
Color::Black => Color::White,
}
}
}

impl Not for Color {
type Output = Self;

#[inline(always)]
fn not(self) -> Self {
self.mirror()
}
Expand Down Expand Up @@ -72,7 +57,6 @@ impl From<Color> for cc::Color {
#[cfg(test)]
mod tests {
use super::*;
use crate::util::Buffer;
use std::mem::size_of;
use test_strategy::proptest;

Expand All @@ -81,45 +65,9 @@ mod tests {
assert_eq!(size_of::<Option<Color>>(), size_of::<Color>());
}

#[proptest]
fn color_has_an_index(c: Color) {
assert_eq!(Color::from_index(c.index()), c);
}

#[proptest]

fn from_index_constructs_color_by_index(#[strategy(0u8..2)] i: u8) {
assert_eq!(Color::from_index(i).index(), i);
}

#[proptest]
#[should_panic]

fn from_index_panics_if_index_out_of_range(#[strategy(2u8..)] i: u8) {
Color::from_index(i);
}

#[proptest]
fn color_is_ordered_by_index(a: Color, b: Color) {
assert_eq!(a < b, a.index() < b.index());
}

#[proptest]
fn all_contains_colors_in_order() {
assert_eq!(
Color::ALL.into_iter().collect::<Buffer<_, 2>>(),
(0..=1).map(Color::from_index).collect()
);
}

#[proptest]
fn color_has_a_mirror(c: Color) {
assert_eq!(c.mirror().index(), 1 - c.index());
}

#[proptest]
fn color_implements_not_operator(c: Color) {
assert_eq!(!!c, c);
assert_eq!(!c, c.mirror());
}

#[proptest]
Expand Down
78 changes: 10 additions & 68 deletions lib/chess/file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::util::Enum;
use cozy_chess as cc;
use derive_more::Display;
use std::ops::Sub;
use std::ops::{RangeInclusive, Sub};

/// A column on the chess board.
#[derive(Debug, Display, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
Expand All @@ -25,51 +26,29 @@ pub enum File {
H,
}

impl File {
pub const ALL: [Self; 8] = [
File::A,
File::B,
File::C,
File::D,
File::E,
File::F,
File::G,
File::H,
];
unsafe impl Enum for File {
const RANGE: RangeInclusive<Self> = File::A..=File::H;

/// Constructs [`File`] from index.
///
/// # Panics
///
/// Panics if `i` is not in the range (0..=7).
pub fn from_index(i: u8) -> Self {
Self::ALL[i as usize]
}

/// This files's index in the range (0..=7).
pub fn index(&self) -> u8 {
#[inline(always)]
fn repr(&self) -> u8 {
*self as _
}

/// Mirrors this file.
pub fn mirror(&self) -> Self {
Self::from_index(File::H as u8 - *self as u8)
}
}

impl Sub for File {
type Output = i8;

#[inline(always)]
fn sub(self, rhs: Self) -> Self::Output {
self.index() as i8 - rhs.index() as i8
self as i8 - rhs as i8
}
}

#[doc(hidden)]
impl From<cc::File> for File {
#[inline(always)]
fn from(f: cc::File) -> Self {
File::from_index(f as _)
File::from_repr(f as _)
}
}

Expand All @@ -84,7 +63,6 @@ impl From<File> for cc::File {
#[cfg(test)]
mod tests {
use super::*;
use crate::util::Buffer;
use std::mem::size_of;
use test_strategy::proptest;

Expand All @@ -93,45 +71,9 @@ mod tests {
assert_eq!(size_of::<Option<File>>(), size_of::<File>());
}

#[proptest]
fn file_has_an_index(f: File) {
assert_eq!(File::from_index(f.index()), f);
}

#[proptest]

fn from_index_constructs_file_by_index(#[strategy(0u8..8)] i: u8) {
assert_eq!(File::from_index(i).index(), i);
}

#[proptest]
#[should_panic]

fn from_index_panics_if_index_out_of_range(#[strategy(8u8..)] i: u8) {
File::from_index(i);
}

#[proptest]
fn file_is_ordered_by_index(a: File, b: File) {
assert_eq!(a < b, a.index() < b.index());
}

#[proptest]
fn all_contains_files_in_order() {
assert_eq!(
File::ALL.into_iter().collect::<Buffer<_, 8>>(),
(0..=7).map(File::from_index).collect()
);
}

#[proptest]
fn file_has_a_mirror(f: File) {
assert_eq!(f.mirror().index(), 7 - f.index());
}

#[proptest]
fn subtracting_files_gives_distance(a: File, b: File) {
assert_eq!(a - b, a.index() as i8 - b.index() as i8);
assert_eq!(a - b, a as i8 - b as i8);
}

#[proptest]
Expand Down
6 changes: 3 additions & 3 deletions lib/chess/move.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::chess::{Role, Square};
use crate::util::{Assume, Binary, Bits};
use crate::util::{Assume, Binary, Bits, Enum};
use derive_more::{Display, Error};
use std::{fmt, num::NonZeroU16, ops::RangeBounds};

Expand Down Expand Up @@ -52,7 +52,7 @@ impl Move {
bits.push(Bits::<u8, 1>::new(promotion.is_some() as _));
bits.push(Bits::<u8, 1>::new(capture.is_some() as _));
bits.push(Bits::<u8, 2>::new(
promotion.map_or(0, |r| r.index().clamp(1, 4) - 1),
promotion.map_or(0, |r| r.repr().clamp(1, 4) - 1),
));
}

Expand All @@ -75,7 +75,7 @@ impl Move {
#[inline(always)]
pub fn promotion(&self) -> Option<Role> {
if self.is_promotion() {
Some(Role::from_index(self.bits(..2).get() as u8 + 1))
Some(Role::from_repr(self.bits(..2).get() as u8 + 1))
} else {
None
}
Expand Down
86 changes: 23 additions & 63 deletions lib/chess/piece.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::chess::{Color, Role};
use crate::util::Enum;
use std::ops::RangeInclusive;

/// A chess [piece][`Role`] of a certain [`Color`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
Expand All @@ -20,62 +22,51 @@ pub enum Piece {
}

impl Piece {
pub const ALL: [Self; 12] = [
Piece::WhitePawn,
Piece::BlackPawn,
Piece::WhiteKnight,
Piece::BlackKnight,
Piece::WhiteBishop,
Piece::BlackBishop,
Piece::WhiteRook,
Piece::BlackRook,
Piece::WhiteQueen,
Piece::BlackQueen,
Piece::WhiteKing,
Piece::BlackKing,
];

/// Constructs [`Piece`] from a pair of [`Color`] and [`Role`].
#[inline(always)]
pub fn new(r: Role, c: Color) -> Self {
Self::from_index(r.index() * 2 + c.index())
Self::from_repr(r.repr() * 2 + c.repr())
}

/// This piece's [`Role`].
#[inline(always)]
pub fn role(&self) -> Role {
Role::from_index(self.index() / 2)
Role::from_repr(self.repr() / 2)
}

/// This piece's [`Color`].
#[inline(always)]
pub fn color(&self) -> Color {
Color::from_index(self.index() % 2)
Color::from_repr(self.repr() % 2)
}
}

/// This piece's index in the range (0..12).
pub fn index(&self) -> u8 {
*self as _
}
unsafe impl Enum for Piece {
const RANGE: RangeInclusive<Self> = Piece::WhitePawn..=Piece::BlackKing;

/// Constructs [`Piece`] from index.
///
/// # Panics
///
/// Panics if `i` is not in the range (0..=11).
pub fn from_index(i: u8) -> Self {
Self::ALL[i as usize]
#[inline(always)]
fn repr(&self) -> u8 {
*self as _
}

/// This piece's mirror of the same [`Role`] and opposite [`Color`].
pub fn mirror(&self) -> Self {
Self::from_index(self.index() ^ Piece::BlackPawn.index())
#[inline(always)]
fn mirror(&self) -> Self {
Self::from_repr(self.repr() ^ Piece::BlackPawn.repr())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::util::Buffer;
use std::mem::size_of;
use test_strategy::proptest;

#[proptest]
fn piece_guarantees_zero_value_optimization() {
assert_eq!(size_of::<Option<Piece>>(), size_of::<Piece>());
}

#[proptest]
fn piece_has_a_color(r: Role, c: Color) {
assert_eq!(Piece::new(r, c).color(), c);
Expand All @@ -86,37 +77,6 @@ mod tests {
assert_eq!(Piece::new(r, c).role(), r);
}

#[proptest]
fn piece_has_an_index(p: Piece) {
assert_eq!(Piece::from_index(p.index()), p);
}

#[proptest]

fn from_index_constructs_piece_by_index(#[strategy(0u8..12)] i: u8) {
assert_eq!(Piece::from_index(i).index(), i);
}

#[proptest]
#[should_panic]

fn from_index_panics_if_index_out_of_range(#[strategy(6u8..)] i: u8) {
Role::from_index(i);
}

#[proptest]
fn piece_is_ordered_by_index(a: Piece, b: Piece) {
assert_eq!(a < b, a.index() < b.index());
}

#[proptest]
fn all_contains_pieces_in_order() {
assert_eq!(
Piece::ALL.into_iter().collect::<Buffer<_, 12>>(),
(0..12).map(Piece::from_index).collect()
);
}

#[proptest]
fn piece_has_a_mirror_of_the_same_role_and_opposite_color(p: Piece) {
assert_eq!(p.mirror().role(), p.role());
Expand Down
Loading

0 comments on commit dbce683

Please sign in to comment.