Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add doc comments & tests. Some refactoring #28

Merged
merged 6 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 43 additions & 44 deletions src/movegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,50 +43,49 @@ impl Position {
/// Generate moves to evade check, optimized using AttackInfo.
fn generate_evasions(&self, av: &mut ArrayVec<Move, MAX_LEGAL_MOVES>) {
let c = self.side_to_move();
if let Some(king) = self.king_position(c) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unwrapped king_position() as this method should be called during evading

let mut checkers_attacks = Bitboard::empty();
let mut checkers_count = 0;
for ch in self.checkers() {
if let Some(p) = self.piece_at(ch) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unwrapped piece_at(ch) as checkers() should only return positions where pieces exist

let pk = p.piece_kind();
// 龍が斜め位置から王手している場合のみ、他の駒の裏に逃がれることができる可能性がある
if pk == PieceKind::ProRook
&& ch.file() != king.file()
&& ch.rank() != king.rank()
{
checkers_attacks |= ATTACK_TABLE.hi.attack(ch, &self.occupied_bitboard());
} else {
checkers_attacks |= ATTACK_TABLE.pseudo_attack(pk, ch, c.flip());
}
}
checkers_count += 1;
}
for to in ATTACK_TABLE.ou.attack(king, c) & !self.player_bitboard(c) & !checkers_attacks
let king = self.king_position(c).unwrap();
let mut checkers_attacks = Bitboard::empty();
let mut checkers_count = 0;
for ch in self.checkers() {
let pk = self.piece_at(ch).unwrap().piece_kind();
// 龍が斜め位置から王手している場合のみ、他の駒の裏に逃がれることができる可能性がある
if pk == PieceKind::ProRook
&& ch.file() != king.file()
&& ch.rank() != king.rank()
{
av.push(Move::Normal {
from: king,
to,
promote: false,
});
}
// 両王手の場合は玉が逃げるしかない
if checkers_count > 1 {
return;
}
if let Some(ch) = self.checkers().into_iter().next() {
let target_drop = BETWEEN_TABLE[ch.array_index()][king.array_index()];
let target_move = target_drop | self.checkers();
self.generate_for_fu(av, &target_move);
self.generate_for_ky(av, &target_move);
self.generate_for_ke(av, &target_move);
self.generate_for_gi(av, &target_move);
self.generate_for_ka(av, &target_move);
self.generate_for_hi(av, &target_move);
self.generate_for_ki(av, &target_move);
self.generate_for_um(av, &target_move);
self.generate_for_ry(av, &target_move);
self.generate_drop(av, &target_drop);
checkers_attacks |= ATTACK_TABLE.hi.attack(ch, &self.occupied_bitboard());
} else {
checkers_attacks |= ATTACK_TABLE.pseudo_attack(pk, ch, c.flip());
}
checkers_count += 1;
}
for to in ATTACK_TABLE.ou.attack(king, c) & !self.player_bitboard(c) & !checkers_attacks
{
av.push(Move::Normal {
from: king,
to,
promote: false,
});
}
// 両王手の場合は玉が逃げるしかない
if checkers_count > 1 {
return;
}
let ch = self.checkers().into_iter().next().unwrap();
let target_drop = BETWEEN_TABLE[ch.array_index()][king.array_index()];
let target_move = target_drop | self.checkers();
self.generate_for_fu(av, &target_move);
self.generate_for_ky(av, &target_move);
self.generate_for_ke(av, &target_move);
self.generate_for_gi(av, &target_move);
self.generate_for_ka(av, &target_move);
self.generate_for_hi(av, &target_move);
self.generate_for_ki(av, &target_move);
self.generate_for_um(av, &target_move);
self.generate_for_ry(av, &target_move);
if !target_drop.is_empty() {
Copy link
Contributor Author

@na2hiro na2hiro Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing generate_drop is somewhat costly/redundant in case target_drop is empty, which is fairly often like in case attacker is not 飛び駒.

// No need to exclude occupied bitboard: Existence of cells between attacker and king is given.
self.generate_drop(av, &target_drop);
}
}
fn generate_for_fu(&self, av: &mut ArrayVec<Move, MAX_LEGAL_MOVES>, target: &Bitboard) {
Expand Down Expand Up @@ -333,8 +332,8 @@ impl Position {
// 玉が相手の攻撃範囲内に動いてしまう指し手は除外
if self.piece_at(from) == Some(king)
&& !self
.attackers_to(c.flip(), m.to(), &self.occupied_bitboard())
.is_empty()
.attackers_to(c.flip(), m.to(), &self.occupied_bitboard())
.is_empty()
{
return false;
}
Expand Down
52 changes: 20 additions & 32 deletions src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,21 @@ impl Position {
match m {
Move::Normal { from, to, promote } => {
let piece = self.inner.piece_at(from).unwrap();
let p = if promote {
if let Some(p) = piece.promote() {
p
} else {
piece
}
let pk = piece.piece_kind();
let pk = if promote {
pk.promote().unwrap_or(pk)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a few refactor with unwrap_or to improve readability. Hoping if this is not harmful for performance.

} else {
piece
pk
};
if self.checkable(p.piece_kind(), to) {
if self.checkable(pk, to) {
return true;
}
// 開き王手
let c = self.inner.side;
if self.pinned(c.flip()).contains(from) {
if let Some(sq) = self.king_position(c.flip()) {
return !(BETWEEN_TABLE[sq.array_index()][from.array_index()].contains(to)
|| BETWEEN_TABLE[sq.array_index()][to.array_index()].contains(from));
}
let c = self.inner.side.flip();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When only flip side is used for multiple times, I rewrote to flipping it once than doing it in every occurrence.

if self.pinned(c).contains(from) {
let sq = self.king_position(c).unwrap();
return !(BETWEEN_TABLE[sq.array_index()][from.array_index()].contains(to)
|| BETWEEN_TABLE[sq.array_index()][to.array_index()].contains(from));
}
false
}
Expand All @@ -110,11 +106,7 @@ impl Position {
last_moved = Some(piece);
if let Some(p) = captured {
let pk = p.piece_kind();
let pk_unpromoted = if let Some(pk) = pk.unpromote() {
pk
} else {
pk
};
let pk_unpromoted = pk.unpromote().unwrap_or(pk);
// Update keys
keys.0 ^= ZOBRIST_TABLE.board(to, p);
keys.1 ^= ZOBRIST_TABLE.hand(
Expand Down Expand Up @@ -182,7 +174,7 @@ impl Position {
});
}
pub fn undo_move(&mut self, m: Move) {
let c = self.side_to_move();
let c = self.side_to_move().flip();
match m {
Move::Normal {
from,
Expand All @@ -193,13 +185,9 @@ impl Position {
let captured = self.captured();
if let Some(p_cap) = captured {
let pk = p_cap.piece_kind();
let pk_unpromoted = if let Some(pk) = pk.unpromote() {
pk
} else {
pk
};
let pk_unpromoted = pk.unpromote().unwrap_or(pk);
self.inner.xor_piece(to, p_cap);
let hand = self.inner.hand_of_a_player_mut(c.flip());
let hand = self.inner.hand_of_a_player_mut(c);
*hand = hand.removed(pk_unpromoted).unwrap();
}
self.inner.xor_piece(from, last_moved);
Expand All @@ -210,11 +198,11 @@ impl Position {
Move::Drop { to, piece } => {
self.inner.xor_piece(to, piece);
*self.inner.piece_at_mut(to) = None;
let hand = self.inner.hand_of_a_player_mut(c.flip());
let hand = self.inner.hand_of_a_player_mut(c);
*hand = hand.added(piece.piece_kind()).unwrap();
}
}
self.inner.side = c.flip();
self.inner.side = c;
self.inner.ply -= 1;
self.states.pop();
}
Expand Down Expand Up @@ -393,14 +381,14 @@ impl AttackInfo {
let ka = ATTACK_TABLE.ka.attack(sq, &occ);
let hi = ATTACK_TABLE.hi.attack(sq, &occ);
let ki = ATTACK_TABLE.ki.attack(sq, opp);
let ou = ATTACK_TABLE.ou.attack(sq, opp);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by calculate_checkers(), omit one table lookup for OU.

let gi = ATTACK_TABLE.gi.attack(sq, opp);
Self {
checkers,
checkables: [
ATTACK_TABLE.fu.attack(sq, opp),
ATTACK_TABLE.ky.attack(sq, opp, &occ),
ATTACK_TABLE.ke.attack(sq, opp),
ATTACK_TABLE.gi.attack(sq, opp),
gi,
ki,
ka,
hi,
Expand All @@ -409,8 +397,8 @@ impl AttackInfo {
ki,
ki,
ki,
ka | ou,
hi | ou,
ka | ki,
hi | gi,
],
pinned,
}
Expand Down