use std::sync::OnceLock;
use crate::types::{Color, PieceType, Square};
use super::sliders::{bishop_effect, horse_effect, lance_step_effect};
use super::{
Bitboard, GOLD_EFFECT, KING_EFFECT, KNIGHT_EFFECT, PAWN_EFFECT, RANK_BB, SILVER_EFFECT,
};
const CHECK_CANDIDATE_NUM: usize = 7;
static CHECK_CANDIDATE_TABLE: OnceLock<
[[[Bitboard; Square::NUM]; CHECK_CANDIDATE_NUM]; Color::NUM],
> = OnceLock::new();
fn check_candidate_table() -> &'static [[[Bitboard; Square::NUM]; CHECK_CANDIDATE_NUM]; Color::NUM]
{
CHECK_CANDIDATE_TABLE.get_or_init(init_check_candidate)
}
#[inline]
fn pt_to_index(pt: PieceType) -> usize {
match pt {
PieceType::Pawn => 0,
PieceType::Lance => 1,
PieceType::Knight => 2,
PieceType::Silver => 3,
PieceType::Bishop => 4,
PieceType::Horse => 5,
PieceType::Gold => 6,
_ => unreachable!("check_candidate_bb: unsupported piece type"),
}
}
#[inline]
pub fn check_candidate_bb(us: Color, pt: PieceType, ksq: Square) -> Bitboard {
check_candidate_table()[us.index()][pt_to_index(pt)][ksq.index()]
}
fn enemy_field(us: Color) -> Bitboard {
match us {
Color::Black => RANK_BB[0] | RANK_BB[1] | RANK_BB[2],
Color::White => RANK_BB[6] | RANK_BB[7] | RANK_BB[8],
}
}
#[inline]
fn pawn_eff(c: Color, sq: Square) -> Bitboard {
PAWN_EFFECT[c.index()][sq.index()]
}
#[inline]
fn knight_eff(c: Color, sq: Square) -> Bitboard {
KNIGHT_EFFECT[c.index()][sq.index()]
}
#[inline]
fn silver_eff(c: Color, sq: Square) -> Bitboard {
SILVER_EFFECT[c.index()][sq.index()]
}
#[inline]
fn gold_eff(c: Color, sq: Square) -> Bitboard {
GOLD_EFFECT[c.index()][sq.index()]
}
#[inline]
fn king_eff(sq: Square) -> Bitboard {
KING_EFFECT[sq.index()]
}
#[inline]
unsafe fn make_square(file: i32, rank: i32) -> Square {
debug_assert!((0..9).contains(&file) && (0..9).contains(&rank));
unsafe { Square::from_u8_unchecked((file * 9 + rank) as u8) }
}
fn init_check_candidate() -> [[[Bitboard; Square::NUM]; CHECK_CANDIDATE_NUM]; Color::NUM] {
let mut result = [[[Bitboard::EMPTY; Square::NUM]; CHECK_CANDIDATE_NUM]; Color::NUM];
for us in [Color::Black, Color::White] {
let them = !us;
let ef = enemy_field(us);
for ksq in Square::all() {
let ksq_bb = Bitboard::from_square(ksq);
let enemy_gold = gold_eff(them, ksq) & ef;
{
let mut target = Bitboard::EMPTY;
for sq in pawn_eff(them, ksq).iter() {
target |= pawn_eff(them, sq);
}
for sq in enemy_gold.iter() {
target |= pawn_eff(them, sq);
}
result[us.index()][0][ksq.index()] = target & !ksq_bb;
}
{
let mut target = lance_step_effect(them, ksq);
if ef.contains(ksq) {
let file = ksq.file().index() as i32;
let rank = ksq.rank().index() as i32;
if file > 0 {
let adj = unsafe { make_square(file - 1, rank) };
target |= lance_step_effect(them, adj);
}
if file < 8 {
let adj = unsafe { make_square(file + 1, rank) };
target |= lance_step_effect(them, adj);
}
}
result[us.index()][1][ksq.index()] = target;
}
{
let mut target = Bitboard::EMPTY;
let combined = knight_eff(them, ksq) | enemy_gold;
for sq in combined.iter() {
target |= knight_eff(them, sq);
}
result[us.index()][2][ksq.index()] = target & !ksq_bb;
}
{
let mut target = Bitboard::EMPTY;
for sq in silver_eff(them, ksq).iter() {
target |= silver_eff(them, sq);
}
for sq in enemy_gold.iter() {
target |= silver_eff(them, sq);
}
for sq in gold_eff(them, ksq).iter() {
target |= ef & silver_eff(them, sq);
}
result[us.index()][3][ksq.index()] = target & !ksq_bb;
}
{
let mut target = Bitboard::EMPTY;
let bishop_step = bishop_effect(ksq, Bitboard::EMPTY);
for sq in bishop_step.iter() {
target |= bishop_effect(sq, Bitboard::EMPTY);
}
for sq in (king_eff(ksq) & ef).iter() {
target |= bishop_effect(sq, Bitboard::EMPTY);
}
for sq in king_eff(ksq).iter() {
target |= ef & bishop_effect(sq, Bitboard::EMPTY);
}
result[us.index()][4][ksq.index()] = target & !ksq_bb;
}
{
let mut target = Bitboard::EMPTY;
let horse_step = horse_effect(ksq, Bitboard::EMPTY);
for sq in horse_step.iter() {
target |= horse_effect(sq, Bitboard::EMPTY);
}
result[us.index()][5][ksq.index()] = target & !ksq_bb;
}
{
let mut target = Bitboard::EMPTY;
for sq in gold_eff(them, ksq).iter() {
target |= gold_eff(them, sq);
}
result[us.index()][6][ksq.index()] = target & !ksq_bb;
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{File, Rank};
#[test]
fn test_check_candidate_pawn_basic() {
let ksq = Square::new(File::File5, Rank::Rank5);
let bb = check_candidate_bb(Color::Black, PieceType::Pawn, ksq);
let sq_5g = Square::new(File::File5, Rank::Rank7);
assert!(bb.contains(sq_5g), "5七に歩の王手候補がない");
let sq_5f = Square::new(File::File5, Rank::Rank6);
assert!(!bb.contains(sq_5f), "5六が候補に含まれている(不正)");
}
#[test]
fn test_check_candidate_gold_basic() {
let ksq = Square::new(File::File5, Rank::Rank5);
let bb = check_candidate_bb(Color::Black, PieceType::Gold, ksq);
assert!(!bb.is_empty(), "金の王手候補が空");
assert!(!bb.contains(ksq), "敵玉位置が候補に含まれている");
}
#[test]
fn test_check_candidate_rook_not_in_table() {
let result = std::panic::catch_unwind(|| {
check_candidate_bb(
Color::Black,
PieceType::Rook,
Square::new(File::File5, Rank::Rank5),
);
});
assert!(result.is_err(), "ROOK で check_candidate_bb が呼べてしまう");
}
#[test]
fn test_check_candidate_lance_promotion() {
let ksq = Square::new(File::File5, Rank::Rank1);
let bb = check_candidate_bb(Color::Black, PieceType::Lance, ksq);
let sq_5b = Square::new(File::File5, Rank::Rank2);
assert!(bb.contains(sq_5b), "同筋が候補にない");
let sq_4b = Square::new(File::File4, Rank::Rank2);
assert!(bb.contains(sq_4b), "隣接筋が候補にない");
}
#[test]
fn test_check_candidate_bishop_basic() {
let ksq = Square::new(File::File5, Rank::Rank5);
let bb = check_candidate_bb(Color::Black, PieceType::Bishop, ksq);
assert!(!bb.is_empty(), "角の王手候補が空");
assert!(!bb.contains(ksq), "敵玉位置が候補に含まれている");
}
}