From 78968f1b0621815325fb442c3d77df9ce2e3c4c8 Mon Sep 17 00:00:00 2001 From: na2hiro Date: Thu, 14 Sep 2023 16:00:33 +0900 Subject: [PATCH 1/5] Add comments and tests to improve coverage --- src/bitboard.rs | 75 +++++++++++++++++++++++++++++++++++++++----- src/bitboard/core.rs | 14 +++++++++ src/movegen.rs | 39 ++++++++++++++++++++--- src/position.rs | 19 ++++++++--- src/tables.rs | 5 +++ 5 files changed, 136 insertions(+), 16 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index fe1fe6e..d8c905e 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -2,12 +2,19 @@ pub(crate) trait Occupied where Self: Sized, { + /// Shift left (South) fn shl(&self) -> Self; + /// Shift right (North) fn shr(&self) -> Self; + /// Slide consecutively to the positive: that is South. fn sliding_positive_consecutive(&self, mask: &Self) -> Self; + /// Slide consecutively to the negative: that is North. fn sliding_negative_consecutive(&self, mask: &Self) -> Self; + /// Slide for 2 directions to the positive. Positive is further West, or further South if it's on the same file. fn sliding_positives(&self, masks: &[Self; 2]) -> Self; + /// Slide for 2 directions to the negative. Negative is further East, or further North if it's on the same file. fn sliding_negatives(&self, masks: &[Self; 2]) -> Self; + /// Vacant files fn vacant_files(&self) -> Self; } @@ -154,25 +161,77 @@ mod tests { #[test] fn sliding_positives() { - let bb = Bitboard::single(SQ_8C) | Bitboard::single(SQ_8G); + // Imagine there's a bishop at 6E + let bb = to_bb(vec![SQ_8C, SQ_8G]); assert_eq!( - bb | Bitboard::single(SQ_7D) | Bitboard::single(SQ_7F), + bb | to_bb(vec![SQ_7D, SQ_7F]), bb.sliding_positives(&[ - Bitboard::single(SQ_7D) | Bitboard::single(SQ_8C) | Bitboard::single(SQ_9B), - Bitboard::single(SQ_7F) | Bitboard::single(SQ_8G) | Bitboard::single(SQ_9H), + to_bb(vec![SQ_7D, SQ_8C, SQ_9B]), + to_bb(vec![SQ_7F, SQ_8G, SQ_9H]), + ]) + ); + + // Imagine there's a rook at 6F + let bb = to_bb(vec![SQ_6H, SQ_8F]); + assert_eq!( + bb | to_bb(vec![SQ_6G, SQ_7F]), + bb.sliding_positives(&[ + to_bb(vec![SQ_6G, SQ_6H,SQ_6I]), + to_bb(vec![SQ_7F, SQ_8F, SQ_9F]), ]) ); } #[test] fn sliding_negatives() { - let bb = Bitboard::single(SQ_2C) | Bitboard::single(SQ_2G); + // Imagine there's a bishop at 4E + let bb = to_bb(vec![SQ_2C, SQ_2G]); + assert_eq!( + bb | to_bb(vec![SQ_3D, SQ_3F]), + bb.sliding_negatives(&[ + to_bb(vec![SQ_3D, SQ_2C, SQ_1B]), + to_bb(vec![SQ_3F, SQ_2G, SQ_1H]), + ]) + ); + // Imagine there's a rook at 4D + let bb = to_bb(vec![SQ_2D, SQ_4B]); assert_eq!( - bb | Bitboard::single(SQ_3D) | Bitboard::single(SQ_3F), + bb | to_bb(vec![SQ_3D, SQ_4C]), bb.sliding_negatives(&[ - Bitboard::single(SQ_3D) | Bitboard::single(SQ_2C) | Bitboard::single(SQ_1B), - Bitboard::single(SQ_3F) | Bitboard::single(SQ_2G) | Bitboard::single(SQ_1H), + to_bb(vec![SQ_3D, SQ_2D, SQ_1D]), + to_bb(vec![SQ_4C, SQ_4B, SQ_4A]), ]) ); } + + #[test] + fn vacant_files() { + assert_eq!( + !Bitboard::empty(), + Bitboard::empty().vacant_files(), + ); + let all_files = to_bb(vec![SQ_1A, SQ_2B, SQ_3C, SQ_4D, SQ_5E, SQ_6F, SQ_7G, SQ_8H, SQ_9I]).vacant_files(); + assert_eq!( + Bitboard::empty(), + all_files + ); + + let odd_files = to_bb(vec![SQ_1A, SQ_3A, SQ_5A, SQ_7A, SQ_9A]).vacant_files(); + let odd_files2 = to_bb(vec![SQ_1I, SQ_3I, SQ_5I, SQ_7I, SQ_9I]).vacant_files(); + assert_eq!(odd_files, odd_files2); + + let even_files = to_bb(vec![SQ_2A, SQ_4A, SQ_6A, SQ_8A]).vacant_files(); + assert_eq!( + Bitboard::empty(), + odd_files & even_files, + ); + assert_eq!( + !Bitboard::empty(), + odd_files | even_files, + ); + } + + fn to_bb(squares: Vec) -> Bitboard { + squares.iter().fold(Bitboard::empty(), |acc, e| (acc | Bitboard::single(*e))) + } } diff --git a/src/bitboard/core.rs b/src/bitboard/core.rs index 7378585..6c1d4a5 100644 --- a/src/bitboard/core.rs +++ b/src/bitboard/core.rs @@ -2,6 +2,7 @@ use super::Occupied; pub(crate) use shogi_core::Bitboard; use shogi_core::Square; +/// Note the alignment of the bitboard: 18 bits and 63 bits out of 2 64-bit int are used const VACANT_MASK_VALUE: u128 = 0x0002_0100_4020_1008_0402_0100; const VACANT_MASK: Bitboard = unsafe { Bitboard::from_u128_unchecked(VACANT_MASK_VALUE) }; const BB_1A: Bitboard = Bitboard::single(Square::SQ_1A); @@ -18,12 +19,20 @@ const MASKED_BBS: [Bitboard; Square::NUM + 2] = { bbs }; +/// # Arguments +/// +/// * `bb` - The occupied bitboard +/// * `mask` - The potential attacks #[inline(always)] fn sliding_positive(bb: &Bitboard, mask: &Bitboard) -> Bitboard { let tz = (*bb & mask | BB_9I).to_u128().trailing_zeros(); *mask & MASKED_BBS[tz as usize + 1] } +/// # Arguments +/// +/// * `bb` - The occupied bitboard +/// * `mask` - The potential attacks #[inline(always)] fn sliding_negative(bb: &Bitboard, mask: &Bitboard) -> Bitboard { let lz = (*bb & mask | BB_1A).to_u128().leading_zeros(); @@ -57,6 +66,11 @@ impl Occupied for Bitboard { } #[inline(always)] fn vacant_files(&self) -> Self { + // Following happens in parallel for each file: + // 1. The highest bit of (0b100000000 - self) is 1 iff the file is vacant thanks to borrowing. + // 2. Shift it by 8 bit to get the flag. Results in either 0b000000000 or 0b000000001 + // 3. 0b100000000 - the value from 2. Results in either 0b100000000 or 0b011111111 + // 4. XOR with 0b100000000. Results in either 0b000000000 or 0b111111111 let bb = unsafe { Self::from_u128_unchecked(VACANT_MASK_VALUE - self.to_u128()) }; VACANT_MASK ^ unsafe { Self::from_u128_unchecked(VACANT_MASK_VALUE - bb.shift_up(8).to_u128()) } diff --git a/src/movegen.rs b/src/movegen.rs index 56a62df..132cf52 100644 --- a/src/movegen.rs +++ b/src/movegen.rs @@ -25,6 +25,7 @@ impl Position { } av } + /// Generate moves. fn generate_all(&self, av: &mut ArrayVec) { let target = !self.player_bitboard(self.side_to_move()); self.generate_for_fu(av, &target); @@ -39,6 +40,7 @@ impl Position { self.generate_for_ry(av, &target); self.generate_drop(av, &(!self.occupied_bitboard() & !Bitboard::empty())); } + /// Generate moves to evade check, optimized using AttackInfo. fn generate_evasions(&self, av: &mut ArrayVec) { let c = self.side_to_move(); if let Some(king) = self.king_position(c) { @@ -231,6 +233,7 @@ impl Position { } } } + // Generate moves of pieces which moves like KI fn generate_for_ki(&self, av: &mut ArrayVec, target: &Bitboard) { let c = self.side_to_move(); for from in (self.piece_kind_bitboard(PieceKind::Gold) @@ -322,6 +325,7 @@ impl Position { } } } + // Checks if the move isn't illegal: king's suicidal moves and moving pinned piece away. fn is_legal(&self, m: Move) -> bool { if let Some(from) = m.from() { let c = self.side_to_move(); @@ -390,6 +394,7 @@ impl Position { | (ATTACK_TABLE.ki.attack(to, opp) & (self.piece_kind_bitboard(PieceKind::Gold) | self.piece_kind_bitboard(PieceKind::ProPawn) | self.piece_kind_bitboard(PieceKind::ProLance) | self.piece_kind_bitboard(PieceKind::ProKnight) | self.piece_kind_bitboard(PieceKind::ProSilver) | self.piece_kind_bitboard(PieceKind::ProBishop) | self.piece_kind_bitboard(PieceKind::King))) ) & self.player_bitboard(c) } + /// Attackers except for king, lance & pawn, which are not applicable to evade check by pawn #[rustfmt::skip] fn attackers_to_except_klp(&self, c: Color, to: Square) -> Bitboard { let opp = c.flip(); @@ -405,6 +410,7 @@ impl Position { #[cfg(test)] mod tests { + use shogi_core::consts::square::SQ_2I; use super::*; use shogi_core::PartialPosition; use shogi_usi_parser::FromUsi; @@ -433,7 +439,7 @@ mod tests { PartialPosition::from_usi( "sfen lnsgkg1nl/1r5s1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/7R1/LNSGKGSNL b Bb 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ); assert_eq!( 43, @@ -466,6 +472,31 @@ mod tests { assert_eq!(593, pos.legal_moves().len()); } + #[test] + fn evasion_moves() { + // TODO: add more cases + // Behind RY + // P1 * * * * * * * * * + // P2 * * * * * * * * * + // P3 * * * * * * * * * + // P4 * * * * * * * * * + // P5 * * * * * * * * * + // P6 * * * * * * * -FU * + // P7 * * * * * * * -RY * + // P8 * * * * * * +OU+KE * + // P9 * * * * -OU * +GI * * + // P+00FU + // P-00AL + // + + let pos = Position::new( + PartialPosition::from_usi("sfen 9/9/9/9/9/7p1/7+r1/6KN1/4k1S2 b Pr2b4g3s3n4l16p 1") + .expect("failed to parse"), + ); + let moves = pos.legal_moves(); + assert_eq!(1, moves.len()); + assert_eq!(SQ_2I, moves[0].to()); + } + #[test] fn pawn_drop() { { @@ -485,7 +516,7 @@ mod tests { PartialPosition::from_usi( "sfen lnsgkgsnl/1r5s1/pppppppp1/9/8L/9/PPPPPPPP1/1B5S1/LNSGKGSN1 w Pp 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ); let drop_moves = pos .legal_moves() @@ -518,7 +549,7 @@ mod tests { PartialPosition::from_usi( "sfen lnsgkgsn1/1r5s1/pppppppp1/9/8l/9/PPPPPPPP1/1B5S1/LNSGKGSN1 b Ppl 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ); let drop_moves = pos .legal_moves() @@ -642,7 +673,7 @@ mod tests { PartialPosition::from_usi( "sfen 6B2/7np/8k/7P1/7G1/9/9/9/9 b P2rb3g4s3n4l15p 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ), Square::SQ_1D, true, diff --git a/src/position.rs b/src/position.rs index 4272ace..cfb4a5d 100644 --- a/src/position.rs +++ b/src/position.rs @@ -3,10 +3,11 @@ use crate::tables::{ATTACK_TABLE, BETWEEN_TABLE}; use crate::zobrist::{Key, ZOBRIST_TABLE}; use shogi_core::{Color, Hand, Move, Piece, PieceKind, Square}; -/// Represents a state of the game. +/// Represents a state of the game with history. This provides an ability to do and undo moves. #[derive(Debug, Clone)] pub struct Position { inner: PartialPosition, + /// History of the positions states: Vec, } @@ -270,6 +271,7 @@ impl Default for Position { } } +/// Represents a state of a single position of a game. #[derive(Clone, Debug)] pub(crate) struct PartialPosition { side: Color, @@ -346,17 +348,24 @@ impl From for PartialPosition { #[derive(Debug, Clone)] struct State { + /// Zobrist hashes for (board ^ side, hand) + /// TODO: It seems it's always used by XOR-ing two keys. Can we only store a single XOR-ed key? keys: (Key, Key), + /// Captured piece in the last move captured: Option, + /// Last moved piece last_moved: Option, attack_info: AttackInfo, } #[derive(Debug, Clone)] struct AttackInfo { - checkers: Bitboard, // 王手をかけている駒の位置 - checkables: [Bitboard; PieceKind::NUM], // 各駒種が王手になり得る位置 - pinned: [Bitboard; Color::NUM], // 飛び駒から玉を守っている駒の位置 + /// 手番側の王に対して王手をかけている相手駒の位置 + checkers: Bitboard, + /// 各駒種が王手になり得る位置 + checkables: [Bitboard; PieceKind::NUM], + /// Color番目の玉を飛び駒から守っている駒(Color問わず)の位置 + pinned: [Bitboard; Color::NUM], } impl AttackInfo { @@ -421,7 +430,9 @@ impl AttackInfo { ( (ATTACK_TABLE.fu.attack(sq, c) & pos.piece_bb[PieceKind::Pawn.array_index()]) | (ATTACK_TABLE.ky.attack(sq, c, &occ) & pos.piece_bb[PieceKind::Lance.array_index()]) | (ATTACK_TABLE.ke.attack(sq, c) & pos.piece_bb[PieceKind::Knight.array_index()]) + // Delta of ProRook (龍) is a superposition of GI and HI | (ATTACK_TABLE.gi.attack(sq, c) & (pos.piece_bb[PieceKind::Silver.array_index()] | pos.piece_bb[PieceKind::ProRook.array_index()])) + // Delta of ProBishop (馬) is a superposition of KA and KI | (ATTACK_TABLE.ka.attack(sq, &occ) & (pos.piece_bb[PieceKind::Bishop.array_index()] | pos.piece_bb[PieceKind::ProBishop.array_index()])) | (ATTACK_TABLE.hi.attack(sq, &occ) & (pos.piece_bb[PieceKind::Rook.array_index()] | pos.piece_bb[PieceKind::ProRook.array_index()])) | (ATTACK_TABLE.ki.attack(sq, c) & (pos.piece_bb[PieceKind::Gold.array_index()] | pos.piece_bb[PieceKind::ProPawn.array_index()] | pos.piece_bb[PieceKind::ProLance.array_index()] | pos.piece_bb[PieceKind::ProKnight.array_index()] | pos.piece_bb[PieceKind::ProSilver.array_index()] | pos.piece_bb[PieceKind::ProBishop.array_index()])) diff --git a/src/tables.rs b/src/tables.rs index a2b3998..2e01d2d 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -58,6 +58,7 @@ impl PieceAttackTable { } } +/// Sliding attack, potentially including a square occupied by a friend. fn sliding_attack(sq: Square, occ: Bitboard, delta: Delta) -> Bitboard { let mut bb = Bitboard::empty(); let mut curr = sq.shift(delta.file, delta.rank); @@ -90,6 +91,7 @@ impl LanceAttackTable { } Self { masks } } + /// Attack as if there were nothing else on the board. #[inline(always)] fn pseudo_attack(&self, sq: Square, c: Color) -> Bitboard { self.masks[sq.array_index()][c.array_index()] @@ -128,6 +130,7 @@ impl SlidingAttackTable { merged_masks, } } + /// Attack as if there were nothing else on the board. #[inline(always)] fn pseudo_attack(&self, sq: Square) -> Bitboard { self.merged_masks[sq.array_index()] @@ -169,6 +172,7 @@ impl AttackTable { PieceKind::ProRook => self.hi.attack(sq, occ) | self.ou.attack(sq, c), } } + /// Attack as if there were nothing else on the board. pub(crate) fn pseudo_attack(&self, pk: PieceKind, sq: Square, c: Color) -> Bitboard { match pk { PieceKind::Lance => self.ky.pseudo_attack(sq, c), @@ -190,6 +194,7 @@ pub static ATTACK_TABLE: Lazy = Lazy::new(|| AttackTable { ou: PieceAttackTable::new(&[PieceAttackTable::BOU_DELTAS, PieceAttackTable::WOU_DELTAS]), }); +/// A table of the squares between two squares. "Between" is defined only for 8 directions. pub(crate) static BETWEEN_TABLE: Lazy<[[Bitboard; Square::NUM]; Square::NUM]> = Lazy::new(|| { let mut bbs = [[Bitboard::empty(); Square::NUM]; Square::NUM]; for sq0 in Square::all() { From 92a53fcf6fe259ed3417e451013953c00e441564 Mon Sep 17 00:00:00 2001 From: na2hiro Date: Thu, 14 Sep 2023 16:22:14 +0900 Subject: [PATCH 2/5] Some refactoring and optimization --- src/movegen.rs | 87 ++++++++++++++++++++++++------------------------- src/position.rs | 52 ++++++++++++----------------- 2 files changed, 63 insertions(+), 76 deletions(-) diff --git a/src/movegen.rs b/src/movegen.rs index 132cf52..7917725 100644 --- a/src/movegen.rs +++ b/src/movegen.rs @@ -43,50 +43,49 @@ impl Position { /// Generate moves to evade check, optimized using AttackInfo. fn generate_evasions(&self, av: &mut ArrayVec) { let c = self.side_to_move(); - if let Some(king) = self.king_position(c) { - let mut checkers_attacks = Bitboard::empty(); - let mut checkers_count = 0; - for ch in self.checkers() { - if let Some(p) = self.piece_at(ch) { - 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() { + // 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, target: &Bitboard) { @@ -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; } diff --git a/src/position.rs b/src/position.rs index cfb4a5d..8df3943 100644 --- a/src/position.rs +++ b/src/position.rs @@ -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) } 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(); + 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 } @@ -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( @@ -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, @@ -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); @@ -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(); } @@ -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); + 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, @@ -409,8 +397,8 @@ impl AttackInfo { ki, ki, ki, - ka | ou, - hi | ou, + ka | ki, + hi | gi, ], pinned, } From 2ed61bc70bc9e86852ab68ba1fe2b4e8f525d1a8 Mon Sep 17 00:00:00 2001 From: sugyan Date: Wed, 11 Oct 2023 23:47:48 +0900 Subject: [PATCH 3/5] Apply cargo fmt --- src/bitboard.rs | 31 ++++++++++++------------------- src/movegen.rs | 23 +++++++++-------------- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index d8c905e..2b98c22 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -176,7 +176,7 @@ mod tests { assert_eq!( bb | to_bb(vec![SQ_6G, SQ_7F]), bb.sliding_positives(&[ - to_bb(vec![SQ_6G, SQ_6H,SQ_6I]), + to_bb(vec![SQ_6G, SQ_6H, SQ_6I]), to_bb(vec![SQ_7F, SQ_8F, SQ_9F]), ]) ); @@ -206,32 +206,25 @@ mod tests { #[test] fn vacant_files() { - assert_eq!( - !Bitboard::empty(), - Bitboard::empty().vacant_files(), - ); - let all_files = to_bb(vec![SQ_1A, SQ_2B, SQ_3C, SQ_4D, SQ_5E, SQ_6F, SQ_7G, SQ_8H, SQ_9I]).vacant_files(); - assert_eq!( - Bitboard::empty(), - all_files - ); + assert_eq!(!Bitboard::empty(), Bitboard::empty().vacant_files()); + let all_files = to_bb(vec![ + SQ_1A, SQ_2B, SQ_3C, SQ_4D, SQ_5E, SQ_6F, SQ_7G, SQ_8H, SQ_9I, + ]) + .vacant_files(); + assert_eq!(Bitboard::empty(), all_files); let odd_files = to_bb(vec![SQ_1A, SQ_3A, SQ_5A, SQ_7A, SQ_9A]).vacant_files(); let odd_files2 = to_bb(vec![SQ_1I, SQ_3I, SQ_5I, SQ_7I, SQ_9I]).vacant_files(); assert_eq!(odd_files, odd_files2); let even_files = to_bb(vec![SQ_2A, SQ_4A, SQ_6A, SQ_8A]).vacant_files(); - assert_eq!( - Bitboard::empty(), - odd_files & even_files, - ); - assert_eq!( - !Bitboard::empty(), - odd_files | even_files, - ); + assert_eq!(Bitboard::empty(), odd_files & even_files); + assert_eq!(!Bitboard::empty(), odd_files | even_files); } fn to_bb(squares: Vec) -> Bitboard { - squares.iter().fold(Bitboard::empty(), |acc, e| (acc | Bitboard::single(*e))) + squares + .iter() + .fold(Bitboard::empty(), |acc, e| (acc | Bitboard::single(*e))) } } diff --git a/src/movegen.rs b/src/movegen.rs index 7917725..9c36278 100644 --- a/src/movegen.rs +++ b/src/movegen.rs @@ -49,18 +49,14 @@ impl Position { 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() - { + 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 - { + for to in ATTACK_TABLE.ou.attack(king, c) & !self.player_bitboard(c) & !checkers_attacks { av.push(Move::Normal { from: king, to, @@ -332,8 +328,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; } @@ -409,7 +405,6 @@ impl Position { #[cfg(test)] mod tests { - use shogi_core::consts::square::SQ_2I; use super::*; use shogi_core::PartialPosition; use shogi_usi_parser::FromUsi; @@ -438,7 +433,7 @@ mod tests { PartialPosition::from_usi( "sfen lnsgkg1nl/1r5s1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/7R1/LNSGKGSNL b Bb 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ); assert_eq!( 43, @@ -493,7 +488,7 @@ mod tests { ); let moves = pos.legal_moves(); assert_eq!(1, moves.len()); - assert_eq!(SQ_2I, moves[0].to()); + assert_eq!(Square::SQ_2I, moves[0].to()); } #[test] @@ -515,7 +510,7 @@ mod tests { PartialPosition::from_usi( "sfen lnsgkgsnl/1r5s1/pppppppp1/9/8L/9/PPPPPPPP1/1B5S1/LNSGKGSN1 w Pp 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ); let drop_moves = pos .legal_moves() @@ -548,7 +543,7 @@ mod tests { PartialPosition::from_usi( "sfen lnsgkgsn1/1r5s1/pppppppp1/9/8l/9/PPPPPPPP1/1B5S1/LNSGKGSN1 b Ppl 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ); let drop_moves = pos .legal_moves() @@ -672,7 +667,7 @@ mod tests { PartialPosition::from_usi( "sfen 6B2/7np/8k/7P1/7G1/9/9/9/9 b P2rb3g4s3n4l15p 1", ) - .expect("failed to parse"), + .expect("failed to parse"), ), Square::SQ_1D, true, From 10f783ca0912c475b991bfa4dc0d6fb71c02fbd2 Mon Sep 17 00:00:00 2001 From: na2hiro Date: Sat, 14 Oct 2023 06:41:01 +0900 Subject: [PATCH 4/5] Fix grammar issues in comment Co-authored-by: Hiroki Kobayashi <3303362+koba-e964@users.noreply.github.com> --- src/position.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/position.rs b/src/position.rs index 8df3943..e567e80 100644 --- a/src/position.rs +++ b/src/position.rs @@ -3,7 +3,7 @@ use crate::tables::{ATTACK_TABLE, BETWEEN_TABLE}; use crate::zobrist::{Key, ZOBRIST_TABLE}; use shogi_core::{Color, Hand, Move, Piece, PieceKind, Square}; -/// Represents a state of the game with history. This provides an ability to do and undo moves. +/// Represents a state of the game with history. This provides the ability to do and undo moves. #[derive(Debug, Clone)] pub struct Position { inner: PartialPosition, @@ -339,7 +339,7 @@ struct State { /// Zobrist hashes for (board ^ side, hand) /// TODO: It seems it's always used by XOR-ing two keys. Can we only store a single XOR-ed key? keys: (Key, Key), - /// Captured piece in the last move + /// Piece captured on the last move captured: Option, /// Last moved piece last_moved: Option, From 5776c780a6bc8b32d5df2d2f2041852cbb2a328d Mon Sep 17 00:00:00 2001 From: na2hiro Date: Sat, 14 Oct 2023 06:42:47 +0900 Subject: [PATCH 5/5] Remove resolved TODO comment --- src/position.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/position.rs b/src/position.rs index e567e80..be77b16 100644 --- a/src/position.rs +++ b/src/position.rs @@ -337,7 +337,6 @@ impl From for PartialPosition { #[derive(Debug, Clone)] struct State { /// Zobrist hashes for (board ^ side, hand) - /// TODO: It seems it's always used by XOR-ing two keys. Can we only store a single XOR-ed key? keys: (Key, Key), /// Piece captured on the last move captured: Option,