### Position::startpos Source: https://docs.rs/shunsai/latest/shunsai/struct.Position.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Returns the standard starting position for a shogi game. ```APIDOC ## Position::startpos ### Description Returns the initial position. ### Method Function ### Signature ```rust pub fn startpos() -> Self ``` ### Returns - `Position` - The initial position. ``` -------------------------------- ### Generate legal moves with Position::legal_moves and Position::generate_moves Source: https://docs.rs/shunsai/latest/src/shunsai/lib.rs.html?search= Two usage examples from the crate-level documentation. The first shows the convenient `Position::legal_moves` method returning a vector of fully legal moves; the second shows the fast `Position::generate_moves` callback API that yields a `MoveSet` per origin and allows early termination via `ControlFlow::Break`. Both examples start from `Position::startpos()` and assert the initial move count is 30. ```rust use shunsai::Position; let position = Position::startpos(); assert_eq!(position.legal_moves().len(), 30); ``` ```rust use core::ops::ControlFlow; use shunsai::Position; let position = Position::startpos(); let mut moves = 0; let _ = position.generate_moves(|set| { moves += set.len(); ControlFlow::Continue(()) }); assert_eq!(moves, 30); ``` -------------------------------- ### Get legal moves with Position::legal_moves Source: https://docs.rs/shunsai/latest/shunsai/index.html?search= Use this snippet to get all legal moves from the starting position. It returns a Vec of moves, so it is convenient when you need to iterate or filter. The example asserts that the starting position has 30 legal moves. ```rust use shunsai::Position; let position = Position::startpos(); assert_eq!(position.legal_moves().len(), 30); ``` -------------------------------- ### Shogi move generation: CheckInfo, generate_drops, is_pawn_drop_mate, and tests Source: https://docs.rs/shunsai/latest/src/shunsai/movegen.rs.html?search=std%3A%3Avec Move-generation internals for a shogi engine: building the CheckInfo result, generating legal drops (including pawn-drop mate detection), and a helper that tests whether a pawn drop is mate. The test module asserts the start position has 30 legal moves and lists corpus positions used for liveness coverage. ```rust checkers |= steps & their; CheckInfo { checkers, // Only our own pieces are pinned; one of theirs in the way is their // discovered-check opportunity, not our problem. pinned: pinned & position.player_bb(us), } ``` ```rust fn generate_drops( position: &Position, us: Color, occupied: Bitboard, king: Option, checkers: Bitboard, listener: &mut impl FnMut(MoveSet) -> ControlFlow<()>, ) -> ControlFlow<()> { let hand = position.hand(us); if hand == Hand::new() { return ControlFlow::Continue(()); } let targets = match checkers.count() { 0 => !occupied, // A drop can only resolve a single check by blocking it. Blocking // never exposes the king, so no further test is needed. 1 => { let king = king.expect("in check without a king"); let mut checkers = checkers; let checker = checkers.next().unwrap(); tables::between(king, checker) & !occupied } _ => return ControlFlow::Continue(()), }; if targets.is_empty() { return ControlFlow::Continue(()); } let has_pawn = hand.count(PieceKind::Pawn).unwrap_or(0) > 0; // Files that already contain one of our unpromoted pawns (nifu). let pawn_files = if has_pawn { let our_pawns = position.piece_kind_bb(PieceKind::Pawn) & position.player_bb(us); (1..=9).fold(Bitboard::EMPTY, |files, file| { let mask = Bitboard::file(file); if (our_pawns & mask).is_empty() { files } else { files | mask } }) } else { Bitboard::EMPTY }; let enemy_king = position.king_square(us.flip()); for piece_kind in Hand::all_hand_pieces() { if hand.count(piece_kind).unwrap_or(0) == 0 { continue; } let piece = Piece::new(piece_kind, us); // A piece may not be dropped where it could never move again — // exactly the squares that would force promotion for a board move. let mut drops = targets & !tables::forced_promotion_zone(piece); if piece_kind == PieceKind::Pawn { drops &= !pawn_files; // Only a pawn that gives check can be a pawn-drop mate, and by // the reverse-lookup trick exactly one square does that, so the // expensive simulation is reached at most once per position. if let Some(enemy_king) = enemy_king { let checking = drops & tables::pawn_attacks(us.flip(), enemy_king); if let Some(to) = checking.into_iter().next() && is_pawn_drop_mate(position, piece, to) { drops ^= Bitboard::single(to); } } } if !drops.is_empty() { listener(MoveSet::Drop { piece, to: drops })?; } } ControlFlow::Continue(()) } ``` ```rust /// Whether dropping `piece` (a checking pawn) on `to` leaves the opponent /// with no legal moves. The pawn checks from an adjacent square, so the /// opponent cannot block and no recursive pawn-drop arises. /// /// [`Position::with_drop`] rather than clone-and-`do_move`: the simulated /// position is thrown away, so it needs no undo history — and carrying one /// made this the only allocating step in move generation. fn is_pawn_drop_mate(position: &Position, piece: Piece, to: Square) -> bool { !position.with_drop(piece, to).has_legal_moves() } ``` ```rust #[cfg(test)] mod tests { use super::*; use shogi_core::PartialPosition; #[test] fn startpos_has_30_moves() { let position = Position::startpos(); assert_eq!(position.legal_moves().len(), 30); assert!(!position.in_check()); } /// Which fixture covers what, and whether anything would notice its /// removal. Four are individually held by a named liveness assertion; two /// jointly hold one; two are corpus breadth, and dropping either of those /// is silent. const CALLBACK_POSITIONS: &[&str] = &[ // Breadth: the ordinary opening shape, and the only position where // nothing can promote. "startpos", // Breadth, and the corpus's bulk — supplies almost all of the // `dropped * 8 > nodes` margin in // `king_danger_agrees_with_the_unfiltered_scan`. "l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w GR5pnsg 1", // Breadth: drop-heavy, and most of the node count. "R8/2K1S1SSk/4B4/9/9/9/9/9/1L1L1L3 b RBGSNLP3g3n17p 1", ]; } ``` -------------------------------- ### Shogi promotion and attack tables in Rust Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E These Rust functions and constants define promotion zones, forced promotion ranks, and attack dispatch for a shogi engine. They use bitboards indexed by color and square, with const tables built at compile time. The `attacks_of` function is incomplete in the source, showing only the start of its implementation. ```rust while index < 81 { let rank = rank_of(index); // Black advances toward rank 1, White toward rank 9. let relative = if color == 0 { rank } else { 10 - rank }; if relative <= max_relative_rank { bits |= 1 << index; } index += 1; } Bitboard::from_bits(bits) } ``` ```rust const fn relative_rank_masks(max_relative_rank: i8) -> [Bitboard; 2] { [ relative_rank_mask(0, max_relative_rank), relative_rank_mask(1, max_relative_rank), ] } ``` ```rust static PROMOTION_ZONE: [Bitboard; 2] = relative_rank_masks(3); static LAST_RANK: [Bitboard; 2] = relative_rank_masks(1); static LAST_TWO_RANKS: [Bitboard; 2] = relative_rank_masks(2); ``` ```rust static PROMOTION_MASK: [[Bitboard; 81]; 2] = promotion_mask_table(); ``` ```rust const fn promotion_mask_table() -> [[Bitboard; 81]; 2] { let mut table = [[Bitboard::EMPTY; 81]; 2]; let mut color = 0; while color < 2 { let zone = PROMOTION_ZONE[color]; let mut index = 0; while index < 81 { // Raw bits rather than `contains`: the builders index // `array_index` arithmetic directly, `Square::new` not being const. table[color][index] = if zone.bits() & (1 << index) != 0 { Bitboard::ALL } else { zone }; index += 1; } color += 1; } table } ``` ```rust #[inline(always)] pub(crate) fn promotion_mask(color: Color, from: Square) -> Bitboard { PROMOTION_MASK[color.array_index()][from.array_index()] } ``` ```rust pub(crate) const PROMOTABLE_KINDS: u32 = (1 << PieceKind::Pawn as u32) | (1 << PieceKind::Lance as u32) | (1 << PieceKind::Knight as u32) | (1 << PieceKind::Silver as u32) | (1 << PieceKind::Bishop as u32) | (1 << PieceKind::Rook as u32); ``` ```rust static FORCED_PROMOTION: [Bitboard; 32] = forced_promotion_table(); ``` ```rust const fn forced_promotion_table() -> [Bitboard; 32] { let mut table = [Bitboard::EMPTY; 32]; let mut color = 0; while color < 2 { let base = color * 16; table[base + PieceKind::Pawn as usize] = LAST_RANK[color]; table[base + PieceKind::Lance as usize] = LAST_RANK[color]; table[base + PieceKind::Knight as usize] = LAST_TWO_RANKS[color]; color += 1; } table } ``` ```rust #[inline(always)] pub(crate) fn forced_promotion_zone(piece: Piece) -> Bitboard { FORCED_PROMOTION[(piece.as_u8() & 31) as usize] } ``` ```rust pub(crate) use crate::sliders::active::{bishop_attacks, rook_attacks}; pub(crate) use crate::sliders::lance_attacks; ``` ```rust #[inline] pub(crate) fn attacks_of(piece: Piece, square: Square, occupied: Bitboard) -> Bitboard { let index = (piece.as_u8() & 31) as usize; } ``` -------------------------------- ### Position::startpos Source: https://docs.rs/shunsai/latest/shunsai/struct.Position.html?search=u32+-%3E+bool Returns the initial position. ```APIDOC ## startpos ### Description Returns the initial position. ### Method Function ### Signature ```rust pub fn startpos() -> Self ``` ### Return Value - **Self** - The initial position. ``` -------------------------------- ### Position::new Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search=u32+-%3E+bool Builds a Position from a PartialPosition, initializing the board, hands, side to move, ply, and Zobrist key. ```APIDOC ## Position::new ### Description Builds a `Position` from a `PartialPosition`. ### Method `pub fn new(partial: PartialPosition) -> Self` ### Parameters - **partial** (`PartialPosition`) - The partial position to initialize from. ### Returns - `Position` - The constructed position. ``` -------------------------------- ### Count moves with Position::generate_moves Source: https://docs.rs/shunsai/latest/shunsai/index.html?search= Use this snippet for fast move generation when you only need to count moves or stop early. It yields one MoveSet per origin with destinations packed as a Bitboard, so no Move is built unless you expand it. The callback can return ControlFlow::Break to stop early; the example counts 30 moves from the starting position. ```rust use core::ops::ControlFlow; use shunsai::Position; let position = Position::startpos(); let mut moves = 0; let _ = position.generate_moves(|set| { moves += set.len(); ControlFlow::Continue(()) }); assert_eq!(moves, 30); ``` -------------------------------- ### Position::startpos Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Creates the initial shogi position using PartialPosition::startpos(). ```APIDOC ## Position::startpos ### Description Creates the initial shogi position using `PartialPosition::startpos()`. ### Method `pub fn startpos() -> Self` ### Returns - **Self** - The initial `Position`. ``` -------------------------------- ### Position::ply Source: https://docs.rs/shunsai/latest/shunsai/struct.Position.html?search=u32+-%3E+bool Returns the current ply (1 at the start of a game). ```APIDOC ## ply ### Description Returns the current ply (1 at the start of a game). ### Method Function ### Signature ```rust pub fn ply(&self) -> u16 ``` ### Return Value - **u16** - The current ply. ``` -------------------------------- ### Position move/undo and piece placement methods Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search=std%3A%3Avec These methods implement the core move application and reversal logic for a shogi position. `do_move` applies a normal move or drop, updating the board, hands, side to move, zobrist key, and ply counter, returning an `Undo` record. `undo_move` reverses that move using the `Undo` value, restoring the captured piece and hand counts. `with_drop` simulates a drop on a cloned position without disturbing the original, used by move generation. `put_piece` and `remove_piece` update the board and bitboards, maintaining king squares and zobrist keys. ```rust let side = self.side_to_move; let captured = match mv { Move::Normal { to, .. } => self.piece_at(to), Move::Drop { .. } => None, }; let undo = Undo { captured, key: self.key, }; match mv { Move::Normal { from, to, promote } => { let piece = self.piece_at(from).expect("do_move: no piece to move"); debug_assert_eq!(piece.color(), side); if let Some(captured) = captured { debug_assert_ne!(captured.color(), side); self.remove_piece(to, captured); let piece_kind = captured.piece_kind(); self.add_to_hand(side, piece_kind.unpromote().unwrap_or(piece_kind)); } self.remove_piece(from, piece); let placed = if promote { piece.promote().expect("do_move: piece cannot promote") } else { piece }; self.put_piece(to, placed); } Move::Drop { piece, to } => { debug_assert_eq!(piece.color(), side); debug_assert!(self.piece_at(to).is_none()); self.remove_from_hand(side, piece.piece_kind()); self.put_piece(to, piece); } } self.side_to_move = side.flip(); self.key ^= zobrist::side_key(); self.ply = self.ply.wrapping_add(1); undo ``` ```rust pub fn undo_move(&mut self, mv: Move, undo: Undo) { let side = self.side_to_move.flip(); match mv { Move::Normal { from, to, promote } => { let placed = self.piece_at(to).expect("undo_move: no piece to put back"); self.remove_piece(to, placed); let piece = if promote { placed .unpromote() .expect("undo_move: piece was not promoted") } else { placed }; self.put_piece(from, piece); if let Some(captured) = undo.captured { let piece_kind = captured.piece_kind(); self.remove_from_hand(side, piece_kind.unpromote().unwrap_or(piece_kind)); self.put_piece(to, captured); } } Move::Drop { piece, to } => { self.remove_piece(to, piece); self.add_to_hand(side, piece.piece_kind()); } } self.side_to_move = side; self.ply = self.ply.wrapping_sub(1); // Cheaper than reversing every XOR above. self.key = undo.key; } ``` ```rust pub(crate) fn with_drop(&self, piece: Piece, to: Square) -> Self { // `position_after_drop_matches_do_move` holds this to `do_move`. let mut next = self.clone(); let side = next.side_to_move; debug_assert_eq!(piece.color(), side); debug_assert!(next.piece_at(to).is_none()); next.remove_from_hand(side, piece.piece_kind()); next.put_piece(to, piece); next.side_to_move = side.flip(); next.key ^= zobrist::side_key(); next.ply = next.ply.wrapping_add(1); next } ``` ```rust fn put_piece(&mut self, square: Square, piece: Piece) { let (piece_kind, color) = piece.to_parts(); debug_assert!(self.board[square.array_index()].is_none()); self.board[square.array_index()] = Some(piece); let single = Bitboard::single(square); self.piece_bb[piece_kind.array_index()] |= single; self.color_bb[color.array_index()] |= single; if piece_kind == PieceKind::King { self.kings[color.array_index()] = Some(square); } self.key ^= zobrist::board_key(piece, square); } ``` ```rust fn remove_piece(&mut self, square: Square, piece: Piece) { let (piece_kind, color) = piece.to_parts(); debug_assert_eq!(self.board[square.array_index()], Some(piece)); self.board[square.array_index()] = None; ``` -------------------------------- ### Position::ply Source: https://docs.rs/shunsai/latest/shunsai/struct.Position.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Returns the current ply count, starting at 1 for the initial position. ```APIDOC ## Position::ply ### Description Returns the current ply (1 at the start of a game). ### Method Function ### Signature ```rust pub fn ply(&self) -> u16 ``` ### Returns - `u16` - The current ply number. ``` -------------------------------- ### Bitboard::file Source: https://docs.rs/shunsai/latest/shunsai/struct.Bitboard.html?search=u32+-%3E+bool Creates a bitboard containing all squares of a given file. ```APIDOC ## Bitboard::file ### Description Creates a bitboard containing all squares of a given file. ### Method Associated function ### Signature `pub const fn file(file: u8) -> Self` ### Parameters - `file` (u8) - Required - The file number, must be in `1..=9`. ### Panics Panics if `file` is outside `1..=9`. The bound is load-bearing rather than cosmetic: an out-of-range file would set bits at 81 or above, and this type’s internals rely on that being impossible. ### Returns A `Bitboard` containing all squares of the specified file. ``` -------------------------------- ### Unit tests for Position Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search= Tests start position invariants and do/undo move restoration, including a bishop exchange and drop. ```rust #[cfg(test)] mod tests { use super::*; fn mv(from: (u8, u8), to: (u8, u8), promote: bool) -> Move { Move::Normal { from: Square::new(from.0, from.1).unwrap(), to: Square::new(to.0, to.1).unwrap(), promote, } } #[test] fn startpos_basics() { let position = Position::startpos(); assert_eq!(position.side_to_move(), Color::Black); assert_eq!(position.ply(), 1); assert_eq!(position.occupied().count(), 40); assert_eq!( position.piece_at(Square::new(7, 7).unwrap()), Some(Piece::new(PieceKind::Pawn, Color::Black)) ); assert_eq!( position.king_square(Color::Black), Some(Square::new(5, 9).unwrap()) ); assert_eq!( position.king_square(Color::White), Some(Square::new(5, 1).unwrap()) ); assert_eq!(position.piece_kind_bb(PieceKind::Pawn).count(), 18); } #[test] fn do_undo_restores_everything() { // 7g7f 3c3d 8h2b+ (bishop exchange) 3a2b, then B*4e. let moves = [ mv((7, 7), (7, 6), false), mv((3, 3), (3, 4), false), mv((8, 8), (2, 2), true), mv((3, 1), (2, 2), false), Move::Drop { piece: Piece::new(PieceKind::Bishop, Color::Black), to: Square::new(4, 5).unwrap(), }, ]; let mut position = Position::startpos(); let mut snapshots = vec![position.clone()]; let mut undos = Vec::new(); for &m in &moves { undos.push(position.do_move(m)); snapshots.push(position.clone()); } // The bishop exchange put a bishop in each hand; Black dropped theirs. assert_eq!( position.hand(Color::Black).count(PieceKind::Bishop), Some(0) ); assert_eq!( ``` -------------------------------- ### Test: transposition_same_key Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search= Checks that applying the same moves in different orders produces the same position key, and that the key differs from the starting position key. ```rust #[test] fn transposition_same_key() { let mut a = Position::startpos(); for m in [ mv((7, 7), (7, 6), false), mv((3, 3), (3, 4), false), mv((2, 7), (2, 6), false), ] { a.do_move(m); } let mut b = Position::startpos(); for m in [ mv((2, 7), (2, 6), false), mv((3, 3), (3, 4), false), mv((7, 7), (7, 6), false), ] { b.do_move(m); } assert_eq!(a.key(), b.key()); assert_ne!(a.key(), Position::startpos().key()); } ``` -------------------------------- ### Bitboard query methods Source: https://docs.rs/shunsai/latest/src/shunsai/bitboard.rs.html?search=u32+-%3E+bool Provides methods to check membership, emptiness, count, and file extraction. The `file` method panics if the file is outside 1..=9 to prevent setting bits above 81. ```rust /// Whether `square` is in the set. #[inline(always)] pub const fn contains(self, square: Square) -> bool { self.0 & (1 << square.array_index()) != 0 } /// Whether the set has no squares. #[inline(always)] pub const fn is_empty(self) -> bool { self.0 == 0 } /// The number of squares in the set. #[inline(always)] pub const fn count(self) -> u32 { self.0.count_ones() } /// All squares of `file`. /// /// # Panics /// /// If `file` is outside `1..=9`. The bound is load-bearing rather than /// cosmetic: an out-of-range file would set bits at 81 or above, and this /// type's internals rely on that being impossible. #[inline(always)] pub const fn file(file: u8) -> Self { assert!(1 <= file && file <= 9, "file must be in 1..=9"); Self(0x1ff << ((file - 1) * 9)) } ``` -------------------------------- ### Test promotion_mask against zone membership Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html?search= Verifies that promotion_mask returns the full board when the move starts in the promotion zone, otherwise the zone itself. Used in a test for the promotion_mask function. ```rust let zone = PROMOTION_ZONE[color.array_index()]; // Promotion is legal iff the move starts or ends in the zone. let expected = if zone.contains(from) { Bitboard::ALL } else { zone }; assert_eq!( promotion_mask(color, from), expected, "promotion_mask disagrees for {color:?} from {from:?}" ); ``` -------------------------------- ### Position unit tests (startpos_basics, do_undo_restores_everything) with mv helper Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search=u32+-%3E+bool Test helpers and unit tests for Position. The mv helper builds a normal move from coordinates; startpos_basics checks the initial position's side, ply, occupied count, piece at a square, king squares, and pawn count; do_undo_restores_everything plays a sequence including a bishop exchange and a drop, then verifies undo restores each snapshot. ```rust fn mv(from: (u8, u8), to: (u8, u8), promote: bool) -> Move { Move::Normal { from: Square::new(from.0, from.1).unwrap(), to: Square::new(to.0, to.1).unwrap(), promote, } } ``` ```rust #[test] fn startpos_basics() { let position = Position::startpos(); assert_eq!(position.side_to_move(), Color::Black); assert_eq!(position.ply(), 1); assert_eq!(position.occupied().count(), 40); assert_eq!( position.piece_at(Square::new(7, 7).unwrap()), Some(Piece::new(PieceKind::Pawn, Color::Black)) ); assert_eq!( position.king_square(Color::Black), Some(Square::new(5, 9).unwrap()) ); assert_eq!( position.king_square(Color::White), Some(Square::new(5, 1).unwrap()) ); assert_eq!(position.piece_kind_bb(PieceKind::Pawn).count(), 18); } ``` ```rust #[test] fn do_undo_restores_everything() { // 7g7f 3c3d 8h2b+ (bishop exchange) 3a2b, then B*4e. let moves = [ mv((7, 7), (7, 6), false), mv((3, 3), (3, 4), false), mv((8, 8), (2, 2), true), mv((3, 1), (2, 2), false), Move::Drop { piece: Piece::new(PieceKind::Bishop, Color::Black), to: Square::new(4, 5).unwrap(), }, ]; let mut position = Position::startpos(); let mut snapshots = vec![position.clone()]; let mut undos = Vec::new(); for &m in &moves { undos.push(position.do_move(m)); snapshots.push(position.clone()); } // The bishop exchange put a bishop in each hand; Black dropped theirs. assert_eq!( position.hand(Color::Black).count(PieceKind::Bishop), Some(0) ); assert_eq!( position.hand(Color::White).count(PieceKind::Bishop), Some(1) ); for (i, undo) in undos.into_iter().enumerate().rev() { position.undo_move(undo); assert_eq!(position, snapshots[i]); } } ``` -------------------------------- ### Test: undo_move restores startpos after move sequence Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search= Verifies that undoing a sequence of moves in reverse order returns the position to the starting position. Uses a snapshot list to compare intermediate positions. ```rust position.hand(Color::White).count(PieceKind::Bishop), Some(1) ); for &m in moves.iter().rev() { assert_eq!(&position, snapshots.last().unwrap()); snapshots.pop(); position.undo_move(m, undos.pop().unwrap()); } assert_eq!(&position, &snapshots[0]); assert_eq!(position, Position::startpos()); } ``` -------------------------------- ### forced_promotion_zone: Lookup forced promotion zone Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html Returns the forced promotion zone for a given piece by indexing the precomputed table. The piece's u8 value is masked with 31 to get the table index. ```rust #[inline(always)] pub(crate) fn forced_promotion_zone(piece: Piece) -> Bitboard { FORCED_PROMOTION[(piece.as_u8() & 31) as usize] } ``` -------------------------------- ### Reference for both step directions Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html?search=u32+-%3E+bool Builds reference tables for both a step list and its flipped counterpart (negated deltas). Returns a pair of [Bitboard; 81] tables for cross-checking. ```rust fn step_table_both_reference(steps: &[(i8, i8)]) -> [[Bitboard; 81]; 2] { let flipped: Vec<(i8, i8)> = steps .iter() .map(|&(file_delta, rank_delta)| (-file_delta, -rank_delta)) .collect(); [step_table_reference(steps), step_table_reference(&flipped)] } ``` -------------------------------- ### Default and From implementations for Position Source: https://docs.rs/shunsai/latest/src/shunsai/position.rs.html?search= Provides Default returning startpos and conversion from PartialPosition. ```rust impl Default for Position { fn default() -> Self { Self::startpos() } } impl From for Position { fn from(partial: PartialPosition) -> Self { Self::new(partial) } } ``` -------------------------------- ### DIAGONAL_DOWN_MULTIPLIERS static array Source: https://docs.rs/shunsai/latest/src/shunsai/sliders/magics.rs.html?search=u32+-%3E+bool Multipliers for the DIAGONAL_DOWN line (rank delta -1 per file step). This is a generated file; do not edit by hand. Regenerate with `cargo run --release --example gen_magics`. ```rust 70/// Multipliers for the DIAGONAL_DOWN line (rank delta -1 per file step). 71#[rustfmt::skip] 72pub(crate) static DIAGONAL_DOWN_MULTIPLIERS: [u64; 81] = [ 73 0x2202881820400040, 0x000800c410001104, 0x8108005008082008, 0x6288004004000000, ``` -------------------------------- ### forced_promotion_table: Build forced promotion ranks Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html Builds a table indexed by piece (with color offset) that gives the ranks where promotion is forced for pawns, lances, and knights. Other pieces have empty bitboards. ```rust static FORCED_PROMOTION: [Bitboard; 32] = forced_promotion_table(); const fn forced_promotion_table() -> [Bitboard; 32] { let mut table = [Bitboard::EMPTY; 32]; let mut color = 0; while color < 2 { let base = color * 16; table[base + PieceKind::Pawn as usize] = LAST_RANK[color]; table[base + PieceKind::Lance as usize] = LAST_RANK[color]; table[base + PieceKind::Knight as usize] = LAST_TWO_RANKS[color]; color += 1; } table } ``` -------------------------------- ### DIAGONAL_UP_MULTIPLIERS static array Source: https://docs.rs/shunsai/latest/src/shunsai/sliders/magics.rs.html?search=u32+-%3E+bool Multipliers for the DIAGONAL_UP line (rank delta 1 per file step). This is a generated file; do not edit by hand. Regenerate with `cargo run --release --example gen_magics`. ```rust 44/// Multipliers for the DIAGONAL_UP line (rank delta 1 per file step). 45#[rustfmt::skip] 46pub(crate) static DIAGONAL_UP_MULTIPLIERS: [u64; 81] = [ 47 0x4810010008201108, 0x1101080900090800, 0x10101801a0202127, 0x62300312000010a0, 48 0x8013424050000804, 0x8890000000110002, 0x8000280401004040, 0x0600000090006004, 49 0x0046052002108430, 0x101080c200080800, 0x1201140080800208, 0x80a241010aa20400, 50 0x202001090002e900, 0xa090080400008000, 0x8410010805840108, 0x80002160004160c0, 51 0x2900300001214100, 0x0500000800080200, 0x1102080108208880, 0x9422080088400110, 52 0x0908620040142084, 0x080544008050a081, 0x310122a100810000, 0x8900220108805818, 53 0x4124081000000020, 0x8000808004404000, 0x8100014800000422, 0x2824081100088800, 54 0x80100140809000a0, 0xc008808080810490, 0x1001101100200208, 0x1018000200489101, 55 0xc104110194820000, 0x4008080200000000, 0x4120002128501008, 0x4820240088010040, 56 0x2014080020c40008, 0x2211090010001380, 0x28001ac224410000, 0x080c02009240e042, 57 0x400c814291800242, 0x882432442081100a, 0x8891420000126002, 0xc028092080012800, 58 0x8468a44010901020, 0x4120080020000821, 0x82500800000c2040, 0x2000384108000200, 59 0x8004004880401000, 0x20a0040020100204, 0x06020404c0600828, 0xc884040090002080, 60 0x4054424208410020, 0x809a010121a00000, 0x8042500040002028, 0xc000000404025000, 61 0x440018108002a000, 0x8014c03180100000, 0x1630029085808071, 0x1008080100022a42, 62 0x060809002080020c, 0x4048109058800260, 0x8212014480400000, 0x00080810c0000052, 63 0x0001006204400400, 0x804001501400200c, 0x5028000020800004, 0x3010288020100014, 64 0x2020b32102000916, 0x82022a2540c01140, 0x0844440082800102, 0x8004040080841200, 65 0x2110144000024000, 0x020002100c080080, 0x8040100600000180, 0x4820000604000000, 66 0x400c082800000000, 0x30a0240080000500, 0x4008018852809811, 0x8004549080c02101, 67 0x2003810010091008, 68]; ``` -------------------------------- ### RANK_MULTIPLIERS static array Source: https://docs.rs/shunsai/latest/src/shunsai/sliders/magics.rs.html?search=u32+-%3E+bool Multipliers for the RANK line (rank delta 0 per file step). This is a generated file; do not edit by hand. Regenerate with `cargo run --release --example gen_magics`. ```rust 1//! Magic multipliers for the magic slider backend (the M4 default). 2//! 3//! GENERATED FILE - do not edit by hand. Regenerate from the 4//! repository with: 5//! 6//! ```sh 7//! cargo run --release --example gen_magics 8//! ``` 9//! 10//! The generator (`examples/gen_magics.rs`) brute-forces these from a fixed 11//! splitmix64 seed and verifies each candidate against every occupancy of its 12//! line before accepting it, so a rerun reproduces this file byte for byte — 13//! which CI checks on every run. These multipliers are the *only* generated 14//! part of the magic backend: `magic.rs` derives each mask and shift from the 15//! board geometry and const-evaluates the attack tables from there, so no 16//! table is ever transcribed from elsewhere. 17 18/// Multipliers for the RANK line (rank delta 0 per file step). 19#[rustfmt::skip] 20pub(crate) static RANK_MULTIPLIERS: [u64; 81] = [ 21 0x8008100242410020, 0x0440288802004140, 0x02400c0282020060, 0x80120210c4010040, 22 0x2084200800428009, 0x2820088408128010, 0x8008408102020010, 0xa810201800c08008, 23 0x0840018414008010, 0x8028880082820006, 0x4002204c01124102, 0x340810011a040000, 24 0x8004080481020052, 0x0828d00400440400, 0x5002021808010402, 0x6408621004010801, 25 0x8016420224010408, 0x8004100c16408022, 0x5001010508090100, 0x14010e100c008082, 26 0x40482010ca024040, 0x1440869014028080, 0x20803000828412c0, 0x8044828400408100, 27 0x10041a08880a0080, 0x08c86b0204240081, 0x0400602850828180, 0x2208001800c40421, 28 0x8004200805010418, 0x0440081101020080, 0x104a009022020128, 0x1034420824410200, 29 0x4030481098084044, 0x4820101011010010, 0x0408020108120080, 0x8042000502058300, 30 0x10040840041c0010, 0x040820100a020021, 0x10200b0808044181, 0x1102100816840288, 31 0x8002024881020180, 0x800414000403003a, 0x8085244000420080, 0xd0420a00040c0080, 32 0x800808040690a101, 0x4410204804284044, 0x1041501102410110, 0x200c210805240048, 33 0x4240081100006040, 0x102020010219a081, 0x184248022c0a0200, 0x25100a0810140840, 34 0x8020144132842084, 0x142002021b111600, 0x4108020088190080, 0x2120020281000200, 35 0x8004085087002100, 0x204002828c042042, 0x0410160805002200, 0x1020200100c60180, 36 0x0843040826004200, 0x3424200204101081, 0x0802080808009240, 0x102228300400c002, 37 0x084144008401040c, 0x6202200204084200, 0x0c10200218820252, 0x6240102100408840, 38 0x2082441808005002, 0x4840300082048000, 0x2040010841808103, 0x0440840400860100, 39 0x04a0048112040040, 0x100c010808001080, 0x4040008420404040, 0x2448080801204200, 40 0x8001080281004100, 0x087a040a48010300, 0x2020042040840090, 0x2004012053020200, 41 0x202008a210840090, 42]; ``` -------------------------------- ### Build step and ray attack tables Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html Builds a 81-entry attack table from a list of (file, rank) steps. `step_table` places a single bit per step; `ray_table` repeats each step to the board edge for slider attacks on an empty board. ```rust const fn step_table(steps: &[(i8, i8)]) -> [Bitboard; 81] { let mut table = [Bitboard::EMPTY; 81]; let mut index = 0; while index < 81 { let (file, rank) = (file_of(index), rank_of(index)); let mut bits = 0u128; let mut i = 0; while i < steps.len() { let (file_delta, rank_delta) = steps[i]; let (to_file, to_rank) = (file + file_delta, rank + rank_delta); if on_board(to_file, to_rank) { bits |= 1 << index_of(to_file, to_rank); } i += 1; } table[index] = Bitboard::from_bits(bits); index += 1; } table } ``` ```rust const fn ray_table(steps: &[(i8, i8)]) -> [Bitboard; 81] { let mut table = [Bitboard::EMPTY; 81]; let mut index = 0; while index < 81 { let mut bits = 0u128; let mut i = 0; while i < steps.len() { let (file_delta, rank_delta) = steps[i]; let (mut file, mut rank) = (file_of(index), rank_of(index)); loop { file += file_delta; rank += rank_delta; if !on_board(file, rank) { break; } bits |= 1 << index_of(file, rank); } i += 1; } table[index] = Bitboard::from_bits(bits); index += 1; } table } ``` -------------------------------- ### Helper function to parse SFEN positions Source: https://docs.rs/shunsai/latest/src/shunsai/movegen.rs.html?search= A Rust function that converts an SFEN string into a Position. It returns the start position for "startpos" and otherwise parses the SFEN using the shogi_usi_parser crate, panicking if parsing fails. ```rust fn position_of(sfen: &str) -> Position { if sfen == "startpos" { return Position::startpos(); } let partial = ::from_usi(&format!("sfen {sfen}")) .expect("test SFEN must parse"); Position::new(partial) } ``` -------------------------------- ### Test promotion tables match promotion rules Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html?search= Verifies promotion tables against the rules: `PROMOTABLE_KINDS` must match `PieceKind::promote()`, and `forced_promotion_zone` must match the per-kind forced ranks (last rank for Pawn/Lance, last two for Knight). ```rust for piece in Piece::all() { let (piece_kind, color) = piece.to_parts(); assert_eq!( PROMOTABLE_KINDS >> (piece.as_u8() & 15) & 1 != 0, piece_kind.promote().is_some(), "PROMOTABLE_KINDS disagrees for {piece:?}" ); let forced = match piece_kind { PieceKind::Pawn | PieceKind::Lance => LAST_RANK[color.array_index()], PieceKind::Knight => LAST_TWO_RANKS[color.array_index()], _ => Bitboard::EMPTY, }; assert_eq!( forced_promotion_zone(piece), forced, "forced_promotion_zone disagrees for {piece:?}" ); for from in Square::all() { ``` -------------------------------- ### Relative rank mask (truncated) Source: https://docs.rs/shunsai/latest/src/shunsai/tables.rs.html?search=std%3A%3Avec This function computes a bitboard mask for the ranks at relative rank 1..=max_relative_rank for the given color, i.e. the max_relative_rank ranks nearest the opponent. The snippet is truncated at the start of the loop; the full implementation is not shown on this page. ```rust const fn relative_rank_mask(color: usize, max_relative_rank: i8) -> Bitboard { let mut bits = 0u128; let mut index = 0; ``` -------------------------------- ### Test file masks for files 1..=9 Source: https://docs.rs/shunsai/latest/src/shunsai/bitboard.rs.html?search=u32+-%3E+bool Checks that Bitboard::file returns a board with exactly 9 squares, all on the requested file. Iterates over files 1 through 9. ```rust for file in 1..=9 { let bb = Bitboard::file(file); assert_eq!(bb.count(), 9); for square in bb { assert_eq!(square.file(), file); } } ```