owlchess 0.4.1

Yet another chess library for Rust
Documentation
//! Correctness verification (not useful except for testing purposes)
//!
//! This module contains correctness checks for the crate. The contents of this module is not
//! useful for applications (as it mostly does nothing except consuming CPU time), but is
//! very useful to test the crate.

use crate::board::Board;
use crate::movegen::{legal, semilegal, MoveList};
use crate::moves::{self, Move, MoveKind, Style};
use crate::types::{Cell, Coord, Piece};

#[derive(Debug, Eq)]
struct BoardFullEq<'a>(&'a Board);

impl<'a> PartialEq for BoardFullEq<'a> {
    #[inline]
    fn eq(&self, other: &BoardFullEq<'a>) -> bool {
        return self.0.r == other.0.r
            && self.0.hash == other.0.hash
            && self.0.white == other.0.white
            && self.0.black == other.0.black
            && self.0.all == other.0.all
            && self.0.pieces == other.0.pieces;
    }
}

fn test_board_valid(b: &Board) {
    let b_other = b.raw().try_into().unwrap();
    assert_eq!(BoardFullEq(&b_other), BoardFullEq(b));
}

fn move_key(m: &Move) -> (u8, u8, u8, u8) {
    (
        m.kind() as u8,
        m.src_cell().index() as u8,
        m.src().index() as u8,
        m.dst().index() as u8,
    )
}

/// Performs correctness checks for the given board `b`
///
/// If this function panics, then this is a bug in `owlchess`.
pub fn selftest(b: &Board) {
    // Check that the board itself is valid
    test_board_valid(b);

    // Check that `as_fen()` and `from_fen()` are symmetrical
    let fen = b.as_fen();
    assert_eq!(Board::from_fen(&fen).as_ref(), Ok(b));

    // Try to generate moves in total and compare the result if we generate simple moves and
    // captures separately
    let mut moves = semilegal::gen_all(b);
    moves.sort_by_key(move_key);
    let mut moves_simple = semilegal::gen_simple(b);
    moves_simple.sort_by_key(move_key);

    let mut moves2 = semilegal::gen_simple_no_promote(b);
    semilegal::gen_simple_promote_into(b, &mut moves2);
    moves2.sort_by_key(move_key);
    assert_eq!(moves_simple, moves2);

    semilegal::gen_capture_into(b, &mut moves2);
    moves2.sort_by_key(move_key);
    assert_eq!(moves, moves2);

    // Check that all the generated moves are well-formed
    for mv in &moves {
        assert!(mv.is_well_formed());
    }

    // Check that converting semilegal moves from/to UCI yields the same results
    for mv in &moves {
        assert_eq!(Move::from_uci(&mv.to_string(), b), Ok(*mv));
    }

    // Check that a well-formed move is generated by `semilegal::gen_all()` iff
    // `mv.semi_validate()` returns `Ok(())`. Note that we consider only non-null
    // moves with `side == b.side()`.
    let mut semilegals = MoveList::new();
    for kind in [
        MoveKind::Simple,
        MoveKind::CastlingKingside,
        MoveKind::CastlingQueenside,
        MoveKind::PawnDouble,
        MoveKind::Enpassant,
        MoveKind::PromoteKnight,
        MoveKind::PromoteBishop,
        MoveKind::PromoteRook,
        MoveKind::PromoteQueen,
    ] {
        for piece in Piece::iter() {
            if !kind.matches_piece(piece) {
                continue;
            }
            for src in Coord::iter() {
                for dst in Coord::iter() {
                    if let Ok(mv) = Move::new(kind, Cell::from_parts(b.side(), piece), src, dst) {
                        if mv.semi_validate(b).is_ok() {
                            semilegals.push(mv);
                        }
                    }
                }
            }
        }
    }
    semilegals.sort_by_key(move_key);
    assert_eq!(moves, semilegals);

    // Check that making and unmaking move doesn't change anything
    let mut legals1 = MoveList::new();
    let mut b_clone = b.clone();
    for mv in &moves {
        unsafe {
            let undo = moves::make_move_unchecked(&mut b_clone, *mv);
            if !b_clone.is_opponent_king_attacked() {
                test_board_valid(&b_clone);
                legals1.push(*mv);
            }
            moves::unmake_move_unchecked(&mut b_clone, *mv, undo);
        }
        assert_eq!(&b_clone, b);
    }
    legals1.sort_by_key(move_key);

    // Check that legal moves are determined correctly in three ways
    // (`is_opponent_king_attacked`, `Move::is_legal_unchecked` and legal movegen).
    let mut legals2 = moves;
    legals2.retain(|mv| unsafe { mv.is_legal_unchecked(b) });
    legals2.sort_by_key(move_key);

    let mut legals3 = legal::gen_all(b);
    legals3.sort_by_key(move_key);

    assert_eq!(legals1, legals2);
    assert_eq!(legals1, legals3);

    // Check that converting legal moves from/to SAN yields the same results
    for mv in &legals1 {
        assert_eq!(
            Move::from_san(&mv.styled(b, Style::San).unwrap().to_string(), b),
            Ok(*mv),
        );
    }

    // Check that `Move::src_cell` works correctly
    for mv in &legals1 {
        assert_eq!(mv.src_cell(), b.get(mv.src()));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::board::Board;

    #[test]
    fn test_simple() {
        selftest(&Board::initial());
        selftest(
            &Board::from_fen("r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4")
                .unwrap(),
        )
    }
}