use crate::bitboard::{
Bitboard, FILE_BB, RANK_BB, between_bb, bishop_effect, check_candidate_bb, dragon_effect,
gold_effect, horse_effect, king_effect, knight_effect, lance_effect, line_bb, pawn_effect,
rook_effect, silver_effect,
};
use crate::position::Position;
use crate::types::{Color, Move, PieceType, Square};
use super::movelist::MoveList;
use super::types::ExtMoveBuffer;
#[derive(Clone, Copy)]
struct GenerateTargets {
general: Bitboard,
pawn: Bitboard,
drop: Bitboard,
}
impl GenerateTargets {
fn new(bb: Bitboard) -> Self {
Self {
general: bb,
pawn: bb,
drop: bb,
}
}
fn with_drop(bb: Bitboard, drop: Bitboard) -> Self {
Self {
general: bb,
pawn: bb,
drop,
}
}
}
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], }
}
fn rank1_bb(us: Color) -> Bitboard {
match us {
Color::Black => RANK_BB[0], Color::White => RANK_BB[8], }
}
fn rank12_bb(us: Color) -> Bitboard {
match us {
Color::Black => RANK_BB[0] | RANK_BB[1], Color::White => RANK_BB[7] | RANK_BB[8], }
}
#[inline]
fn add_move(buffer: &mut ExtMoveBuffer, mv: Move) {
buffer.push_move(mv);
}
#[derive(Clone, Copy)]
enum PromotionMode {
Both,
PromoteOnly,
}
fn generate_pawn_moves(
pos: &Position,
target: Bitboard,
buffer: &mut ExtMoveBuffer,
promo_mode: PromotionMode,
) {
let us = pos.side_to_move();
let pawns = pos.pieces(us, PieceType::Pawn);
if pawns.is_empty() {
return;
}
let promo_ranks = enemy_field(us);
let rank1 = rank1_bb(us);
for from in pawns.iter() {
let attacks = pawn_effect(us, from) & target;
let moved_pc = pos.piece_on(from);
for to in attacks.iter() {
let in_promo = promo_ranks.contains(to);
let to_is_rank1 = rank1.contains(to);
match (in_promo, promo_mode) {
(true, PromotionMode::PromoteOnly) => {
let promoted_pc = moved_pc.promote().unwrap();
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
}
(true, PromotionMode::Both) => {
let promoted_pc = moved_pc.promote().unwrap();
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if !to_is_rank1 {
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
}
(false, _) => {
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc))
}
}
}
}
}
fn generate_lance_moves(
pos: &Position,
target: Bitboard,
buffer: &mut ExtMoveBuffer,
include_non_promotions: bool,
) {
let us = pos.side_to_move();
let lances = pos.pieces(us, PieceType::Lance);
if lances.is_empty() {
return;
}
let promo_ranks = enemy_field(us);
let rank1 = rank1_bb(us);
let occupied = pos.occupied();
let rank12 = rank12_bb(us);
let non_promo_mask = if include_non_promotions {
!rank1
} else {
!rank12
};
for from in lances.iter() {
let attacks = lance_effect(us, from, occupied) & target;
let moved_pc = pos.piece_on(from);
let promo_targets = attacks & promo_ranks;
let promoted_pc = moved_pc.promote().unwrap();
for to in promo_targets.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
}
let non_promo_targets = attacks & non_promo_mask;
for to in non_promo_targets.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
}
}
fn generate_knight_moves(pos: &Position, target: Bitboard, buffer: &mut ExtMoveBuffer) {
let us = pos.side_to_move();
let knights = pos.pieces(us, PieceType::Knight);
if knights.is_empty() {
return;
}
let promo_ranks = enemy_field(us);
let rank12 = rank12_bb(us);
for from in knights.iter() {
let attacks = knight_effect(us, from) & target;
let moved_pc = pos.piece_on(from);
for to in attacks.iter() {
if promo_ranks.contains(to) {
let promoted_pc = moved_pc.promote().unwrap();
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if !rank12.contains(to) {
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
} else {
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
}
}
}
fn generate_silver_moves(pos: &Position, target: Bitboard, buffer: &mut ExtMoveBuffer) {
let us = pos.side_to_move();
let silvers = pos.pieces(us, PieceType::Silver);
if silvers.is_empty() {
return;
}
let promo_ranks = enemy_field(us);
for from in silvers.iter() {
let attacks = silver_effect(us, from) & target;
let from_in_promo = promo_ranks.contains(from);
let moved_pc = pos.piece_on(from);
if from_in_promo {
let promoted_pc = moved_pc.promote().unwrap();
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
} else {
let promo_targets = attacks & promo_ranks;
let non_promo_targets = attacks & !promo_ranks;
let promoted_pc = moved_pc.promote().unwrap();
for to in promo_targets.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
for to in non_promo_targets.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, moved_pc));
}
}
}
}
fn generate_br_moves(
pos: &Position,
target: Bitboard,
buffer: &mut ExtMoveBuffer,
include_non_promotions: bool,
) {
let us = pos.side_to_move();
let pieces = pos.pieces(us, PieceType::Bishop) | pos.pieces(us, PieceType::Rook);
if pieces.is_empty() {
return;
}
let promo_ranks = enemy_field(us);
let occupied = pos.occupied();
for from in pieces.iter() {
let pc = pos.piece_on(from);
let pt = pc.piece_type();
let attacks = match pt {
PieceType::Bishop => bishop_effect(from, occupied),
PieceType::Rook => rook_effect(from, occupied),
_ => unreachable!(),
} & target;
let from_in_promo = promo_ranks.contains(from);
if from_in_promo {
let promoted_pc = pc.promote().unwrap();
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if include_non_promotions {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
} else {
let promo_targets = attacks & promo_ranks;
let non_promo_targets = attacks & !promo_ranks;
let promoted_pc = pc.promote().unwrap();
for to in promo_targets.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if include_non_promotions {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
for to in non_promo_targets.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
}
fn generate_ghdk_moves(pos: &Position, target: Bitboard, buffer: &mut ExtMoveBuffer) {
let us = pos.side_to_move();
let occupied = pos.occupied();
let king_sq = pos.king_square(us);
let pieces = pos.golds_c(us)
| pos.pieces(us, PieceType::Horse)
| pos.pieces(us, PieceType::Dragon)
| Bitboard::from_square(king_sq);
for from in pieces.iter() {
let pc = pos.piece_on(from);
let pt = pc.piece_type();
let attacks = match pt {
PieceType::Gold
| PieceType::ProPawn
| PieceType::ProLance
| PieceType::ProKnight
| PieceType::ProSilver => gold_effect(us, from),
PieceType::Horse => horse_effect(from, occupied),
PieceType::Dragon => dragon_effect(from, occupied),
PieceType::King => king_effect(from),
_ => unreachable!(),
} & target;
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
fn generate_ghd_moves(pos: &Position, target: Bitboard, buffer: &mut ExtMoveBuffer) {
let us = pos.side_to_move();
let occupied = pos.occupied();
let pieces =
pos.golds_c(us) | pos.pieces(us, PieceType::Horse) | pos.pieces(us, PieceType::Dragon);
for from in pieces.iter() {
let pc = pos.piece_on(from);
let pt = pc.piece_type();
let attacks = match pt {
PieceType::Gold
| PieceType::ProPawn
| PieceType::ProLance
| PieceType::ProKnight
| PieceType::ProSilver => gold_effect(us, from),
PieceType::Horse => horse_effect(from, occupied),
PieceType::Dragon => dragon_effect(from, occupied),
_ => unreachable!(),
} & target;
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
fn pawn_drop_mask(us: Color, our_pawns: Bitboard) -> Bitboard {
match us {
Color::Black | Color::White => {} }
let mut mask = Bitboard::ALL;
for file_bb in &FILE_BB {
if !(our_pawns & *file_bb).is_empty() {
mask &= !*file_bb;
}
}
mask
}
fn generate_pawn_drops(pos: &Position, target: Bitboard, buffer: &mut ExtMoveBuffer) {
let us = pos.side_to_move();
if !pos.hand(us).has(PieceType::Pawn) {
return;
}
let empties = !pos.occupied();
let rank1 = rank1_bb(us);
let valid_targets = target & empties & !rank1;
let our_pawns = pos.pieces(us, PieceType::Pawn);
let mut valid_targets = valid_targets & pawn_drop_mask(us, our_pawns);
let them = !us;
let them_king = pos.king_square(them);
let pe = pawn_effect(them, them_king);
if let Some(to) = (pe & valid_targets).lsb()
&& !pos.legal_pawn_drop_check(to)
{
valid_targets ^= pe;
}
let dropped_pc = crate::types::Piece::make(us, PieceType::Pawn);
for to in valid_targets.iter() {
add_move(buffer, Move::new_drop_with_piece(PieceType::Pawn, to, dropped_pc));
}
}
fn generate_non_pawn_drops(pos: &Position, target: Bitboard, buffer: &mut ExtMoveBuffer) {
let us = pos.side_to_move();
let hand = pos.hand(us);
let empties = !pos.occupied();
let target = target & empties;
let dummy = (PieceType::Pawn, crate::types::Piece::make(us, PieceType::Pawn));
let mut drops = [dummy; 6];
let mut num = 0usize;
if hand.has(PieceType::Knight) {
drops[num] = (PieceType::Knight, crate::types::Piece::make(us, PieceType::Knight));
num += 1;
}
let next_to_knight = num;
if hand.has(PieceType::Lance) {
drops[num] = (PieceType::Lance, crate::types::Piece::make(us, PieceType::Lance));
num += 1;
}
let next_to_lance = num;
for pt in [
PieceType::Silver,
PieceType::Gold,
PieceType::Bishop,
PieceType::Rook,
] {
if hand.has(pt) {
drops[num] = (pt, crate::types::Piece::make(us, pt));
num += 1;
}
}
if num == 0 {
return;
}
let drops = &drops[..num];
if next_to_lance == 0 {
for to in target.iter() {
for &(pt, pc) in drops {
add_move(buffer, Move::new_drop_with_piece(pt, to, pc));
}
}
} else {
let rank1 = rank1_bb(us);
let rank12 = rank12_bb(us);
let rank2_only = rank12 & !rank1;
let target1 = target & rank1;
if next_to_lance < num {
for to in target1.iter() {
for &(pt, pc) in &drops[next_to_lance..] {
add_move(buffer, Move::new_drop_with_piece(pt, to, pc));
}
}
}
let target2 = target & rank2_only;
if next_to_knight < num {
for to in target2.iter() {
for &(pt, pc) in &drops[next_to_knight..] {
add_move(buffer, Move::new_drop_with_piece(pt, to, pc));
}
}
}
let target3 = target & !rank12;
for to in target3.iter() {
for &(pt, pc) in drops {
add_move(buffer, Move::new_drop_with_piece(pt, to, pc));
}
}
}
}
fn generate_non_evasions_core(
pos: &Position,
buffer: &mut ExtMoveBuffer,
targets: GenerateTargets,
include_non_promotions: bool,
pawn_promo_mode: PromotionMode,
include_drops: bool,
) {
generate_pawn_moves(pos, targets.pawn, buffer, pawn_promo_mode);
generate_lance_moves(pos, targets.general, buffer, include_non_promotions);
generate_knight_moves(pos, targets.general, buffer);
generate_silver_moves(pos, targets.general, buffer);
generate_br_moves(pos, targets.general, buffer, include_non_promotions);
generate_ghdk_moves(pos, targets.general, buffer);
if include_drops {
let drop_target = targets.drop & !pos.occupied();
generate_pawn_drops(pos, drop_target, buffer);
generate_non_pawn_drops(pos, drop_target, buffer);
}
}
pub fn generate_non_evasions(pos: &Position, buffer: &mut ExtMoveBuffer) -> usize {
let us = pos.side_to_move();
let targets = GenerateTargets::with_drop(!pos.pieces_c(us), !pos.occupied());
generate_non_evasions_core(pos, buffer, targets, false, PromotionMode::PromoteOnly, true);
buffer.len()
}
fn generate_evasions_with_promos(
pos: &Position,
buffer: &mut ExtMoveBuffer,
include_non_promotions: bool,
pawn_promo_mode: PromotionMode,
) {
debug_assert!(pos.in_check());
let us = pos.side_to_move();
let them = !us;
let king_sq = pos.king_square(us);
let checkers = pos.checkers();
let occupied = pos.occupied();
let occ_without_king = occupied & !Bitboard::from_square(king_sq);
let mut checker_attacks = Bitboard::EMPTY;
let mut checker_count = 0;
let mut checker_sq: Option<Square> = None;
for sq in checkers.iter() {
checker_count += 1;
checker_sq = Some(sq);
let pc = pos.piece_on(sq);
let pt = pc.piece_type();
let attacks_from_checker = match pt {
PieceType::Pawn => pawn_effect(them, sq),
PieceType::Lance => lance_effect(them, sq, occ_without_king),
PieceType::Knight => knight_effect(them, sq),
PieceType::Silver => silver_effect(them, sq),
PieceType::Gold
| PieceType::ProPawn
| PieceType::ProLance
| PieceType::ProKnight
| PieceType::ProSilver => gold_effect(them, sq),
PieceType::Bishop => bishop_effect(sq, occ_without_king),
PieceType::Rook => rook_effect(sq, occ_without_king),
PieceType::Horse => bishop_effect(sq, occ_without_king) | king_effect(sq),
PieceType::Dragon => rook_effect(sq, occ_without_king) | king_effect(sq),
PieceType::King => king_effect(sq),
};
checker_attacks |= attacks_from_checker;
}
let king_targets = king_effect(king_sq) & !pos.pieces_c(us) & !checker_attacks;
let moved_pc = pos.piece_on(king_sq);
for to in king_targets.iter() {
add_move(buffer, Move::new_move_with_piece(king_sq, to, false, moved_pc));
}
if checker_count >= 2 {
return;
}
let checker_sq = checker_sq.expect("in_checkなら王手駒が存在する");
let between = between_bb(checker_sq, king_sq);
let drop_target = between; let move_target = between | Bitboard::from_square(checker_sq);
generate_pawn_moves(pos, move_target, buffer, pawn_promo_mode);
generate_lance_moves(pos, move_target, buffer, include_non_promotions);
generate_knight_moves(pos, move_target, buffer);
generate_silver_moves(pos, move_target, buffer);
generate_br_moves(pos, move_target, buffer, include_non_promotions);
generate_ghd_moves(pos, move_target, buffer);
if !drop_target.is_empty() {
generate_pawn_drops(pos, drop_target, buffer);
generate_non_pawn_drops(pos, drop_target, buffer);
}
}
pub fn generate_evasions(pos: &Position, buffer: &mut ExtMoveBuffer) -> usize {
generate_evasions_with_promos(pos, buffer, false, PromotionMode::PromoteOnly);
buffer.len()
}
#[inline]
fn piece_effect(pt: PieceType, us: Color, from: Square, occupied: Bitboard) -> Bitboard {
match pt {
PieceType::Pawn => pawn_effect(us, from),
PieceType::Lance => lance_effect(us, from, occupied),
PieceType::Knight => knight_effect(us, from),
PieceType::Silver => silver_effect(us, from),
PieceType::Gold
| PieceType::ProPawn
| PieceType::ProLance
| PieceType::ProKnight
| PieceType::ProSilver => gold_effect(us, from),
PieceType::Bishop => bishop_effect(from, occupied),
PieceType::Rook => rook_effect(from, occupied),
PieceType::Horse => horse_effect(from, occupied),
PieceType::Dragon => dragon_effect(from, occupied),
PieceType::King => king_effect(from),
}
}
fn generate_moves_from_sq(
pos: &Position,
buffer: &mut ExtMoveBuffer,
from: Square,
target: Bitboard,
include_non_promotions: bool,
pawn_promo_mode: PromotionMode,
) {
let us = pos.side_to_move();
let pc = pos.piece_on(from);
let pt = pc.piece_type();
let occupied = pos.occupied();
let effect = piece_effect(pt, us, from, occupied);
let attacks = effect & target;
if attacks.is_empty() {
return;
}
let promo_ranks = enemy_field(us);
let from_in_promo = promo_ranks.contains(from);
match pt {
PieceType::Pawn => {
let rank1 = rank1_bb(us);
for to in attacks.iter() {
let in_promo = promo_ranks.contains(to);
match (in_promo, pawn_promo_mode) {
(true, PromotionMode::PromoteOnly) => {
add_move(
buffer,
Move::new_move_with_piece(from, to, true, pc.promote().unwrap()),
);
}
(true, PromotionMode::Both) => {
add_move(
buffer,
Move::new_move_with_piece(from, to, true, pc.promote().unwrap()),
);
if !rank1.contains(to) {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
(false, _) => {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
}
PieceType::Lance => {
let rank1 = rank1_bb(us);
let rank12 = rank12_bb(us);
let non_promo_mask = if include_non_promotions {
!rank1
} else {
!rank12
};
let promoted_pc = pc.promote().unwrap();
for to in (attacks & promo_ranks).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
}
for to in (attacks & non_promo_mask).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
PieceType::Knight => {
let rank12 = rank12_bb(us);
let promoted_pc = pc.promote().unwrap();
for to in attacks.iter() {
if promo_ranks.contains(to) {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if !rank12.contains(to) {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
} else {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
PieceType::Silver => {
let promoted_pc = pc.promote().unwrap();
if from_in_promo {
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
} else {
for to in (attacks & promo_ranks).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
for to in (attacks & !promo_ranks).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
PieceType::Bishop | PieceType::Rook => {
let promoted_pc = pc.promote().unwrap();
if from_in_promo {
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if include_non_promotions {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
} else {
for to in (attacks & promo_ranks).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
if include_non_promotions {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
for to in (attacks & !promo_ranks).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
_ => {
for to in attacks.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
}
fn generate_direct_check_from_sq(
pos: &Position,
buffer: &mut ExtMoveBuffer,
from: Square,
target: Bitboard,
include_non_promotions: bool,
pawn_promo_mode: PromotionMode,
) {
let us = pos.side_to_move();
let pc = pos.piece_on(from);
let pt = pc.piece_type();
let occupied = pos.occupied();
let effect = piece_effect(pt, us, from, occupied);
let promo_ranks = enemy_field(us);
let from_in_promo = promo_ranks.contains(from);
if let Some(promoted_pt) = pt.promote() {
let promoted_pc = pc.promote().unwrap();
let check_sq_promoted = pos.check_squares(promoted_pt);
let check_sq_raw = pos.check_squares(pt);
let promo_dst = effect & check_sq_promoted & target;
let promo_dst = if from_in_promo {
promo_dst
} else {
promo_dst & promo_ranks
};
for to in promo_dst.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, true, promoted_pc));
}
let nonpro_dst = effect & check_sq_raw & target;
match pt {
PieceType::Pawn => {
let rank1 = rank1_bb(us);
let mask = match pawn_promo_mode {
PromotionMode::PromoteOnly => !promo_ranks, PromotionMode::Both => !rank1, };
for to in (nonpro_dst & mask).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
PieceType::Lance => {
let rank1 = rank1_bb(us);
let rank12 = rank12_bb(us);
let mask = if include_non_promotions {
!rank1
} else {
!rank12
};
for to in (nonpro_dst & mask).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
PieceType::Knight => {
let rank12 = rank12_bb(us);
for to in (nonpro_dst & !rank12).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
PieceType::Silver => {
for to in nonpro_dst.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
PieceType::Bishop | PieceType::Rook => {
let mask = if include_non_promotions {
Bitboard::ALL } else if from_in_promo {
Bitboard::EMPTY } else {
!promo_ranks };
for to in (nonpro_dst & mask).iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
_ => unreachable!(),
}
} else {
let check_sq = pos.check_squares(pt);
let dst = effect & check_sq & target;
for to in dst.iter() {
add_move(buffer, Move::new_move_with_piece(from, to, false, pc));
}
}
}
fn generate_checks(
pos: &Position,
buffer: &mut ExtMoveBuffer,
include_non_promotions: bool,
pawn_promo_mode: PromotionMode,
quiet_only: bool,
) {
let us = pos.side_to_move();
let them = !us;
let them_king = pos.king_square(them);
let occupied = pos.occupied();
let target = if quiet_only {
!occupied
} else {
!pos.pieces_c(us)
};
let blockers = pos.blockers_for_king(them) & pos.pieces_c(us);
for from in blockers.iter() {
let pin_line = line_bb(them_king, from);
let disc_target = target & !pin_line;
generate_moves_from_sq(
pos,
buffer,
from,
disc_target,
include_non_promotions,
pawn_promo_mode,
);
let direct_on_line = target & pin_line;
if !direct_on_line.is_empty() {
generate_direct_check_from_sq(
pos,
buffer,
from,
direct_on_line,
include_non_promotions,
pawn_promo_mode,
);
}
}
let candidates = (pos.pieces(us, PieceType::Pawn)
& check_candidate_bb(us, PieceType::Pawn, them_king))
| (pos.pieces(us, PieceType::Lance)
& check_candidate_bb(us, PieceType::Lance, them_king))
| (pos.pieces(us, PieceType::Knight)
& check_candidate_bb(us, PieceType::Knight, them_king))
| (pos.pieces(us, PieceType::Silver)
& check_candidate_bb(us, PieceType::Silver, them_king))
| (pos.golds_c(us) & check_candidate_bb(us, PieceType::Gold, them_king))
| (pos.pieces(us, PieceType::Bishop)
& check_candidate_bb(us, PieceType::Bishop, them_king))
| (pos.rook_dragon() & pos.pieces_c(us)) | (pos.pieces(us, PieceType::Horse)
& check_candidate_bb(us, PieceType::Horse, them_king));
let non_blockers = candidates & !blockers;
for from in non_blockers.iter() {
generate_direct_check_from_sq(
pos,
buffer,
from,
target,
include_non_promotions,
pawn_promo_mode,
);
}
let empties = !occupied;
let hand = pos.hand(us);
if hand.has(PieceType::Pawn) {
let check_target = pos.check_squares(PieceType::Pawn) & empties;
if !check_target.is_empty() {
let rank1 = rank1_bb(us);
let our_pawns = pos.pieces(us, PieceType::Pawn);
let valid = check_target & !rank1 & pawn_drop_mask(us, our_pawns);
let dropped_pc = crate::types::Piece::make(us, PieceType::Pawn);
for to in valid.iter() {
if !pos.legal_pawn_drop_check(to) {
continue;
}
add_move(buffer, Move::new_drop_with_piece(PieceType::Pawn, to, dropped_pc));
}
}
}
if hand.has(PieceType::Lance) {
let check_target = pos.check_squares(PieceType::Lance) & empties;
let rank1 = rank1_bb(us);
let dropped_pc = crate::types::Piece::make(us, PieceType::Lance);
for to in (check_target & !rank1).iter() {
add_move(buffer, Move::new_drop_with_piece(PieceType::Lance, to, dropped_pc));
}
}
if hand.has(PieceType::Knight) {
let check_target = pos.check_squares(PieceType::Knight) & empties;
let rank12 = rank12_bb(us);
let dropped_pc = crate::types::Piece::make(us, PieceType::Knight);
for to in (check_target & !rank12).iter() {
add_move(buffer, Move::new_drop_with_piece(PieceType::Knight, to, dropped_pc));
}
}
for pt in [
PieceType::Silver,
PieceType::Gold,
PieceType::Bishop,
PieceType::Rook,
] {
if hand.has(pt) {
let check_target = pos.check_squares(pt) & empties;
let dropped_pc = crate::types::Piece::make(us, pt);
for to in check_target.iter() {
add_move(buffer, Move::new_drop_with_piece(pt, to, dropped_pc));
}
}
}
}
fn generate_recaptures(
pos: &Position,
buffer: &mut ExtMoveBuffer,
sq: Square,
include_non_promotions: bool,
pawn_promo_mode: PromotionMode,
) {
let target = Bitboard::from_square(sq);
let targets = GenerateTargets::new(target);
generate_non_evasions_core(
pos,
buffer,
targets,
include_non_promotions,
pawn_promo_mode,
false,
);
}
pub fn generate_with_type(
pos: &Position,
gen_type: crate::movegen::GenType,
buffer: &mut ExtMoveBuffer,
recapture_sq: Option<Square>,
) -> usize {
use crate::movegen::GenType::*;
let us = pos.side_to_move();
let empties = !pos.occupied();
let enemy = pos.pieces_c(!us);
match gen_type {
NonEvasions => {
let targets = GenerateTargets::with_drop(!pos.pieces_c(us), empties);
generate_non_evasions_core(
pos,
buffer,
targets,
false,
PromotionMode::PromoteOnly,
true,
);
}
NonEvasionsAll => {
let targets = GenerateTargets::with_drop(!pos.pieces_c(us), empties);
generate_non_evasions_core(pos, buffer, targets, true, PromotionMode::Both, true);
}
Quiets => {
let targets = GenerateTargets::with_drop(empties, empties);
generate_non_evasions_core(
pos,
buffer,
targets,
false,
PromotionMode::PromoteOnly,
true,
);
}
QuietsAll => {
let targets = GenerateTargets::with_drop(empties, empties);
generate_non_evasions_core(pos, buffer, targets, true, PromotionMode::Both, true);
}
QuietsProMinus => {
let pawn_target = !enemy_field(us) & empties;
let targets = GenerateTargets {
general: empties,
pawn: pawn_target,
drop: empties,
};
generate_non_evasions_core(
pos,
buffer,
targets,
false,
PromotionMode::PromoteOnly,
true,
);
}
QuietsProMinusAll => {
let pawn_target = !enemy_field(us) & empties;
let targets = GenerateTargets {
general: empties,
pawn: pawn_target,
drop: empties,
};
generate_non_evasions_core(pos, buffer, targets, true, PromotionMode::Both, true);
}
Captures => {
let targets = GenerateTargets::new(enemy);
generate_non_evasions_core(
pos,
buffer,
targets,
false,
PromotionMode::PromoteOnly,
false,
);
}
CapturesAll => {
let targets = GenerateTargets::new(enemy);
generate_non_evasions_core(pos, buffer, targets, true, PromotionMode::Both, false);
}
CapturesProPlus => {
let pawn_target = (!pos.pieces_c(us) & enemy_field(us)) | enemy;
let targets = GenerateTargets {
general: enemy,
pawn: pawn_target,
drop: enemy,
};
generate_non_evasions_core(
pos,
buffer,
targets,
false,
PromotionMode::PromoteOnly,
false,
);
}
CapturesProPlusAll => {
let pawn_target = (!pos.pieces_c(us) & enemy_field(us)) | enemy;
let targets = GenerateTargets {
general: enemy,
pawn: pawn_target,
drop: enemy,
};
generate_non_evasions_core(pos, buffer, targets, true, PromotionMode::Both, false);
}
Recaptures => {
let sq = recapture_sq.expect("Recaptures requires a target square");
generate_recaptures(pos, buffer, sq, false, PromotionMode::PromoteOnly);
}
RecapturesAll => {
let sq = recapture_sq.expect("RecapturesAll requires a target square");
generate_recaptures(pos, buffer, sq, true, PromotionMode::Both);
}
Evasions => {
generate_evasions_with_promos(pos, buffer, false, PromotionMode::PromoteOnly);
}
EvasionsAll => {
generate_evasions_with_promos(pos, buffer, true, PromotionMode::Both);
}
Legal => {
let mut temp_buffer = ExtMoveBuffer::new();
if pos.in_check() {
generate_evasions_with_promos(
pos,
&mut temp_buffer,
false,
PromotionMode::PromoteOnly,
);
} else {
let targets = GenerateTargets::with_drop(!pos.pieces_c(us), empties);
generate_non_evasions_core(
pos,
&mut temp_buffer,
targets,
false,
PromotionMode::PromoteOnly,
true,
);
};
for ext in temp_buffer.iter() {
if pos.is_legal(ext.mv) {
buffer.push_move(ext.mv);
}
}
}
LegalAll => {
let mut temp_buffer = ExtMoveBuffer::new();
if pos.in_check() {
generate_evasions_with_promos(pos, &mut temp_buffer, true, PromotionMode::Both);
} else {
let targets = GenerateTargets::with_drop(!pos.pieces_c(us), empties);
generate_non_evasions_core(
pos,
&mut temp_buffer,
targets,
true,
PromotionMode::Both,
true,
);
};
for ext in temp_buffer.iter() {
if pos.is_legal(ext.mv) {
buffer.push_move(ext.mv);
}
}
}
Checks | ChecksAll | QuietChecks | QuietChecksAll => {
let include_non_promotions = matches!(gen_type, ChecksAll | QuietChecksAll);
let pawn_mode = if include_non_promotions {
PromotionMode::Both
} else {
PromotionMode::PromoteOnly
};
let quiet_only = matches!(gen_type, QuietChecks | QuietChecksAll);
generate_checks(pos, buffer, include_non_promotions, pawn_mode, quiet_only);
}
}
buffer.len()
}
pub fn generate_all(pos: &Position, buffer: &mut ExtMoveBuffer) -> usize {
if pos.in_check() {
generate_evasions(pos, buffer)
} else {
generate_non_evasions(pos, buffer)
}
}
pub fn generate_legal(pos: &Position, list: &mut MoveList) {
let mut buffer = ExtMoveBuffer::new();
generate_all(pos, &mut buffer);
for ext in buffer.iter() {
if pos.is_legal(ext.mv) {
list.push(ext.mv);
}
}
}
pub fn generate_legal_all(pos: &Position, list: &mut MoveList) {
let mut buffer = ExtMoveBuffer::new();
generate_with_type(pos, crate::movegen::GenType::LegalAll, &mut buffer, None);
for ext in buffer.iter() {
list.push(ext.mv);
}
}
pub fn generate_legal_with_pass(pos: &Position, list: &mut MoveList) {
generate_legal(pos, list);
if pos.can_pass() {
list.push(Move::PASS);
}
}
pub fn generate_legal_all_with_pass(pos: &Position, list: &mut MoveList) {
generate_legal_all(pos, list);
if pos.can_pass() {
list.push(Move::PASS);
}
}
#[inline]
pub fn is_legal_with_pass(pos: &Position, m: Move) -> bool {
if m.is_pass() {
return pos.can_pass();
}
pos.is_legal(m)
}
impl Position {
fn attackers_to_pawn(&self, c: Color, pawn_sq: Square) -> Bitboard {
let them = !c;
let occ = self.occupied();
let horses = self.pieces(c, PieceType::Horse);
let dragons = self.pieces(c, PieceType::Dragon);
let hd = horses | dragons;
let gold_like = self.pieces(c, PieceType::Gold)
| self.pieces(c, PieceType::ProPawn)
| self.pieces(c, PieceType::ProLance)
| self.pieces(c, PieceType::ProKnight)
| self.pieces(c, PieceType::ProSilver);
let knights = knight_effect(them, pawn_sq) & self.pieces(c, PieceType::Knight);
let silvers = silver_effect(them, pawn_sq) & (self.pieces(c, PieceType::Silver) | hd);
let golds = gold_effect(them, pawn_sq) & (gold_like | hd);
let bishops = bishop_effect(pawn_sq, occ) & (self.pieces(c, PieceType::Bishop) | horses);
let rooks = rook_effect(pawn_sq, occ) & (self.pieces(c, PieceType::Rook) | dragons);
knights | silvers | golds | bishops | rooks
}
fn legal_pawn_drop_check(&self, to: Square) -> bool {
let us = self.side_to_move();
let them = !us;
let them_king = self.king_square(them);
debug_assert!(pawn_effect(us, to).contains(them_king));
let occ_with_pawn = self.occupied() | Bitboard::from_square(to);
if (self.attackers_to_occ(to, occ_with_pawn) & self.pieces_c(us)).is_empty() {
return true;
}
let attackers = self.attackers_to_pawn(them, to);
let pinned = self.blockers_for_king(them);
let file_mask = FILE_BB[to.file().index()];
if !(attackers & (!pinned | file_mask)).is_empty() {
return true;
}
let mut escape_bb = king_effect(them_king) & !self.pieces_c(them);
escape_bb ^= Bitboard::from_square(to);
for king_to in escape_bb.iter() {
if (self.attackers_to_occ(king_to, occ_with_pawn) & self.pieces_c(us)).is_empty() {
return true; }
}
false
}
pub fn is_legal(&self, mv: Move) -> bool {
if mv.is_pass() {
return self.can_pass();
}
let us = self.side_to_move();
let king_sq = self.king_square(us);
if mv.is_drop() {
if mv.drop_piece_type() == PieceType::Pawn {
return self.is_legal_pawn_drop(mv.to());
}
return true;
}
let from = mv.from();
let to = mv.to();
let to_pc = self.piece_on(to);
if to_pc.is_some() {
if to_pc.color() == us {
return false;
}
if to_pc.piece_type() == PieceType::King {
return false;
}
}
if from == king_sq {
let occ = self.occupied() ^ Bitboard::from_square(from);
return !self.is_attacked_by(!us, to, occ);
}
let pinned = self.blockers_for_king(us);
if pinned.contains(from) {
return line_bb(king_sq, from).contains(to);
}
true
}
fn is_legal_pawn_drop(&self, to: Square) -> bool {
let us = self.side_to_move();
let them = !us;
let them_king = self.king_square(them);
let file_mask = FILE_BB[to.file().index()];
if !(self.pieces(us, PieceType::Pawn) & file_mask).is_empty() {
return false;
}
let pawn_attack = pawn_effect(us, to);
if !pawn_attack.contains(them_king) {
return true;
}
self.legal_pawn_drop_check(to)
}
fn is_attacked_by(&self, c: Color, sq: Square, occupied: Bitboard) -> bool {
let attackers = self.attackers_to_occ(sq, occupied);
!(attackers & self.pieces_c(c)).is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{File, PieceType, Rank, Square};
#[test]
fn test_generate_non_evasions_hirate() {
let mut pos = Position::new();
pos.set_hirate();
let mut buffer = ExtMoveBuffer::new();
let count = generate_non_evasions(&pos, &mut buffer);
assert!(count >= 30, "Generated {count} moves");
for ext in buffer.as_slice().iter().take(count) {
assert!(ext.mv.has_piece_info(), "生成手はpiece情報を持つ必要がある: {:?}", ext.mv);
}
}
#[test]
fn test_generate_legal_hirate() {
let mut pos = Position::new();
pos.set_hirate();
let mut list = MoveList::new();
generate_legal(&pos, &mut list);
assert_eq!(list.len(), 30, "Generated {} legal moves", list.len());
for mv in list.iter() {
assert!(mv.has_piece_info(), "合法手はpiece情報を持つ必要がある: {:?}", mv);
}
}
#[test]
fn test_pawn_drop_mask() {
let sq55 = Square::new(File::File5, Rank::Rank5);
let pawns = Bitboard::from_square(sq55);
let mask = pawn_drop_mask(Color::Black, pawns);
assert!(!mask.contains(Square::new(File::File5, Rank::Rank6)));
assert!(mask.contains(Square::new(File::File4, Rank::Rank5)));
assert!(mask.contains(Square::new(File::File6, Rank::Rank5)));
}
#[test]
fn test_enemy_field() {
let black_field = enemy_field(Color::Black);
let white_field = enemy_field(Color::White);
assert!(black_field.contains(Square::new(File::File5, Rank::Rank1)));
assert!(black_field.contains(Square::new(File::File5, Rank::Rank3)));
assert!(!black_field.contains(Square::new(File::File5, Rank::Rank4)));
assert!(white_field.contains(Square::new(File::File5, Rank::Rank7)));
assert!(white_field.contains(Square::new(File::File5, Rank::Rank9)));
assert!(!white_field.contains(Square::new(File::File5, Rank::Rank6)));
}
#[test]
fn test_pawn_drop_not_mate() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/9/9/9/9/4K4 b P 1").unwrap();
let drop_sq = Square::new(File::File5, Rank::Rank2);
let mv = Move::new_drop(PieceType::Pawn, drop_sq);
assert!(pos.is_legal(mv), "打ち歩詰めでない手は合法");
}
#[test]
fn test_pawn_drop_mate_is_illegal() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/3GN1B2/4R4/9/9/9/9/4K4 b P 1").unwrap();
let drop_sq = Square::new(File::File5, Rank::Rank2);
let mv = Move::new_drop(PieceType::Pawn, drop_sq);
assert!(!pos.is_legal(mv), "打ち歩詰め(玉の逃げ場なし)は非合法のはず");
}
#[test]
fn test_pawn_drop_is_blocked_by_nifu() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/4P4/9/9/9/9/9/4K4 b P 1").unwrap();
let drop_sq = Square::new(File::File5, Rank::Rank2);
let mv = Move::new_drop(PieceType::Pawn, drop_sq);
assert!(!pos.is_legal(mv), "同筋に歩があるので打ち歩は不可");
}
#[test]
fn test_evasion_moves_are_legal_against_adjacent_checker() {
let mut pos = Position::new();
pos.set_sfen("9/9/9/4g4/4K4/9/9/9/9 b - 1").unwrap();
assert!(pos.in_check());
let mut buffer = ExtMoveBuffer::new();
let count = generate_evasions(&pos, &mut buffer);
for ext in buffer.as_slice().iter().take(count) {
assert!(pos.is_legal(ext.mv), "王手回避の生成には自殺手を含めない: {:?}", ext.mv);
assert!(ext.mv.has_piece_info(), "王手回避手はpiece情報を持つ必要がある: {:?}", ext.mv);
}
}
#[test]
fn test_generate_checks_only_returns_check_moves() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/9/9/9/4R4/4K4 b - 1").unwrap();
let from = pos
.pieces(Color::Black, PieceType::Rook)
.iter()
.next()
.expect("先手飛が存在しない");
let mut buf = ExtMoveBuffer::new();
let count = generate_with_type(&pos, crate::movegen::GenType::ChecksAll, &mut buf, None);
assert!(count > 0);
for ext in buf.iter() {
assert_eq!(ext.mv.from(), from);
assert!(pos.gives_check(ext.mv), "非チェック手が混入: {:?}", ext.mv);
assert!(ext.mv.has_piece_info(), "王手生成手はpiece情報を持つ必要がある: {:?}", ext.mv);
}
}
#[test]
fn test_generate_recaptures_targets_only_given_square() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/4G4/4p4/9/9/9/4K4 b - 1").unwrap();
let recapture_sq =
pos.pieces(Color::White, PieceType::Pawn).iter().next().expect("白歩がない");
assert!(
pos.attackers_to_c(recapture_sq, Color::Black).is_not_empty(),
"取り返せる先手駒がない"
);
let mut buf = ExtMoveBuffer::new();
let count = generate_with_type(
&pos,
crate::movegen::GenType::Recaptures,
&mut buf,
Some(recapture_sq),
);
assert!(count > 0);
for ext in buf.iter() {
assert_eq!(ext.mv.to(), recapture_sq, "他升への手が混入: {:?}", ext.mv);
}
}
#[test]
fn test_bishop_promotion_only_in_default_mode() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/9/9/9/4B4/4K4 b - 1").unwrap();
let mut buf = ExtMoveBuffer::new();
let count = generate_with_type(&pos, crate::movegen::GenType::NonEvasions, &mut buf, None);
let from = pos
.pieces(Color::Black, PieceType::Bishop)
.iter()
.next()
.expect("角が存在しない");
let enemy = enemy_field(pos.side_to_move());
let has_non_promo = buf.as_slice()[..count].iter().any(|ext| {
ext.mv.from() == from && enemy.contains(ext.mv.to()) && !ext.mv.is_promotion()
});
assert!(!has_non_promo, "通常モードでは敵陣への角移動は成りのみのはず");
}
#[test]
fn test_bishop_promotion_and_unpromotion_in_all_mode() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/9/9/9/4B4/4K4 b - 1").unwrap();
let mut buf = ExtMoveBuffer::new();
let count =
generate_with_type(&pos, crate::movegen::GenType::NonEvasionsAll, &mut buf, None);
let from = pos
.pieces(Color::Black, PieceType::Bishop)
.iter()
.next()
.expect("角が存在しない");
let has_non_promo = buf.as_slice()[..count]
.iter()
.any(|ext| ext.mv.from() == from && !ext.mv.is_promotion());
assert!(has_non_promo, "All モードでは不成も生成する");
}
#[test]
fn test_generate_legal_all_includes_bishop_non_promote() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/9/9/9/4B4/4K4 b - 1").unwrap();
let mut list = MoveList::new();
generate_legal_all(&pos, &mut list);
let from = pos
.pieces(Color::Black, PieceType::Bishop)
.iter()
.next()
.expect("角が存在しない");
let has_non_promo = list.iter().any(|m| m.from() == from && !m.is_promotion());
assert!(has_non_promo, "generate_legal_all は不成の角移動も含むべき");
}
#[test]
fn test_quiets_pro_minus_omits_pawn_promotion() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/9/4P4/9/9/4K4 b - 1").unwrap();
let mut buf = ExtMoveBuffer::new();
let count =
generate_with_type(&pos, crate::movegen::GenType::QuietsProMinus, &mut buf, None);
let from = pos.pieces(Color::Black, PieceType::Pawn).iter().next().expect("歩が存在しない");
let to = pawn_effect(Color::Black, from).iter().next().expect("歩の利きがない");
let moves_from: Vec<Move> = buf.as_slice()[..count]
.iter()
.map(|ext| ext.mv)
.filter(|m| m.from() == from && m.to() == to)
.collect();
assert!(!moves_from.is_empty(), "対象の手が生成されていない: {moves_from:?}");
let has_non_promo = buf.as_slice()[..count]
.iter()
.any(|ext| ext.mv.from() == from && ext.mv.to() == to && !ext.mv.is_promotion());
let has_promo = buf.as_slice()[..count]
.iter()
.any(|ext| ext.mv.from() == from && ext.mv.to() == to && ext.mv.is_promotion());
assert!(has_non_promo, "不成の静かな手は生成される");
assert!(!has_promo, "QuietsProMinusでは歩の静かな成りは生成しないはず");
}
#[test]
fn test_knight_capture_3a4c_is_generated() {
let mut pos = Position::new();
pos.set_sfen(
"6n1l/2+S1k4/2lp1G2p/1np1B2b1/3PP4/1N1S3rP/1P2+pPP+p1/1p1G5/3KG2r1 w SN2L4Pgs2p 2",
)
.unwrap();
assert!(pos.in_check(), "この局面は王手がかかっている");
let mut list = MoveList::new();
generate_legal(&pos, &mut list);
let found = list.iter().any(|m| m.to_usi() == "3a4c");
assert!(found, "3a4c(桂馬で金を取る手)が生成されていない");
}
#[test]
fn test_knight_to_rank3_generates_both_promote_and_non_promote() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/9/2N6/9/9/9/4K4 b - 1").unwrap();
let mut list = MoveList::new();
generate_legal(&pos, &mut list);
let has_6c_promote = list.iter().any(|m| m.to_usi() == "7e6c+");
let has_6c_non_promote = list.iter().any(|m| m.to_usi() == "7e6c");
assert!(has_6c_promote, "桂馬の成り手 7e6c+ が生成されていない");
assert!(
has_6c_non_promote,
"桂馬の不成手 7e6c が生成されていない(3段目なので不成も合法)"
);
let has_8c_promote = list.iter().any(|m| m.to_usi() == "7e8c+");
let has_8c_non_promote = list.iter().any(|m| m.to_usi() == "7e8c");
assert!(has_8c_promote, "桂馬の成り手 7e8c+ が生成されていない");
assert!(
has_8c_non_promote,
"桂馬の不成手 7e8c が生成されていない(3段目なので不成も合法)"
);
}
#[test]
fn test_knight_to_rank1_generates_only_promote() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/2N6/9/9/9/9/9/4K4 b - 1").unwrap();
let mut list = MoveList::new();
generate_legal(&pos, &mut list);
let has_6a_promote = list.iter().any(|m| m.to_usi() == "7c6a+");
let has_6a_non_promote = list.iter().any(|m| m.to_usi() == "7c6a");
assert!(has_6a_promote, "7c6a+ が生成されていない");
assert!(!has_6a_non_promote, "7c6a(不成)は生成されてはいけない(1段目は行き場がない)");
let has_8a_promote = list.iter().any(|m| m.to_usi() == "7c8a+");
let has_8a_non_promote = list.iter().any(|m| m.to_usi() == "7c8a");
assert!(has_8a_promote, "7c8a+ が生成されていない");
assert!(!has_8a_non_promote, "7c8a(不成)は生成されてはいけない(1段目は行き場がない)");
}
#[test]
fn test_knight_to_rank2_generates_only_promote() {
let mut pos = Position::new();
pos.set_sfen("4k4/9/9/2N6/9/9/9/9/4K4 b - 1").unwrap();
let mut list = MoveList::new();
generate_legal(&pos, &mut list);
let has_8b_promote = list.iter().any(|m| m.to_usi() == "7d8b+");
let has_8b_non_promote = list.iter().any(|m| m.to_usi() == "7d8b");
assert!(has_8b_promote, "7d8b+ が生成されていない");
assert!(!has_8b_non_promote, "7d8b(不成)は生成されてはいけない(2段目は行き場がない)");
let has_6b_promote = list.iter().any(|m| m.to_usi() == "7d6b+");
let has_6b_non_promote = list.iter().any(|m| m.to_usi() == "7d6b");
assert!(has_6b_promote, "7d6b+ が生成されていない");
assert!(!has_6b_non_promote, "7d6b(不成)は生成されてはいけない(2段目は行き場がない)");
}
#[test]
fn test_generate_legal_with_pass_no_pass_rights() {
let mut pos = Position::new();
pos.set_hirate();
let mut list = MoveList::new();
generate_legal_with_pass(&pos, &mut list);
assert!(!list.is_empty());
assert!(
!list.iter().any(|m| m.is_pass()),
"PASS should not be generated without pass rights"
);
}
#[test]
fn test_generate_legal_with_pass_with_pass_rights() {
let mut pos = Position::new();
pos.set_startpos_with_pass_rights(2, 2);
let mut list = MoveList::new();
generate_legal_with_pass(&pos, &mut list);
assert!(list.iter().any(|m| m.is_pass()), "PASS should be generated with pass rights");
}
#[test]
fn test_generate_legal_with_pass_in_check() {
let sfen = "4k4/4G4/9/9/9/9/9/9/4K4 w - 1";
let mut pos = Position::new();
pos.set_sfen_with_pass_rights(sfen, 2, 2).unwrap();
assert!(pos.in_check());
assert!(!pos.can_pass());
let mut list = MoveList::new();
generate_legal_with_pass(&pos, &mut list);
assert!(!list.iter().any(|m| m.is_pass()), "PASS should not be generated when in check");
}
#[test]
fn test_is_legal_with_pass_normal_move() {
let mut pos = Position::new();
pos.set_hirate();
let mv = Move::from_usi("7g7f").unwrap();
assert!(is_legal_with_pass(&pos, mv));
assert_eq!(is_legal_with_pass(&pos, mv), pos.is_legal(mv));
}
#[test]
fn test_is_legal_with_pass_pass_move() {
let mut pos = Position::new();
pos.set_hirate();
assert!(!is_legal_with_pass(&pos, Move::PASS));
pos.set_startpos_with_pass_rights(2, 2);
assert!(is_legal_with_pass(&pos, Move::PASS));
let sfen = "4k4/4G4/9/9/9/9/9/9/4K4 w - 1";
pos.set_sfen_with_pass_rights(sfen, 2, 2).unwrap();
assert!(!is_legal_with_pass(&pos, Move::PASS));
}
#[test]
fn test_generate_legal_with_pass_count() {
let mut pos = Position::new();
pos.set_hirate();
let mut list_without_pass = MoveList::new();
generate_legal(&pos, &mut list_without_pass);
pos.set_startpos_with_pass_rights(2, 2);
let mut list_with_pass = MoveList::new();
generate_legal_with_pass(&pos, &mut list_with_pass);
assert_eq!(
list_with_pass.len(),
list_without_pass.len() + 1,
"With pass rights, legal move count should increase by 1"
);
}
#[test]
fn test_generate_checks_set_matches_filter() {
use std::collections::HashSet;
let sfens = [
"lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1",
"lnsgkgsnl/1r5b1/ppppppppp/9/9/2P6/PP1PPPPPP/1B5R1/LNSGKGSNL w - 2",
"ln1gk2nl/1rs1g2b1/pppppp1pp/6p2/9/2P1P4/PP1P1PPPP/1B2G2R1/LNS1KGSNL b - 1",
"4k4/9/9/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b GS 1",
"4k4/4r4/4S4/9/9/9/9/9/4K4 b - 1",
];
for sfen in &sfens {
let mut pos = Position::new();
pos.set_sfen(sfen).unwrap();
if pos.in_check() {
continue;
}
for quiet_only in [true, false] {
for include_non_promo in [true, false] {
let pawn_mode = if include_non_promo {
PromotionMode::Both
} else {
PromotionMode::PromoteOnly
};
let mut buf_new = ExtMoveBuffer::new();
generate_checks(&pos, &mut buf_new, include_non_promo, pawn_mode, quiet_only);
let mut buf_old = ExtMoveBuffer::new();
{
let us = pos.side_to_move();
let empties = !pos.occupied();
let targets = if quiet_only {
GenerateTargets::with_drop(empties, empties)
} else {
GenerateTargets::with_drop(!pos.pieces_c(us), empties)
};
let mut temp = ExtMoveBuffer::new();
generate_non_evasions_core(
&pos,
&mut temp,
targets,
include_non_promo,
pawn_mode,
true,
);
for ext in temp.iter() {
if quiet_only && pos.is_capture(ext.mv) {
continue;
}
if pos.gives_check(ext.mv) {
buf_old.push_move(ext.mv);
}
}
}
let set_new: HashSet<u16> =
buf_new.as_slice().iter().map(|e| e.mv.raw()).collect();
let set_old: HashSet<u16> =
buf_old.as_slice().iter().map(|e| e.mv.raw()).collect();
let missing: Vec<_> = set_old.difference(&set_new).collect();
let extra: Vec<_> = set_new.difference(&set_old).collect();
assert!(
missing.is_empty() && extra.is_empty(),
"sfen={sfen} quiet_only={quiet_only} include_non_promo={include_non_promo}\n\
missing={missing:?} extra={extra:?}\n\
new_count={} old_count={}",
buf_new.len(),
buf_old.len()
);
}
}
}
}
}