rshogi-core 0.2.4

A high-performance shogi engine core library with NNUE evaluation
Documentation
// 1手詰め探索用の初期化テーブル

use crate::bitboard::{
    Bitboard, bishop_effect, gold_effect, king_effect, knight_effect, lance_effect, pawn_effect,
    rook_effect, silver_effect,
};
use crate::mate::cross45_step_effect;
use crate::types::{Color, File, PieceType, Rank, Square};
use std::sync::LazyLock;

/// 王手になる駒の種類(PieceTypeCheckの列挙)
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PieceTypeCheck {
    /// 不成りのまま王手になるところ(成れる場合は含まず)
    PawnWithNoPro = 0,
    /// 成りで王手になるところ
    PawnWithPro = 1,
    /// 香での王手
    Lance = 2,
    /// 桂での王手
    Knight = 3,
    /// 銀での王手
    Silver = 4,
    /// 金での王手
    Gold = 5,
    /// 角での王手
    Bishop = 6,
    /// 飛での王手
    Rook = 7,
    /// 馬での王手
    ProBishop = 8,
    /// 龍での王手
    ProRook = 9,
    /// 非遠方駒の合体bitboard
    NonSlider = 10,
}

impl PieceTypeCheck {
    pub const NUM: usize = 11;

    #[inline]
    pub const fn from_u8(v: u8) -> Option<Self> {
        match v {
            0 => Some(Self::PawnWithNoPro),
            1 => Some(Self::PawnWithPro),
            2 => Some(Self::Lance),
            3 => Some(Self::Knight),
            4 => Some(Self::Silver),
            5 => Some(Self::Gold),
            6 => Some(Self::Bishop),
            7 => Some(Self::Rook),
            8 => Some(Self::ProBishop),
            9 => Some(Self::ProRook),
            10 => Some(Self::NonSlider),
            _ => None,
        }
    }
}

/// 王手になる候補の駒の位置を示すBitboard
/// [玉の位置][PieceTypeCheck][攻撃側の色]
pub static CHECK_CAND_BB: LazyLock<[[[Bitboard; 2]; PieceTypeCheck::NUM]; 81]> =
    LazyLock::new(init_check_cand_bb);

/// 玉周辺の利きを求めるときに使う、玉周辺に利きをつける候補の駒を表すBB
/// [玉の位置][駒の種類(PAWN-KING)][攻撃側の色]
pub static CHECK_AROUND_BB: LazyLock<[[[Bitboard; 2]; PieceType::NUM + 1]; 81]> =
    LazyLock::new(init_check_around_bb);

/// sq1に対してsq2の延長上にある次の升
/// [sq1][sq2] -> 次の升(盤外ならNone)
pub static NEXT_SQUARE: LazyLock<[[Option<Square>; 81]; 81]> = LazyLock::new(init_next_square);

/// テーブルのラッパー(Color/enum指定で取りやすくする)
#[inline]
pub fn check_cand_bb(us: Color, pc: PieceTypeCheck, sq_king: Square) -> Bitboard {
    CHECK_CAND_BB[sq_king.index()][pc as usize][us.index()]
}

#[inline]
pub fn check_around_bb(us: Color, pt: PieceType, sq_king: Square) -> Bitboard {
    CHECK_AROUND_BB[sq_king.index()][pt.index()][us.index()]
}

/// CHECK_CAND_BBの初期化
fn init_check_cand_bb() -> [[[Bitboard; 2]; PieceTypeCheck::NUM]; 81] {
    let mut table = [[[Bitboard::EMPTY; 2]; PieceTypeCheck::NUM]; 81];

    for sq_king in Square::all() {
        for &us in &[Color::Black, Color::White] {
            let idx = sq_king.index();
            let c = us.index();

            // 歩(不成): 敵歩利き先が王手となる升。ただし敵陣は除外。
            let enemy_field = crate::mate::enemy_field(us);
            let mut bb_no_pro = Bitboard::EMPTY;
            let mut step = pawn_effect(!us, sq_king) & !enemy_field;
            if step.is_not_empty() {
                // stepは最大1升なのでpopで十分
                let to = step.pop();
                bb_no_pro = pawn_effect(!us, to);
            }
            table[idx][PieceTypeCheck::PawnWithNoPro as usize][c] = bb_no_pro;

            // 歩(成): 金利きで敵陣のみ
            let mut bb_pro = Bitboard::EMPTY;
            let mut promo_targets = gold_effect(!us, sq_king) & enemy_field;
            while promo_targets.is_not_empty() {
                let to = promo_targets.pop();
                bb_pro |= pawn_effect(!us, to);
            }
            table[idx][PieceTypeCheck::PawnWithPro as usize][c] = bb_pro;

            // 香: 遮蔽物なしの直進利き + 敵陣での成り香(金相当)で王手できる隣接筋も候補に含める
            let mut lance_bb = lance_effect(!us, sq_king, Bitboard::EMPTY);
            if enemy_field.contains(sq_king) {
                if let Some(s) = sq_king.offset(Square::DELTA_R) {
                    lance_bb |= lance_effect(!us, s, Bitboard::EMPTY);
                }
                if let Some(s) = sq_king.offset(Square::DELTA_L) {
                    lance_bb |= lance_effect(!us, s, Bitboard::EMPTY);
                }
            }
            table[idx][PieceTypeCheck::Lance as usize][c] = lance_bb;

            // 桂: 桂利き + 成りで金になるケース(敵陣のみ金利きから逆算)
            let mut knight_bb = Bitboard::EMPTY;
            let mut tmp = knight_effect(!us, sq_king);
            while tmp.is_not_empty() {
                let to = tmp.pop();
                knight_bb |= knight_effect(!us, to);
            }
            // 成って王手になる移動元(金利きから逆算、敵陣のみ)
            let mut promo = gold_effect(!us, sq_king) & enemy_field;
            while promo.is_not_empty() {
                let to = promo.pop();
                knight_bb |= knight_effect(!us, to);
            }
            table[idx][PieceTypeCheck::Knight as usize][c] = knight_bb;

            // 銀: 銀利き + 成り金パターン、特殊例(4段目→3段目成り/5段目からの桂バック等)も含む
            let mut silver_bb = Bitboard::EMPTY;
            let mut tmp = silver_effect(!us, sq_king);
            while tmp.is_not_empty() {
                let to = tmp.pop();
                silver_bb |= silver_effect(!us, to);
            }
            let mut promo_gold = gold_effect(!us, sq_king) & enemy_field;
            while promo_gold.is_not_empty() {
                let to = promo_gold.pop();
                silver_bb |= silver_effect(!us, to);
            }
            // 4段目(先手) / 6段目(後手) での3段目成り金
            let special_rank = if us == Color::Black {
                Rank::Rank4
            } else {
                Rank::Rank6
            };
            if sq_king.rank() == special_rank {
                let r3 = if us == Color::Black {
                    Rank::Rank3
                } else {
                    Rank::Rank7
                };
                let base = Square::new(sq_king.file(), r3);
                silver_bb |= Bitboard::from_square(base);
                silver_bb |= cross45_step_effect(base);
                // 2升隣
                let file_idx = base.file().index() as i16;
                if let Some(file_r) = File::from_u8((file_idx + 2) as u8) {
                    silver_bb |= Bitboard::from_square(Square::new(file_r, r3));
                }
                if file_idx >= 2
                    && let Some(file_l) = File::from_u8((file_idx - 2) as u8)
                {
                    silver_bb |= Bitboard::from_square(Square::new(file_l, r3));
                }
            }
            // 5段目のバックアタック桂
            if sq_king.rank() == Rank::Rank5 {
                silver_bb |= knight_effect(us, sq_king);
            }
            table[idx][PieceTypeCheck::Silver as usize][c] = silver_bb;

            // 金: 敵玉から金の金にある駒 (2-hop)
            let mut gold_bb = Bitboard::EMPTY;
            let mut gold_targets = gold_effect(!us, sq_king);
            while gold_targets.is_not_empty() {
                let to = gold_targets.pop();
                gold_bb |= gold_effect(!us, to);
            }
            gold_bb &= !Bitboard::from_square(sq_king);
            table[idx][PieceTypeCheck::Gold as usize][c] = gold_bb;

            // 角・飛は盤上無視の利き
            table[idx][PieceTypeCheck::Bishop as usize][c] =
                bishop_effect(sq_king, Bitboard::EMPTY);
            table[idx][PieceTypeCheck::Rook as usize][c] = rook_effect(sq_king, Bitboard::EMPTY);
            table[idx][PieceTypeCheck::ProBishop as usize][c] =
                bishop_effect(sq_king, Bitboard::EMPTY) | king_effect(sq_king);
            table[idx][PieceTypeCheck::ProRook as usize][c] =
                rook_effect(sq_king, Bitboard::EMPTY) | king_effect(sq_king);

            let mut non_slider = Bitboard::EMPTY;
            non_slider |= table[idx][PieceTypeCheck::PawnWithNoPro as usize][c];
            non_slider |= table[idx][PieceTypeCheck::PawnWithPro as usize][c];
            non_slider |= table[idx][PieceTypeCheck::Knight as usize][c];
            non_slider |= table[idx][PieceTypeCheck::Silver as usize][c];
            non_slider |= table[idx][PieceTypeCheck::Gold as usize][c];
            table[idx][PieceTypeCheck::NonSlider as usize][c] = non_slider;
        }
    }

    table
}

/// CHECK_AROUND_BBの初期化
fn init_check_around_bb() -> [[[Bitboard; 2]; PieceType::NUM + 1]; 81] {
    let mut table = [[[Bitboard::EMPTY; 2]; PieceType::NUM + 1]; 81];

    for sq_king in Square::all() {
        let around8 = king_effect(sq_king);
        for &us in &[Color::Black, Color::White] {
            let c = us.index();
            for pt_idx in 1..=PieceType::NUM {
                let pt = PieceType::from_u8(pt_idx as u8).unwrap();
                let mut bb = Bitboard::EMPTY;

                // 玉周辺の8近傍から逆算
                for near in around8.iter() {
                    let cand = match pt {
                        PieceType::Pawn => pawn_effect(!us, near),
                        PieceType::Lance => lance_effect(!us, near, Bitboard::EMPTY),
                        PieceType::Knight => knight_effect(!us, near),
                        PieceType::Silver => silver_effect(!us, near),
                        PieceType::Bishop => bishop_effect(near, Bitboard::EMPTY),
                        PieceType::Rook => rook_effect(near, Bitboard::EMPTY),
                        PieceType::ProPawn
                        | PieceType::ProLance
                        | PieceType::ProKnight
                        | PieceType::ProSilver
                        | PieceType::Gold => gold_effect(!us, near),
                        PieceType::Horse => {
                            bishop_effect(near, Bitboard::EMPTY) | king_effect(near)
                        }
                        PieceType::Dragon => rook_effect(near, Bitboard::EMPTY) | king_effect(near),
                        PieceType::King => king_effect(near),
                    };
                    bb |= cand;
                }

                // 玉自身の升のみ除外
                bb &= !Bitboard::from_square(sq_king);
                table[sq_king.index()][pt.index()][c] = bb;
            }
        }
    }

    table
}

/// NEXT_SQUAREの初期化
fn init_next_square() -> [[Option<Square>; 81]; 81] {
    let mut table = [[None; 81]; 81];

    for s1 in Square::all() {
        for s2 in Square::all() {
            let f1 = s1.file().index() as i32;
            let r1 = s1.rank().index() as i32;
            let f2 = s2.file().index() as i32;
            let r2 = s2.rank().index() as i32;

            let df = (f2 - f1).signum();
            let dr = (r2 - r1).signum();

            // 同一マスや非直線の場合はNone
            if (df == 0 && dr == 0) || !(df == 0 || dr == 0 || df.abs() == dr.abs()) {
                table[s1.index()][s2.index()] = None;
                continue;
            }

            let nf = f2 + df;
            let nr = r2 + dr;
            if (0..=8).contains(&nf)
                && (0..=8).contains(&nr)
                && let (Some(file), Some(rank)) =
                    (crate::types::File::from_u8(nf as u8), crate::types::Rank::from_u8(nr as u8))
            {
                table[s1.index()][s2.index()] = Some(Square::new(file, rank));
            }
        }
    }

    table
}

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

    #[test]
    fn test_piece_type_check_enum() {
        assert_eq!(PieceTypeCheck::NUM, 11);
        assert_eq!(PieceTypeCheck::from_u8(0), Some(PieceTypeCheck::PawnWithNoPro));
        assert_eq!(PieceTypeCheck::from_u8(10), Some(PieceTypeCheck::NonSlider));
        assert_eq!(PieceTypeCheck::from_u8(11), None);
    }

    #[test]
    fn test_check_cand_special_cases() {
        // 歩不成は敵陣外のみ
        let black_pawn = CHECK_CAND_BB[Square::SQ_55.index()]
            [PieceTypeCheck::PawnWithNoPro as usize][Color::Black.index()];
        assert!(black_pawn.is_not_empty());
        let bottom = Square::new(Square::SQ_55.file(), Rank::Rank1);
        assert!(!black_pawn.contains(bottom));

        // 香は敵陣なら左右成り香候補を含む(1一に対して先手香なら1二と2二が入る)
        let lance = CHECK_CAND_BB[Square::SQ_11.index()][PieceTypeCheck::Lance as usize]
            [Color::Black.index()];
        let sq_12 = Square::new(File::File1, Rank::Rank2);
        assert!(lance.contains(sq_12));
    }

    #[test]
    fn test_check_cand_silver_includes_two_files_away_on_special_rank() {
        // 4段目(先手)/6段目(後手)の特殊ケースで「2升隣」が含まれる。
        // 先手視点: 玉5四 → 基準升5三の2升隣(7三,3三)が候補に入る。
        let black_king = Square::new(File::File5, Rank::Rank4);
        let black_silver = CHECK_CAND_BB[black_king.index()][PieceTypeCheck::Silver as usize]
            [Color::Black.index()];
        assert!(black_silver.contains(Square::new(File::File7, Rank::Rank3)));
        assert!(black_silver.contains(Square::new(File::File3, Rank::Rank3)));

        // 後手視点: 玉5六 → 基準升5七の2升隣(7七,3七)が候補に入る。
        let white_king = Square::new(File::File5, Rank::Rank6);
        let white_silver = CHECK_CAND_BB[white_king.index()][PieceTypeCheck::Silver as usize]
            [Color::White.index()];
        assert!(white_silver.contains(Square::new(File::File7, Rank::Rank7)));
        assert!(white_silver.contains(Square::new(File::File3, Rank::Rank7)));
    }

    #[test]
    fn test_tables_initialization() {
        // テーブルが初期化されることを確認
        let _ = &*CHECK_CAND_BB;
        let _ = &*CHECK_AROUND_BB;
        let _ = &*NEXT_SQUARE;
    }

    #[test]
    fn test_check_around_bb_matches_yaneuraou_formula() {
        for sq_king in Square::all() {
            let around8 = king_effect(sq_king);
            for &us in &[Color::Black, Color::White] {
                for pt_idx in 1..=PieceType::NUM {
                    let pt = PieceType::from_u8(pt_idx as u8).unwrap();
                    let mut expected = Bitboard::EMPTY;

                    for near in around8.iter() {
                        let cand = match pt {
                            PieceType::Pawn => pawn_effect(!us, near),
                            PieceType::Lance => lance_effect(!us, near, Bitboard::EMPTY),
                            PieceType::Knight => knight_effect(!us, near),
                            PieceType::Silver => silver_effect(!us, near),
                            PieceType::Bishop => bishop_effect(near, Bitboard::EMPTY),
                            PieceType::Rook => rook_effect(near, Bitboard::EMPTY),
                            PieceType::ProPawn
                            | PieceType::ProLance
                            | PieceType::ProKnight
                            | PieceType::ProSilver
                            | PieceType::Gold => gold_effect(!us, near),
                            PieceType::Horse => {
                                bishop_effect(near, Bitboard::EMPTY) | king_effect(near)
                            }
                            PieceType::Dragon => {
                                rook_effect(near, Bitboard::EMPTY) | king_effect(near)
                            }
                            PieceType::King => king_effect(near),
                        };
                        expected |= cand;
                    }

                    expected &= !Bitboard::from_square(sq_king);

                    let actual = CHECK_AROUND_BB[sq_king.index()][pt.index()][us.index()];
                    assert_eq!(
                        actual,
                        expected,
                        "CHECK_AROUND_BB mismatch: sq_king={} us={:?} pt={:?}",
                        sq_king.to_usi(),
                        us,
                        pt
                    );
                }
            }
        }
    }
}