use super::*;
use crate::types::{Bitboard, Color, File, PieceType, Rank, SQ_11, SQ_22, SQ_55, SQ_99, Square};
fn enemy_field_mask(us: Color) -> Bitboard {
match us {
Color::BLACK => {
Bitboard::rank_mask(Rank::RANK_1)
| Bitboard::rank_mask(Rank::RANK_2)
| Bitboard::rank_mask(Rank::RANK_3)
}
Color::WHITE => {
Bitboard::rank_mask(Rank::RANK_7)
| Bitboard::rank_mask(Rank::RANK_8)
| Bitboard::rank_mask(Rank::RANK_9)
}
}
}
fn pawn_attacks(color: Color, sq: Square) -> Bitboard {
PAWN_ATTACKS[sq][color.to_index()]
}
fn knight_attacks(color: Color, sq: Square) -> Bitboard {
KNIGHT_ATTACKS[sq][color.to_index()]
}
fn silver_attacks(color: Color, sq: Square) -> Bitboard {
SILVER_ATTACKS[sq][color.to_index()]
}
fn gold_attacks(color: Color, sq: Square) -> Bitboard {
GOLD_ATTACKS[sq][color.to_index()]
}
fn king_attacks(sq: Square) -> Bitboard {
KING_ATTACKS[sq]
}
fn lance_step_attacks(color: Color, sq: Square) -> Bitboard {
let beams = LANCE_BEAMS[sq.to_index()];
match color {
Color::BLACK => beams.black,
Color::WHITE => beams.white,
}
}
fn bishop_step_attacks(sq: Square) -> Bitboard {
bishop_attacks(sq, Bitboard::EMPTY)
}
fn horse_attacks(sq: Square) -> Bitboard {
bishop_step_attacks(sq) | king_attacks(sq)
}
fn attacks_from(piece_type: PieceType, color: Color, sq: Square, occupied: Bitboard) -> Bitboard {
match piece_type {
PieceType::PAWN => pawn_attacks(color, sq),
PieceType::LANCE => lance_attacks(sq, occupied, color),
PieceType::KNIGHT => knight_attacks(color, sq),
PieceType::SILVER => silver_attacks(color, sq),
PieceType::BISHOP => bishop_attacks(sq, occupied),
PieceType::ROOK => rook_attacks(sq, occupied),
PieceType::GOLD
| PieceType::PRO_PAWN
| PieceType::PRO_LANCE
| PieceType::PRO_KNIGHT
| PieceType::PRO_SILVER => gold_attacks(color, sq),
PieceType::HORSE => bishop_attacks(sq, occupied) | king_attacks(sq),
PieceType::DRAGON => rook_attacks(sq, occupied) | king_attacks(sq),
PieceType::KING => king_attacks(sq),
_ => Bitboard::EMPTY,
}
}
fn check_candidate_expected(ksq: Square, pt_idx: usize, us: Color) -> Bitboard {
let them = us.flip();
let enemy_field = enemy_field_mask(us);
let enemy_gold = gold_attacks(them, ksq) & enemy_field;
let mut target = Bitboard::EMPTY;
match pt_idx {
0 => {
let mut bb = pawn_attacks(them, ksq);
while let Some(sq) = bb.pop_lsb() {
target |= pawn_attacks(them, sq);
}
let mut bb = enemy_gold;
while let Some(sq) = bb.pop_lsb() {
target |= pawn_attacks(them, sq);
}
target = target.and_not(Bitboard::from_square(ksq));
}
1 => {
target = lance_step_attacks(them, ksq);
if enemy_field.test(ksq) {
let file = ksq.raw() / 9;
let rank = ksq.raw() % 9;
if file != 0 {
let sq = Square::new((file - 1) * 9 + rank);
target |= lance_step_attacks(them, sq);
}
if file != 8 {
let sq = Square::new((file + 1) * 9 + rank);
target |= lance_step_attacks(them, sq);
}
}
}
2 => {
let mut bb = knight_attacks(them, ksq) | enemy_gold;
while let Some(sq) = bb.pop_lsb() {
target |= knight_attacks(them, sq);
}
target = target.and_not(Bitboard::from_square(ksq));
}
3 => {
let mut bb = silver_attacks(them, ksq);
while let Some(sq) = bb.pop_lsb() {
target |= silver_attacks(them, sq);
}
let mut bb = enemy_gold;
while let Some(sq) = bb.pop_lsb() {
target |= silver_attacks(them, sq);
}
let mut bb = gold_attacks(them, ksq);
while let Some(sq) = bb.pop_lsb() {
target |= silver_attacks(them, sq) & enemy_field;
}
target = target.and_not(Bitboard::from_square(ksq));
}
4 => {
let mut bb = gold_attacks(them, ksq);
while let Some(sq) = bb.pop_lsb() {
target |= gold_attacks(them, sq);
}
target = target.and_not(Bitboard::from_square(ksq));
}
5 => {
let mut bb = bishop_step_attacks(ksq);
while let Some(sq) = bb.pop_lsb() {
target |= bishop_step_attacks(sq);
}
let mut bb = king_attacks(ksq) & enemy_field;
while let Some(sq) = bb.pop_lsb() {
target |= bishop_step_attacks(sq);
}
let mut bb = king_attacks(ksq);
while let Some(sq) = bb.pop_lsb() {
target |= bishop_step_attacks(sq) & enemy_field;
}
target = target.and_not(Bitboard::from_square(ksq));
}
6 => {
let mut bb = horse_attacks(ksq);
while let Some(sq) = bb.pop_lsb() {
target |= horse_attacks(sq);
}
target = target.and_not(Bitboard::from_square(ksq));
}
_ => {}
}
target
}
#[test]
fn test_pawn_attacks_black() {
let sq = SQ_55; let attacks = PAWN_ATTACKS[sq][Color::BLACK.to_index()];
let expected_sq = Square::new(sq.raw() - 1); assert!(attacks.test(expected_sq), "SQ_55の先手歩の利きにSQ_54が含まれていない");
assert_eq!(attacks.count(), 1, "歩の利きは1マスのみ");
}
#[test]
fn test_pawn_attacks_white() {
let sq = SQ_55;
let attacks = PAWN_ATTACKS[sq][Color::WHITE.to_index()];
let expected_sq = Square::new(sq.raw() + 1); assert!(attacks.test(expected_sq), "SQ_55の後手歩の利きにSQ_56が含まれていない");
assert_eq!(attacks.count(), 1, "歩の利きは1マスのみ");
}
#[test]
fn test_pawn_attacks_edge() {
let sq = SQ_11; let attacks_black = PAWN_ATTACKS[sq][Color::BLACK.to_index()];
assert!(attacks_black.is_empty(), "SQ_11の先手歩の利きは盤外なので空");
let sq = SQ_99; let attacks_white = PAWN_ATTACKS[sq][Color::WHITE.to_index()];
assert!(attacks_white.is_empty(), "SQ_99の後手歩の利きは盤外なので空");
}
#[test]
fn test_knight_attacks_black() {
let sq = SQ_55; let attacks = KNIGHT_ATTACKS[sq][Color::BLACK.to_index()];
let sq_west = Square::new(47);
let sq_east = Square::new(29);
assert!(attacks.test(sq_west), "SQ_55の先手桂馬の利きにW方向の駒取り位置が含まれていない");
assert!(attacks.test(sq_east), "SQ_55の先手桂馬の利きにE方向の駒取り位置が含まれていない");
assert_eq!(attacks.count(), 2, "桂馬の利きは2マス");
}
#[test]
fn test_knight_attacks_edge() {
let sq = SQ_11;
let attacks_black = KNIGHT_ATTACKS[sq][Color::BLACK.to_index()];
assert!(attacks_black.is_empty(), "SQ_11の先手桂馬の利きは盤外");
let sq = SQ_22; let attacks_black = KNIGHT_ATTACKS[sq][Color::BLACK.to_index()];
assert!(attacks_black.is_empty(), "SQ_22の先手桂馬の利きは盤外");
}
#[test]
fn test_silver_attacks_black() {
let sq = SQ_55; let attacks = SILVER_ATTACKS[sq][Color::BLACK.to_index()];
assert_eq!(attacks.count(), 5, "銀の利きは5マス");
}
#[test]
fn test_gold_attacks_black() {
let sq = SQ_55; let attacks = GOLD_ATTACKS[sq][Color::BLACK.to_index()];
assert_eq!(attacks.count(), 6, "金の利きは6マス");
}
#[test]
fn test_attack_tables_accessible() {
assert_eq!(PAWN_ATTACKS.len(), 81);
assert_eq!(KNIGHT_ATTACKS.len(), 81);
assert_eq!(SILVER_ATTACKS.len(), 81);
assert_eq!(GOLD_ATTACKS.len(), 81);
assert_eq!(KING_ATTACKS.len(), 81);
assert_eq!(LANCE_BEAMS.len(), 81);
assert_eq!(BISHOP_BEAMS.len(), 81);
assert_eq!(ROOK_BEAMS.len(), 81);
assert_eq!(QUGIY_STEP_ATTACKS.len(), 6);
}
#[test]
fn test_gold_attacks_white_corner() {
use std::str::FromStr;
let sq = Square::from_str("9a").expect("valid square");
let attacks = GOLD_ATTACKS[sq][Color::WHITE.to_index()];
let diag_target = Square::from_str("8b").expect("valid square");
assert!(attacks.test(diag_target), "後手の金の斜め前利きが欠落している");
}
#[test]
fn test_all_squares_covered() {
for sq in 0..81 {
assert!(PAWN_ATTACKS[sq][Color::BLACK.to_index()].count() <= 1);
assert!(PAWN_ATTACKS[sq][Color::WHITE.to_index()].count() <= 1);
assert!(KNIGHT_ATTACKS[sq][Color::BLACK.to_index()].count() <= 2);
assert!(KNIGHT_ATTACKS[sq][Color::WHITE.to_index()].count() <= 2);
assert!(SILVER_ATTACKS[sq][Color::BLACK.to_index()].count() <= 5);
assert!(SILVER_ATTACKS[sq][Color::WHITE.to_index()].count() <= 5);
assert!(GOLD_ATTACKS[sq][Color::BLACK.to_index()].count() <= 6);
assert!(GOLD_ATTACKS[sq][Color::WHITE.to_index()].count() <= 6);
assert!(KING_ATTACKS[sq].count() <= 8);
assert!(LANCE_BEAMS[sq].black.count() <= 8);
assert!(LANCE_BEAMS[sq].white.count() <= 8);
assert!(BISHOP_BEAMS[sq].ne.count() <= 8);
assert!(ROOK_BEAMS[sq].n.count() <= 8);
}
}
#[test]
fn test_king_attacks() {
let sq = SQ_55; let attacks = KING_ATTACKS[sq];
assert_eq!(attacks.count(), 8, "中央の玉の利きは8マス");
}
#[test]
fn test_king_attacks_corner() {
let sq = SQ_11; let attacks = KING_ATTACKS[sq];
assert_eq!(attacks.count(), 3, "隅の玉の利きは3マス");
}
#[test]
fn test_king_attacks_edge() {
let sq = SQ_55; let attacks = KING_ATTACKS[sq];
assert!(attacks.count() > 0, "玉の利きは空ではない");
}
#[test]
fn test_check_candidate_bb_matches_builder() {
for ksq_idx in 0..Square::COUNT {
let ksq = Square::from_index(ksq_idx);
for (pt_idx, pt_row) in CHECK_CANDIDATE_BB[ksq.to_index()].iter().enumerate() {
for us in [Color::BLACK, Color::WHITE] {
let expected = check_candidate_expected(ksq, pt_idx, us);
let actual = pt_row[us.to_index()];
assert_eq!(
expected, actual,
"check candidate mismatch: ksq={ksq:?} pt_idx={pt_idx} us={us}"
);
}
}
}
}
#[test]
fn test_check_candidate_bb_api() {
let piece_types = [
(PieceType::PAWN, 0usize),
(PieceType::LANCE, 1usize),
(PieceType::KNIGHT, 2usize),
(PieceType::SILVER, 3usize),
(PieceType::GOLD, 4usize),
(PieceType::BISHOP, 5usize),
(PieceType::ROOK, 6usize),
];
for ksq_idx in 0..Square::COUNT {
let ksq = Square::from_index(ksq_idx);
for (pt, idx) in piece_types {
for us in [Color::BLACK, Color::WHITE] {
let expected = CHECK_CANDIDATE_BB[ksq.to_index()][idx][us.to_index()];
let actual = check_candidate_bb(us, pt, ksq);
assert_eq!(
expected, actual,
"check_candidate_bb mismatch: ksq={ksq:?} pt={pt:?} us={us}"
);
}
}
}
assert_eq!(check_candidate_bb(Color::BLACK, PieceType::KING, SQ_55), Bitboard::EMPTY);
assert_eq!(check_candidate_bb(Color::WHITE, PieceType::HORSE, SQ_55), Bitboard::EMPTY);
}
#[test]
fn test_representative_attacks() {
let sq_55 = SQ_55;
assert_eq!(PAWN_ATTACKS[sq_55][Color::BLACK.to_index()].count(), 1);
assert_eq!(PAWN_ATTACKS[sq_55][Color::WHITE.to_index()].count(), 1);
assert_eq!(KNIGHT_ATTACKS[sq_55][Color::BLACK.to_index()].count(), 2);
assert_eq!(KNIGHT_ATTACKS[sq_55][Color::WHITE.to_index()].count(), 2);
assert_eq!(SILVER_ATTACKS[sq_55][Color::BLACK.to_index()].count(), 5);
assert_eq!(SILVER_ATTACKS[sq_55][Color::WHITE.to_index()].count(), 5);
assert_eq!(GOLD_ATTACKS[sq_55][Color::BLACK.to_index()].count(), 6);
assert_eq!(GOLD_ATTACKS[sq_55][Color::WHITE.to_index()].count(), 6);
assert_eq!(KING_ATTACKS[sq_55].count(), 8);
}
#[test]
fn test_lance_beams_properties() {
let sq_55 = SQ_55;
let beams = LANCE_BEAMS[sq_55.to_index()];
assert_eq!(beams.black.count(), 4);
assert_eq!(beams.white.count(), 4);
assert!(LANCE_BEAMS[SQ_11.to_index()].black.is_empty());
assert!(LANCE_BEAMS[SQ_99.to_index()].white.is_empty());
}
#[test]
fn test_bishop_beams_properties() {
let sq_55 = SQ_55;
let beams = BISHOP_BEAMS[sq_55.to_index()];
assert_eq!(beams.ne.count(), 4);
assert_eq!(beams.se.count(), 4);
assert_eq!(beams.sw.count(), 4);
assert_eq!(beams.nw.count(), 4);
let corner_beams = BISHOP_BEAMS[SQ_11.to_index()];
assert!(corner_beams.ne.is_empty());
assert!(corner_beams.nw.is_empty());
assert!(corner_beams.sw.is_empty());
assert_eq!(corner_beams.se.count(), 8);
}
#[test]
fn test_rook_beams_properties() {
let sq_55 = SQ_55;
let beams = ROOK_BEAMS[sq_55.to_index()];
assert_eq!(beams.n.count(), 4);
assert_eq!(beams.e.count(), 4);
assert_eq!(beams.s.count(), 4);
assert_eq!(beams.w.count(), 4);
let corner_beams = ROOK_BEAMS[SQ_11.to_index()];
assert!(corner_beams.n.is_empty());
assert!(corner_beams.w.is_empty());
assert_eq!(corner_beams.e.count(), 8);
assert_eq!(corner_beams.s.count(), 8);
}
#[test]
fn test_beam_structs_equality() {
let sq_55 = SQ_55;
let lance = LANCE_BEAMS[sq_55.to_index()];
assert_eq!(lance, lance);
let bishop = BISHOP_BEAMS[sq_55.to_index()];
assert_eq!(bishop, bishop);
let rook = ROOK_BEAMS[sq_55.to_index()];
assert_eq!(rook, rook);
}
#[test]
fn test_yaneu_compatibility_pawn() {
let sq = SQ_55;
let attacks = PAWN_ATTACKS[sq][Color::BLACK.to_index()];
let expected_sq = Square::new(39);
assert!(attacks.test(expected_sq), "SQ_55の先手歩の利きにSQ_54が含まれる");
assert_eq!(attacks.count(), 1);
}
#[test]
fn test_yaneu_compatibility_knight() {
let sq = SQ_55;
let attacks = KNIGHT_ATTACKS[sq][Color::BLACK.to_index()];
let sq_east = Square::new(29);
let sq_west = Square::new(47);
assert!(attacks.test(sq_west), "桂馬のW側の利き");
assert!(attacks.test(sq_east), "桂馬のE側の利き");
assert_eq!(attacks.count(), 2);
}
#[test]
fn test_yaneu_compatibility_silver() {
let sq = SQ_55;
let attacks = SILVER_ATTACKS[sq][Color::BLACK.to_index()];
assert_eq!(attacks.count(), 5, "銀の利きは5マス");
}
#[test]
fn test_yaneu_compatibility_gold() {
let sq = SQ_55;
let attacks = GOLD_ATTACKS[sq][Color::BLACK.to_index()];
assert_eq!(attacks.count(), 6, "金の利きは6マス");
}
#[test]
fn test_yaneu_compatibility_king() {
let sq = SQ_55;
let attacks = KING_ATTACKS[sq];
assert_eq!(attacks.count(), 8, "中央の玉の利きは8マス");
let sq = SQ_11;
let attacks = KING_ATTACKS[sq];
assert_eq!(attacks.count(), 3, "隅の玉の利きは3マス");
}
#[test]
#[allow(clippy::uninlined_format_args)]
fn test_lance_beams_black() {
let sq = SQ_55;
let beams = LANCE_BEAMS[sq.to_index()];
let expected_squares = vec![
Square::new(36), Square::new(37), Square::new(38), Square::new(39), ];
for expected_sq in expected_squares {
assert!(
beams.black.test(expected_sq),
"SQ_55の先手香車ビームに{:?}が含まれていない",
expected_sq
);
}
assert!(!beams.black.test(sq), "香車ビームは元のマス自身を含まない");
let south_sq = Square::new(sq.raw() + 1); assert!(!beams.black.test(south_sq), "先手香車ビームにS方向のマスが含まれてはいけない");
}
#[test]
#[allow(clippy::uninlined_format_args)]
fn test_lance_beams_white() {
let sq = SQ_55;
let beams = LANCE_BEAMS[sq.to_index()];
let expected_squares = vec![
Square::new(41), Square::new(42), Square::new(43), Square::new(44), ];
for expected_sq in expected_squares {
assert!(
beams.white.test(expected_sq),
"SQ_55の後手香車ビームに{:?}が含まれていない",
expected_sq
);
}
assert!(!beams.white.test(sq), "香車ビームは元のマス自身を含まない");
}
#[test]
fn test_lance_beams_edge() {
let sq = SQ_11; let beams = LANCE_BEAMS[sq.to_index()];
assert!(beams.black.is_empty(), "1段目の先手香車のN方向ビームは空");
let sq = SQ_99; let beams = LANCE_BEAMS[sq.to_index()];
assert!(beams.white.is_empty(), "9段目の後手香車のS方向ビームは空");
}
#[test]
#[allow(
clippy::similar_names,
clippy::identity_op,
clippy::erasing_op,
clippy::uninlined_format_args
)]
fn test_bishop_beams_center() {
let sq = SQ_55;
let beams = BISHOP_BEAMS[sq.to_index()];
let nw = beams.ne;
let sw = beams.se;
let se = beams.sw;
let ne = beams.nw;
let ne_squares = vec![
Square::new(5 * 9 + 3), Square::new(6 * 9 + 2), Square::new(7 * 9 + 1), Square::new(8 * 9 + 0), ];
for sq in ne_squares {
assert!(nw.test(sq), "NWビームに{:?}が含まれていない", sq);
}
let se_squares = vec![
Square::new(5 * 9 + 5), Square::new(6 * 9 + 6), Square::new(7 * 9 + 7), Square::new(8 * 9 + 8), ];
for sq in se_squares {
assert!(sw.test(sq), "SWビームに{:?}が含まれていない", sq);
}
let sw_squares = vec![
Square::new(3 * 9 + 5), Square::new(2 * 9 + 6), Square::new(1 * 9 + 7), Square::new(0 * 9 + 8), ];
for sq in sw_squares {
assert!(se.test(sq), "SEビームに{:?}が含まれていない", sq);
}
let nw_squares = vec![
Square::new(3 * 9 + 3), Square::new(2 * 9 + 2), Square::new(1 * 9 + 1), Square::new(0 * 9 + 0), ];
for sq in nw_squares {
assert!(ne.test(sq), "NEビームに{:?}が含まれていない", sq);
}
}
#[test]
fn test_bishop_beams_corner() {
let sq = SQ_11; let beams = BISHOP_BEAMS[sq.to_index()];
let nw = beams.ne;
let sw = beams.se;
let se = beams.sw;
let ne = beams.nw;
assert!(nw.is_empty(), "隅からのNWビームは盤外");
assert!(ne.is_empty(), "隅からのNEビームは盤外");
assert!(se.is_empty(), "隅からのSEビームは盤外");
assert_eq!(sw.count(), 8, "隅からのSWビームは8マス");
}
#[test]
#[allow(clippy::identity_op, clippy::erasing_op, clippy::uninlined_format_args)]
fn test_rook_beams_center() {
let sq = SQ_55;
let beams = ROOK_BEAMS[sq.to_index()];
let w = beams.e;
let e = beams.w;
let n_squares = vec![
Square::new(4 * 9 + 3), Square::new(4 * 9 + 2), Square::new(4 * 9 + 1), Square::new(4 * 9 + 0), ];
for sq in n_squares {
assert!(beams.n.test(sq), "Nビームに{:?}が含まれていない", sq);
}
let e_squares = vec![
Square::new(5 * 9 + 4), Square::new(6 * 9 + 4), Square::new(7 * 9 + 4), Square::new(8 * 9 + 4), ];
for sq in e_squares {
assert!(w.test(sq), "Wビームに{:?}が含まれていない", sq);
}
let s_squares = vec![
Square::new(4 * 9 + 5), Square::new(4 * 9 + 6), Square::new(4 * 9 + 7), Square::new(4 * 9 + 8), ];
for sq in s_squares {
assert!(beams.s.test(sq), "Sビームに{:?}が含まれていない", sq);
}
let w_squares = vec![
Square::new(3 * 9 + 4), Square::new(2 * 9 + 4), Square::new(1 * 9 + 4), Square::new(0 * 9 + 4), ];
for sq in w_squares {
assert!(e.test(sq), "Eビームに{:?}が含まれていない", sq);
}
}
#[test]
fn test_rook_beams_edge() {
let sq = SQ_11; let beams = ROOK_BEAMS[sq.to_index()];
let w = beams.e;
let e = beams.w;
assert!(beams.n.is_empty(), "1段目からのNビームは盤外");
assert!(e.is_empty(), "1筋からのEビームは盤外");
assert_eq!(w.count(), 8, "1筋からのWビームは8マス");
assert_eq!(beams.s.count(), 8, "1段目からのSビームは8マス");
}
#[test]
fn test_slider_attacks_implementation_note() {
let sq = SQ_55;
let occupied = Bitboard::EMPTY;
let _lance = lance_attacks(sq, occupied, Color::BLACK);
let _rook = rook_attacks(sq, occupied);
let _bishop = bishop_attacks(sq, occupied);
}
#[test]
fn test_lance_attacks_with_occupied() {
use crate::types::Color;
let sq = SQ_55;
let occupied = Bitboard::EMPTY;
let attacks = lance_attacks(sq, occupied, Color::BLACK);
assert_eq!(attacks.count(), 4, "占有なしの香車利きは4マス");
assert!(attacks.test(Square::new(39)), "rank=3への利き");
assert!(attacks.test(Square::new(38)), "rank=2への利き");
assert!(attacks.test(Square::new(37)), "rank=1への利き");
assert!(attacks.test(Square::new(36)), "rank=0への利き");
let mut occupied = Bitboard::EMPTY;
occupied.set(Square::new(39)); let attacks = lance_attacks(sq, occupied, Color::BLACK);
assert_eq!(attacks.count(), 1, "遮断点までの香車利きは1マス");
assert!(attacks.test(Square::new(39)), "遮断点への利き");
assert!(!attacks.test(Square::new(38)), "遮断点より奥の利きはなし");
}
#[test]
fn test_rook_attacks_with_occupied() {
let sq = SQ_55;
let occupied = Bitboard::EMPTY;
let attacks = rook_attacks(sq, occupied);
let beams = ROOK_BEAMS[sq.to_index()];
let north = attacks & beams.n;
let south = attacks & beams.s;
let west = attacks & beams.e;
let east = attacks & beams.w;
assert_eq!(north.count(), 4, "N方向の飛車利きは4マス");
assert_eq!(south.count(), 4, "S方向の飛車利きは4マス");
assert_eq!(west.count(), 4, "W方向の飛車利きは4マス");
assert_eq!(east.count(), 4, "E方向の飛車利きは4マス");
assert_eq!(attacks.count(), 16, "占有なしの飛車利きは16マス");
assert!(attacks.test(Square::new(39)), "N方向rank=3への利き");
assert!(attacks.test(Square::new(36)), "N方向rank=0への利き");
assert!(attacks.test(Square::new(49)), "W方向file=5への利き");
assert!(attacks.test(Square::new(76)), "W方向file=8への利き");
let mut occupied = Bitboard::EMPTY;
occupied.set(Square::new(39)); occupied.set(Square::new(49)); let attacks = rook_attacks(sq, occupied);
assert_eq!(attacks.count(), 10, "遮断ありの飛車利きは10マス");
assert!(attacks.test(Square::new(39)), "Nの遮断点への利き");
assert!(attacks.test(Square::new(49)), "Wの遮断点への利き");
assert!(!attacks.test(Square::new(38)), "Nの遮断点より奥の利きはなし");
}
#[test]
fn test_qugiy_rook_mask_matches_generated_rays() {
let sq = SQ_55;
let file = sq.file().raw();
let rank = sq.rank().raw();
let mut west = Bitboard::EMPTY;
for f in (file + 1)..=File::FILE_9.raw() {
west.set(Square::from_file_rank(File::new(f), Rank::new(rank)));
}
let mut east = Bitboard::EMPTY;
for f in (File::FILE_1.raw()..file).rev() {
east.set(Square::from_file_rank(File::new(f), Rank::new(rank)));
}
let east_rev = east.byte_reverse();
let (hi, lo) = Bitboard::unpack(east_rev, west);
assert_eq!(lo, QUGIY_ROOK_MASK[sq.to_index()][0]);
assert_eq!(hi, QUGIY_ROOK_MASK[sq.to_index()][1]);
}
#[test]
fn test_rook_file_attacks_matches_lance_pair() {
let occupancies = [
Bitboard::EMPTY,
Bitboard::ALL,
Bitboard::from_square(SQ_11) | Bitboard::from_square(SQ_55) | Bitboard::from_square(SQ_99),
Bitboard::file_mask(File::FILE_1) | Bitboard::file_mask(File::FILE_9),
Bitboard::rank_mask(Rank::RANK_3) | Bitboard::rank_mask(Rank::RANK_7),
];
for raw in 0..Square::COUNT as i8 {
let sq = Square::new(raw);
for occupied in occupancies {
let expected = lance_attacks(sq, occupied, Color::BLACK)
| lance_attacks(sq, occupied, Color::WHITE);
assert_eq!(rook_file_attacks(sq, occupied), expected, "sq={sq:?}");
}
}
}
#[test]
#[allow(clippy::similar_names)]
fn test_qugiy_bishop_mask_matches_generated_rays() {
let sq = SQ_55;
let file = sq.file().raw();
let rank = sq.rank().raw();
let mut lu = Bitboard::EMPTY;
let mut ld = Bitboard::EMPTY;
let mut ru = Bitboard::EMPTY;
let mut rd = Bitboard::EMPTY;
let mut f = file + 1;
let mut r = rank - 1;
while f <= File::FILE_9.raw() && r >= Rank::RANK_1.raw() {
lu.set(Square::from_file_rank(File::new(f), Rank::new(r)));
f += 1;
r -= 1;
}
let mut f = file + 1;
let mut r = rank + 1;
while f <= File::FILE_9.raw() && r <= Rank::RANK_9.raw() {
ld.set(Square::from_file_rank(File::new(f), Rank::new(r)));
f += 1;
r += 1;
}
let mut f = file - 1;
let mut r = rank - 1;
while f >= File::FILE_1.raw() && r >= Rank::RANK_1.raw() {
ru.set(Square::from_file_rank(File::new(f), Rank::new(r)));
f -= 1;
r -= 1;
}
let mut f = file - 1;
let mut r = rank + 1;
while f >= File::FILE_1.raw() && r <= Rank::RANK_9.raw() {
rd.set(Square::from_file_rank(File::new(f), Rank::new(r)));
f -= 1;
r += 1;
}
let ru = ru.byte_reverse();
let rd = rd.byte_reverse();
for (part, mask) in QUGIY_BISHOP_MASK[sq.to_index()].iter().enumerate().take(2) {
let [p0, p1, p2, p3] = mask.parts();
let [lu_lo, lu_hi] = lu.parts();
let [ru_lo, ru_hi] = ru.parts();
let [ld_lo, ld_hi] = ld.parts();
let [rd_lo, rd_hi] = rd.parts();
if part == 0 {
assert_eq!(p0, lu_lo);
assert_eq!(p1, ru_lo);
assert_eq!(p2, ld_lo);
assert_eq!(p3, rd_lo);
} else {
assert_eq!(p0, lu_hi);
assert_eq!(p1, ru_hi);
assert_eq!(p2, ld_hi);
assert_eq!(p3, rd_hi);
}
}
}
#[test]
fn test_qugiy_step_attacks_direction_order() {
let sq = SQ_55;
let file = sq.file().raw();
let rank = sq.rank().raw();
let directions = [(-1, -1), (-1, 0), (-1, 1), (1, -1), (1, 0), (1, 1)];
let reverse_dirs = [true, true, true, false, false, false];
for (dir_idx, (df, dr)) in directions.iter().enumerate() {
let mut ray = Bitboard::EMPTY;
let mut f = file + df;
let mut r = rank + dr;
while (File::FILE_1.raw()..=File::FILE_9.raw()).contains(&f)
&& (Rank::RANK_1.raw()..=Rank::RANK_9.raw()).contains(&r)
{
ray.set(Square::from_file_rank(File::new(f), Rank::new(r)));
f += df;
r += dr;
}
if reverse_dirs[dir_idx] {
ray = ray.byte_reverse();
}
assert_eq!(ray, QUGIY_STEP_ATTACKS[dir_idx][sq.to_index()]);
}
}
#[test]
fn test_bitboard_attacks_counts_match_yaneuraou() {
let sq = SQ_55;
let empty = Bitboard::EMPTY;
let filled = Bitboard::ALL;
let p0_table = [0, 1, 4, 2, 5, 16, 16, 6, 8, 6, 6, 6, 6, 20, 20, 6];
let p1_table = [0, 1, 1, 2, 5, 4, 4, 6, 8, 6, 6, 6, 6, 8, 8, 6];
let piece_types = [
PieceType::PAWN,
PieceType::LANCE,
PieceType::KNIGHT,
PieceType::SILVER,
PieceType::BISHOP,
PieceType::ROOK,
PieceType::GOLD,
PieceType::KING,
PieceType::PRO_PAWN,
PieceType::PRO_LANCE,
PieceType::PRO_KNIGHT,
PieceType::PRO_SILVER,
PieceType::HORSE,
PieceType::DRAGON,
];
for color in [Color::BLACK, Color::WHITE] {
for piece_type in piece_types {
let idx = piece_type.to_index();
let attacks0 = attacks_from(piece_type, color, sq, empty);
let attacks1 = attacks_from(piece_type, color, sq, filled);
assert_eq!(attacks0.count(), p0_table[idx]);
assert_eq!(attacks1.count(), p1_table[idx]);
}
}
}
#[test]
fn test_bishop_attacks_with_occupied() {
let sq = SQ_55;
let occupied = Bitboard::EMPTY;
let attacks = bishop_attacks(sq, occupied);
assert_eq!(attacks.count(), 16, "占有なしの角利きは16マス");
assert!(attacks.test(Square::new(48)), "NW方向(5,3)への利き");
assert!(attacks.test(Square::new(72)), "NW方向(8,0)への利き");
assert!(attacks.test(Square::new(50)), "SW方向(5,5)への利き");
assert!(attacks.test(Square::new(80)), "SW方向(8,8)への利き");
let mut occupied = Bitboard::EMPTY;
occupied.set(Square::new(48)); occupied.set(Square::new(50)); let attacks = bishop_attacks(sq, occupied);
assert_eq!(attacks.count(), 10, "遮断ありの角利きは10マス");
assert!(attacks.test(Square::new(48)), "NWの遮断点への利き");
assert!(attacks.test(Square::new(50)), "SWの遮断点への利き");
assert!(!attacks.test(Square::new(56)), "NWの遮断点より奥の利きはなし");
}