shogiutil 0.7.0

A collection of tools to handle shogi data.
Documentation
use crate::error::ShogiUtilError::CsaParseError;
use crate::{Bitboard, ShogiUtilError, Square};
use std::str::FromStr;

mod moves;
use moves::piece_moves;

#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Piece {
    None,
    Pawn,
    Lance,
    Knight,
    Silver,
    Gold,
    Bishop,
    Rook,
    King,
    ProPawn,
    ProLance,
    ProKnight,
    ProSilver,
    ProBishop,
    ProRook,
}

impl FromStr for Piece {
    type Err = ShogiUtilError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "* " => Ok(Piece::None),
            "FU" => Ok(Piece::Pawn),
            "KY" => Ok(Piece::Lance),
            "KE" => Ok(Piece::Knight),
            "GI" => Ok(Piece::Silver),
            "KI" => Ok(Piece::Gold),
            "KA" => Ok(Piece::Bishop),
            "HI" => Ok(Piece::Rook),
            "OU" => Ok(Piece::King),
            "TO" => Ok(Piece::ProPawn),
            "NY" => Ok(Piece::ProLance),
            "NK" => Ok(Piece::ProKnight),
            "NG" => Ok(Piece::ProSilver),
            "UM" => Ok(Piece::ProBishop),
            "RY" => Ok(Piece::ProRook),
            _ => Err(CsaParseError(format!("Invalid piece type: {}", s))),
        }
    }
}

impl From<u8> for Piece {
    fn from(b: u8) -> Self {
        match b {
            0 => Piece::None,
            1 => Piece::Pawn,
            2 => Piece::Lance,
            3 => Piece::Knight,
            4 => Piece::Silver,
            5 => Piece::Gold,
            6 => Piece::Bishop,
            7 => Piece::Rook,
            8 => Piece::King,
            9 => Piece::ProPawn,
            10 => Piece::ProLance,
            11 => Piece::ProKnight,
            12 => Piece::ProSilver,
            13 => Piece::ProBishop,
            14 => Piece::ProRook,
            _ => unreachable!(),
        }
    }
}

impl Piece {
    pub fn to_byte(&self) -> u8 {
        match self {
            Piece::None => 0,
            Piece::Pawn => 1,
            Piece::Lance => 2,
            Piece::Knight => 3,
            Piece::Silver => 4,
            Piece::Gold => 5,
            Piece::Bishop => 6,
            Piece::Rook => 7,
            Piece::King => 8,
            Piece::ProPawn => 9,
            Piece::ProLance => 10,
            Piece::ProKnight => 11,
            Piece::ProSilver => 12,
            Piece::ProBishop => 13,
            Piece::ProRook => 14,
        }
    }

    pub fn to_usize(&self) -> usize {
        self.to_byte() as usize
    }

    pub fn promote(&self) -> Option<Piece> {
        match self {
            Piece::Pawn => Some(Piece::ProPawn),
            Piece::Lance => Some(Piece::ProLance),
            Piece::Knight => Some(Piece::ProKnight),
            Piece::Silver => Some(Piece::ProSilver),
            Piece::Bishop => Some(Piece::ProBishop),
            Piece::Rook => Some(Piece::ProRook),
            _ => None,
        }
    }

    pub fn to_csa(&self) -> String {
        match self {
            Piece::None => "* ".to_string(),
            Piece::Pawn => "FU".to_string(),
            Piece::Lance => "KY".to_string(),
            Piece::Knight => "KE".to_string(),
            Piece::Silver => "GI".to_string(),
            Piece::Gold => "KI".to_string(),
            Piece::Bishop => "KA".to_string(),
            Piece::Rook => "HI".to_string(),
            Piece::King => "OU".to_string(),
            Piece::ProPawn => "TO".to_string(),
            Piece::ProLance => "NY".to_string(),
            Piece::ProKnight => "NK".to_string(),
            Piece::ProSilver => "NG".to_string(),
            Piece::ProBishop => "UM".to_string(),
            Piece::ProRook => "RY".to_string(),
        }
    }

    pub fn to_sfen(&self) -> char {
        match self {
            Piece::Pawn => 'P',
            Piece::Lance => 'L',
            Piece::Knight => 'N',
            Piece::Silver => 'S',
            Piece::Gold => 'G',
            Piece::Bishop => 'B',
            Piece::Rook => 'R',
            Piece::King => 'K',
            _ => unreachable!(),
        }
    }

    pub fn revert_promotion(&self) -> Option<Piece> {
        match self {
            Piece::ProPawn => Some(Piece::Pawn),
            Piece::ProLance => Some(Piece::Lance),
            Piece::ProKnight => Some(Piece::Knight),
            Piece::ProSilver => Some(Piece::Silver),
            Piece::ProBishop => Some(Piece::Bishop),
            Piece::ProRook => Some(Piece::Rook),
            _ => None,
        }
    }

    pub fn is_valid_promotion(&self, promoted: &Piece) -> bool {
        if let Some(valid_promotion) = self.promote() {
            &valid_promotion == promoted
        } else {
            false
        }
    }

    pub fn is_promoted(&self) -> bool {
        self == &Piece::ProPawn
            || self == &Piece::ProLance
            || self == &Piece::ProKnight
            || self == &Piece::ProSilver
            || self == &Piece::ProBishop
            || self == &Piece::ProRook
    }

    pub const fn max_piece_in_hand(&self) -> usize {
        match self {
            Piece::Pawn => 18,
            Piece::Lance => 4,
            Piece::Knight => 4,
            Piece::Silver => 4,
            Piece::Gold => 4,
            Piece::Bishop => 2,
            Piece::Rook => 2,
            _ => 0,
        }
    }

    pub fn generate_moves(&self, from: &Square, occupied: &[Bitboard; 2], moves: &mut Vec<Square>) {
        piece_moves(*self, from, occupied, moves)
    }
}

#[cfg(test)]
mod tests {
    use crate::debug::generate_bitboard;
    use crate::piece::Piece;
    use crate::Square;

    #[test]
    fn test_from_to_byte() {
        for i in 0..15 {
            let piece = Piece::from(i);
            assert_eq!(piece.to_byte(), i);
        }
    }

    #[test]
    fn test_pawn_generate_moves() {
        let occupied = [
            generate_bitboard(
                r"
            .........
            .........
            .........
            ....#....
            .........
            .........
            .........
            .........
            .........
        ",
            ),
            generate_bitboard(
                r"
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
        ",
            ),
        ];
        let mut moves = vec![];
        Piece::Pawn.generate_moves(&Square { file: 5, rank: 5 }, &occupied, &mut moves);
        assert!(moves.is_empty());

        let occupied = [
            generate_bitboard(
                r"
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
        ",
            ),
            generate_bitboard(
                r"
            .........
            .........
            .........
            ....#....
            .........
            .........
            .........
            .........
            .........
        ",
            ),
        ];
        let mut moves = vec![];
        Piece::Pawn.generate_moves(&Square { file: 5, rank: 5 }, &occupied, &mut moves);
        assert_eq!(moves, [Square { file: 5, rank: 4 }]);
    }
    #[test]
    fn test_lance_generate_moves() {
        let occupied = [
            generate_bitboard(
                r"
            .........
            ....#....
            .........
            .........
            .........
            .........
            .........
            .........
            .........
        ",
            ),
            generate_bitboard(
                r"
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
        ",
            ),
        ];
        let mut moves = vec![];
        Piece::Lance.generate_moves(&Square { file: 5, rank: 5 }, &occupied, &mut moves);
        assert_eq!(
            moves,
            [Square { file: 5, rank: 4 }, Square { file: 5, rank: 3 }]
        );

        let occupied = [
            generate_bitboard(
                r"
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
            .........
        ",
            ),
            generate_bitboard(
                r"
            .........
            ....#....
            .........
            .........
            .........
            .........
            .........
            .........
            .........
        ",
            ),
        ];
        let mut moves = vec![];
        Piece::Lance.generate_moves(&Square { file: 5, rank: 5 }, &occupied, &mut moves);
        assert_eq!(
            moves,
            [
                Square { file: 5, rank: 4 },
                Square { file: 5, rank: 3 },
                Square { file: 5, rank: 2 }
            ]
        );
    }
}