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]
}
#[test]
fn unchecked_step_attack_accessors_match_generated_geometry() {
for sq in Square::iter() {
for color in Color::iter() {
assert_eq!(
unsafe { pawn_attacks_unchecked(sq, color) },
PAWN_ATTACKS[sq][color.to_index()]
);
assert_eq!(
unsafe { knight_attacks_unchecked(sq, color) },
KNIGHT_ATTACKS[sq][color.to_index()]
);
assert_eq!(
unsafe { silver_attacks_unchecked(sq, color) },
SILVER_ATTACKS[sq][color.to_index()]
);
assert_eq!(
unsafe { gold_attacks_unchecked(sq, color) },
GOLD_ATTACKS[sq][color.to_index()]
);
let lance = LANCE_BEAMS[sq];
let expected_lance = match color {
Color::BLACK => lance.black,
Color::WHITE => lance.white,
};
assert_eq!(crate::board::attack_tables::lance_step_attacks(sq, color), expected_lance);
}
assert_eq!(unsafe { king_attacks_unchecked(sq) }, KING_ATTACKS[sq]);
let bishop = BISHOP_BEAMS[sq];
assert_eq!(
crate::board::attack_tables::bishop_step_attacks(sq),
bishop.ne | bishop.se | bishop.sw | bishop.nw
);
let rook = ROOK_BEAMS[sq];
assert_eq!(
crate::board::attack_tables::rook_step_attacks(sq),
rook.n | rook.e | rook.s | rook.w
);
}
}
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);
}
#[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_pawn_attack_matches_board_geometry() {
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_knight_attack_matches_board_geometry() {
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_silver_attack_matches_board_geometry() {
let sq = SQ_55;
let attacks = SILVER_ATTACKS[sq][Color::BLACK.to_index()];
assert_eq!(attacks.count(), 5, "銀の利きは5マス");
}
#[test]
fn test_gold_attack_matches_board_geometry() {
let sq = SQ_55;
let attacks = GOLD_ATTACKS[sq][Color::BLACK.to_index()];
assert_eq!(attacks.count(), 6, "金の利きは6マス");
}
#[test]
fn test_king_attack_matches_board_geometry() {
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_attack_api_accepts_empty_occupancy() {
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_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:?}");
}
}
}
fn scalar_ray_attacks(sq: Square, occupied: Bitboard, direction: (i8, i8)) -> Bitboard {
let mut attacks = Bitboard::EMPTY;
let (df, dr) = direction;
let mut file = sq.file().raw() + df;
let mut rank = sq.rank().raw() + dr;
while (0..9).contains(&file) && (0..9).contains(&rank) {
let target = Square::from_file_rank(File::new(file), Rank::new(rank));
attacks.set(target);
if occupied.test(target) {
break;
}
file += df;
rank += dr;
}
attacks
}
fn scalar_slider_attacks(sq: Square, occupied: Bitboard, directions: [(i8, i8); 4]) -> Bitboard {
directions.into_iter().fold(Bitboard::EMPTY, |attacks, direction| {
attacks | scalar_ray_attacks(sq, occupied, direction)
})
}
fn blocker_choices(sq: Square, direction: (i8, i8)) -> Vec<Bitboard> {
let (df, dr) = direction;
let mut choices = vec![Bitboard::EMPTY];
let mut file = sq.file().raw() + df;
let mut rank = sq.rank().raw() + dr;
while (0..9).contains(&file) && (0..9).contains(&rank) {
choices
.push(Bitboard::from_square(Square::from_file_rank(File::new(file), Rank::new(rank))));
file += df;
rank += dr;
}
choices
}
fn assert_slider_matches_scalar(
attacks: fn(Square, Bitboard) -> Bitboard,
directions: [(i8, i8); 4],
) {
for raw in 0..Square::COUNT as i8 {
let sq = Square::new(raw);
let choices = directions.map(|direction| blocker_choices(sq, direction));
for b0 in &choices[0] {
for b1 in &choices[1] {
for b2 in &choices[2] {
for b3 in &choices[3] {
let occupied = *b0 | *b1 | *b2 | *b3;
let expected = scalar_slider_attacks(sq, occupied, directions);
assert_eq!(attacks(sq, occupied), expected, "sq={sq:?}");
}
}
}
}
}
}
#[test]
fn test_rook_attacks_matches_scalar_for_every_nearest_blocker() {
assert_slider_matches_scalar(rook_attacks, [(0, -1), (1, 0), (0, 1), (-1, 0)]);
}
#[test]
fn test_bishop_attacks_matches_scalar_for_every_nearest_blocker() {
assert_slider_matches_scalar(bishop_attacks, [(1, -1), (1, 1), (-1, 1), (-1, -1)]);
}
#[test]
fn test_lance_attacks_matches_scalar_for_every_nearest_blocker() {
for raw in 0..Square::COUNT as i8 {
let sq = Square::new(raw);
for (color, direction) in [(Color::BLACK, (0, -1)), (Color::WHITE, (0, 1))] {
for blocker in blocker_choices(sq, direction) {
let expected = scalar_ray_attacks(sq, blocker, direction);
assert_eq!(lance_attacks(sq, blocker, color), expected, "sq={sq:?} color={color}");
}
}
}
}
#[test]
fn test_slider_attacks_match_scalar_for_randomized_occupancies() {
let mut state = 0xd1b5_4a32_d192_ed03u64;
for _ in 0..512 {
state ^= state << 7;
state ^= state >> 9;
state ^= state << 8;
let occupied = Bitboard::from_packed_bits(
u128::from(state) | (u128::from(state.rotate_left(29)) << 64),
);
for raw in 0..Square::COUNT as i8 {
let sq = Square::new(raw);
assert_eq!(
rook_attacks(sq, occupied),
scalar_slider_attacks(sq, occupied, [(0, -1), (1, 0), (0, 1), (-1, 0)]),
"rook sq={sq:?}"
);
assert_eq!(
bishop_attacks(sq, occupied),
scalar_slider_attacks(sq, occupied, [(1, -1), (1, 1), (-1, 1), (-1, -1)]),
"bishop sq={sq:?}"
);
for (color, direction) in [(Color::BLACK, (0, -1)), (Color::WHITE, (0, 1))] {
assert_eq!(
lance_attacks(sq, occupied, color),
scalar_ray_attacks(sq, occupied, direction),
"lance sq={sq:?} color={color}"
);
}
}
}
}
#[test]
fn test_piece_attack_counts_on_center_square() {
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の遮断点より奥の利きはなし");
}