use std::env;
use std::fmt::Write;
use std::fs;
use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
let dest_path = Path::new(&out_dir);
generate_square_tables(dest_path);
generate_attack_tables(dest_path);
generate_policy_label_tables(dest_path);
}
fn generate_square_tables(out_dir: &Path) {
let mut output = String::new();
output.push_str(
"// Auto-generated by build.rs - DO NOT EDIT
",
);
output.push_str(
"/// マスから筋への変換テーブル (82要素: 0..81 + `SQ_NONE`)
",
);
output.push_str(
"pub const SQUARE_TO_FILE: [File; 82] = [
",
);
for sq in 0..81 {
let file = sq / 9;
if sq % 9 == 0 {
output.push_str(" ");
}
let _ = write!(output, "File::new({file}), ");
if sq % 9 == 8 {
let _ = writeln!(output, "// SQ_{}1 - SQ_{}9", 9 - file, 9 - file);
}
}
output.push_str(
" File::new(-1), // SQ_NONE
",
);
output.push_str(
"];
",
);
output.push_str(
"/// マスから段への変換テーブル (82要素: 0..81 + `SQ_NONE`)
",
);
output.push_str(
"pub const SQUARE_TO_RANK: [Rank; 82] = [
",
);
for sq in 0..81 {
let rank = sq % 9;
if sq % 9 == 0 {
output.push_str(" ");
}
let _ = write!(output, "Rank::new({rank}), ");
if sq % 9 == 8 {
let _ = writeln!(output, "// SQ_{}1 - SQ_{}9", 9 - sq / 9, 9 - sq / 9);
}
}
output.push_str(
" Rank::new(-1), // SQ_NONE
",
);
output.push_str(
"];
",
);
let path = out_dir.join("square_tables.rs");
fs::write(path, output).expect("Failed to write square_tables.rs");
}
fn generate_attack_tables(out_dir: &Path) {
let mut output = String::new();
output.push_str(
"// Auto-generated attack tables - DO NOT EDIT\n\
\n\
use crate::board::Bitboard;\n\
use crate::types::SquareTable;\n\
\n",
);
output.push_str(&generate_non_slider_attacks());
output.push_str(&generate_slider_beams());
output.push_str(&generate_rook_rank_extraction_masks());
output.push_str(&generate_bishop_diagonal_extraction_masks());
output.push_str(&generate_check_candidate_table());
let path = out_dir.join("attack_tables.rs");
fs::write(path, output).expect("Failed to write attack_tables.rs");
}
const POLICY_LABEL_SQUARES: usize = 81;
const POLICY_LABEL_CLASS_COUNT: usize = 27;
const POLICY_LABEL_COUNT: usize = POLICY_LABEL_SQUARES * POLICY_LABEL_CLASS_COUNT;
const COMPACT_POLICY_LABEL_COUNT: usize = 1496;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum PolicyPieceType {
Pawn,
Lance,
Knight,
Silver,
Bishop,
Rook,
Gold,
King,
ProPawn,
ProLance,
ProKnight,
ProSilver,
Horse,
Dragon,
}
impl PolicyPieceType {
const ALL: [Self; 14] = [
Self::Pawn,
Self::Lance,
Self::Knight,
Self::Silver,
Self::Bishop,
Self::Rook,
Self::Gold,
Self::King,
Self::ProPawn,
Self::ProLance,
Self::ProKnight,
Self::ProSilver,
Self::Horse,
Self::Dragon,
];
const fn is_promotable(self) -> bool {
matches!(
self,
Self::Pawn | Self::Lance | Self::Knight | Self::Silver | Self::Bishop | Self::Rook
)
}
}
fn generate_policy_label_tables(out_dir: &Path) {
let compact_to_move_label = compute_policy_structural_labels();
assert_eq!(
compact_to_move_label.len(),
COMPACT_POLICY_LABEL_COUNT,
"unexpected compact policy label count"
);
let mut move_label_to_compact = vec![None; POLICY_LABEL_COUNT];
for (compact, &move_label) in compact_to_move_label.iter().enumerate() {
move_label_to_compact[usize::from(move_label)] =
Some(u16::try_from(compact).expect("compact label index fits in u16"));
}
let mut output = String::new();
output.push_str("// Auto-generated by build.rs - DO NOT EDIT\n\n");
let _ = writeln!(
output,
"pub const COMPACT_TO_MOVE_LABEL: [u16; {COMPACT_POLICY_LABEL_COUNT}] = ["
);
for (idx, label) in compact_to_move_label.iter().enumerate() {
if idx % 16 == 0 {
output.push_str(" ");
}
let _ = write!(output, "{label}, ");
if idx % 16 == 15 {
output.push('\n');
}
}
if !compact_to_move_label.len().is_multiple_of(16) {
output.push('\n');
}
output.push_str("];\n\n");
let _ = writeln!(
output,
"pub const MOVE_LABEL_TO_COMPACT: [Option<u16>; {POLICY_LABEL_COUNT}] = ["
);
for (idx, label) in move_label_to_compact.iter().enumerate() {
if idx % 12 == 0 {
output.push_str(" ");
}
match label {
Some(value) => {
let _ = write!(output, "Some({value}), ");
}
None => output.push_str("None, "),
}
if idx % 12 == 11 {
output.push('\n');
}
}
if move_label_to_compact.len() % 12 != 0 {
output.push('\n');
}
output.push_str("];\n");
let path = out_dir.join("policy_label_tables.rs");
fs::write(path, output).expect("Failed to write policy_label_tables.rs");
}
fn compute_policy_structural_labels() -> Vec<u16> {
let mut valid = vec![false; POLICY_LABEL_COUNT];
for piece_type in PolicyPieceType::ALL {
for from in 0..POLICY_LABEL_SQUARES {
add_piece_labels(piece_type, from, &mut valid);
}
}
for to in 0..POLICY_LABEL_SQUARES {
let (_, to_y) = policy_square_xy(to);
if to_y != 0 {
mark_label(20, to, &mut valid);
mark_label(21, to, &mut valid);
}
if to_y > 1 {
mark_label(22, to, &mut valid);
}
mark_label(23, to, &mut valid);
mark_label(24, to, &mut valid);
mark_label(25, to, &mut valid);
mark_label(26, to, &mut valid);
}
valid
.into_iter()
.enumerate()
.filter_map(|(idx, is_valid)| {
is_valid.then_some(u16::try_from(idx).expect("label fits in u16"))
})
.collect()
}
fn add_piece_labels(piece_type: PolicyPieceType, from: usize, valid: &mut [bool]) {
for &(dx, dy) in piece_step_offsets(piece_type) {
let Some(to) = policy_offset_square(from, dx, dy) else {
continue;
};
add_board_move_label(piece_type, from, to, valid);
}
for &(dx, dy) in piece_slide_offsets(piece_type) {
let mut next = policy_offset_square(from, dx, dy);
while let Some(to) = next {
add_board_move_label(piece_type, from, to, valid);
next = policy_offset_square(to, dx, dy);
}
}
}
const fn piece_step_offsets(piece_type: PolicyPieceType) -> &'static [(i8, i8)] {
const PAWN: &[(i8, i8)] = &[(0, -1)];
const KNIGHT: &[(i8, i8)] = &[(-1, -2), (1, -2)];
const SILVER: &[(i8, i8)] = &[(-1, -1), (0, -1), (1, -1), (-1, 1), (1, 1)];
const GOLD_LIKE: &[(i8, i8)] = &[(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (0, 1)];
const KING: &[(i8, i8)] =
&[(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)];
const HORSE_STEPS: &[(i8, i8)] = &[(-1, 0), (1, 0), (0, -1), (0, 1)];
const DRAGON_STEPS: &[(i8, i8)] = &[(-1, -1), (1, -1), (-1, 1), (1, 1)];
match piece_type {
PolicyPieceType::Pawn => PAWN,
PolicyPieceType::Knight => KNIGHT,
PolicyPieceType::Silver => SILVER,
PolicyPieceType::Gold
| PolicyPieceType::ProPawn
| PolicyPieceType::ProLance
| PolicyPieceType::ProKnight
| PolicyPieceType::ProSilver => GOLD_LIKE,
PolicyPieceType::King => KING,
PolicyPieceType::Horse => HORSE_STEPS,
PolicyPieceType::Dragon => DRAGON_STEPS,
PolicyPieceType::Lance | PolicyPieceType::Bishop | PolicyPieceType::Rook => &[],
}
}
const fn piece_slide_offsets(piece_type: PolicyPieceType) -> &'static [(i8, i8)] {
const LANCE: &[(i8, i8)] = &[(0, -1)];
const BISHOP: &[(i8, i8)] = &[(-1, -1), (1, -1), (-1, 1), (1, 1)];
const ROOK: &[(i8, i8)] = &[(-1, 0), (1, 0), (0, -1), (0, 1)];
match piece_type {
PolicyPieceType::Lance => LANCE,
PolicyPieceType::Bishop | PolicyPieceType::Horse => BISHOP,
PolicyPieceType::Rook | PolicyPieceType::Dragon => ROOK,
PolicyPieceType::Pawn
| PolicyPieceType::Knight
| PolicyPieceType::Silver
| PolicyPieceType::Gold
| PolicyPieceType::King
| PolicyPieceType::ProPawn
| PolicyPieceType::ProLance
| PolicyPieceType::ProKnight
| PolicyPieceType::ProSilver => &[],
}
}
fn add_board_move_label(piece_type: PolicyPieceType, from: usize, to: usize, valid: &mut [bool]) {
let move_class = classify_move_label_class(from, to);
if piece_type.is_promotable() {
if is_mandatory_promotion(piece_type, to) {
mark_label(move_class + 10, to, valid);
return;
}
mark_label(move_class, to, valid);
if is_promotion_move(from, to) {
mark_label(move_class + 10, to, valid);
}
return;
}
mark_label(move_class, to, valid);
}
fn policy_offset_square(from: usize, dx: i8, dy: i8) -> Option<usize> {
let (from_x, from_y) = policy_square_xy(from);
let to_x = from_x + dx;
let to_y = from_y + dy;
if !(0..=8).contains(&to_x) || !(0..=8).contains(&to_y) {
return None;
}
let to_x = usize::try_from(to_x).ok()?;
let to_y = usize::try_from(to_y).ok()?;
Some(to_x * 9 + to_y)
}
fn policy_square_xy(square: usize) -> (i8, i8) {
let x = i8::try_from(square / 9).expect("file fits in i8");
let y = i8::try_from(square % 9).expect("rank fits in i8");
(x, y)
}
fn classify_move_label_class(from: usize, to: usize) -> usize {
let (from_x, from_y) = policy_square_xy(from);
let (to_x, to_y) = policy_square_xy(to);
let dir_x = from_x - to_x;
let dir_y = to_y - from_y;
if dir_y < 0 && dir_x == 0 {
0
} else if dir_y == -2 && dir_x == -1 {
8
} else if dir_y == -2 && dir_x == 1 {
9
} else if dir_y < 0 && dir_x < 0 {
1
} else if dir_y < 0 && dir_x > 0 {
2
} else if dir_y == 0 && dir_x < 0 {
3
} else if dir_y == 0 && dir_x > 0 {
4
} else if dir_y > 0 && dir_x == 0 {
5
} else if dir_y > 0 && dir_x < 0 {
6
} else {
7
}
}
fn is_promotion_move(from: usize, to: usize) -> bool {
is_promotion_zone_square(from) || is_promotion_zone_square(to)
}
fn is_promotion_zone_square(square: usize) -> bool {
let (_, y) = policy_square_xy(square);
y <= 2
}
fn is_mandatory_promotion(piece_type: PolicyPieceType, to: usize) -> bool {
let (_, to_y) = policy_square_xy(to);
match piece_type {
PolicyPieceType::Pawn | PolicyPieceType::Lance => to_y == 0,
PolicyPieceType::Knight => to_y <= 1,
PolicyPieceType::Silver
| PolicyPieceType::Bishop
| PolicyPieceType::Rook
| PolicyPieceType::Gold
| PolicyPieceType::King
| PolicyPieceType::ProPawn
| PolicyPieceType::ProLance
| PolicyPieceType::ProKnight
| PolicyPieceType::ProSilver
| PolicyPieceType::Horse
| PolicyPieceType::Dragon => false,
}
}
fn mark_label(class_idx: usize, to: usize, valid: &mut [bool]) {
valid[class_idx * POLICY_LABEL_SQUARES + to] = true;
}
fn generate_check_candidate_table() -> String {
let mut output = String::new();
output.push_str(
"/// 王手候補テーブル \\[king_sq\\]\\[piece_index\\]\\[color\\]\n\
/// piece_index: 0=PAWN,1=LANCE,2=KNIGHT,3=SILVER,4=GOLD,5=BISHOP,6=ROOK(HORSE)\n\
#[allow(clippy::large_const_arrays)]\n\
pub const CHECK_CANDIDATE_BB: [[[Bitboard; 2]; 7]; 81] = [\n",
);
for ksq in 0..81 {
output.push_str(" [\n");
for pt_idx in 0..7 {
output.push_str(" [\n");
for us in 0..2 {
let bb = check_candidate_bb(ksq, pt_idx, us);
let _ = writeln!(output, " Bitboard::from_packed_bits({bb}u128),");
}
output.push_str(" ],\n");
}
output.push_str(" ],\n");
}
output.push_str("];\n\n");
output
}
#[allow(clippy::too_many_lines)]
fn check_candidate_bb(ksq: i8, pt_idx: usize, us: usize) -> u128 {
let them = us ^ 1;
let enemy_field = enemy_field_mask(us);
let enemy_gold = gold_attacks(them, ksq) & enemy_field;
let mut target = 0u128;
match pt_idx {
0 => {
let mut bb = pawn_attacks(them, ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= pawn_attacks(them, sq);
}
let mut bb = enemy_gold;
while let Some(sq) = pop_lsb(&mut bb) {
target |= pawn_attacks(them, sq);
}
target &= !bit_of(ksq);
}
1 => {
target = lance_step_attacks(them, ksq);
if (enemy_field & bit_of(ksq)) != 0 {
let file = ksq / 9;
let rank = ksq % 9;
if file != 0 {
let sq = (file - 1) * 9 + rank;
target |= lance_step_attacks(them, sq);
}
if file != 8 {
let sq = (file + 1) * 9 + rank;
target |= lance_step_attacks(them, sq);
}
}
}
2 => {
let mut bb = knight_attacks(them, ksq) | enemy_gold;
while let Some(sq) = pop_lsb(&mut bb) {
target |= knight_attacks(them, sq);
}
target &= !bit_of(ksq);
}
3 => {
let mut bb = silver_attacks(them, ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= silver_attacks(them, sq);
}
let mut bb = enemy_gold;
while let Some(sq) = pop_lsb(&mut bb) {
target |= silver_attacks(them, sq);
}
let mut bb = gold_attacks(them, ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= silver_attacks(them, sq) & enemy_field;
}
target &= !bit_of(ksq);
}
4 => {
let mut bb = gold_attacks(them, ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= gold_attacks(them, sq);
}
target &= !bit_of(ksq);
}
5 => {
let mut bb = bishop_step_attacks(ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= bishop_step_attacks(sq);
}
let mut bb = king_attacks(ksq) & enemy_field;
while let Some(sq) = pop_lsb(&mut bb) {
target |= bishop_step_attacks(sq);
}
let mut bb = king_attacks(ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= bishop_step_attacks(sq) & enemy_field;
}
target &= !bit_of(ksq);
}
6 => {
let mut bb = horse_attacks(ksq);
while let Some(sq) = pop_lsb(&mut bb) {
target |= horse_attacks(sq);
}
target &= !bit_of(ksq);
}
_ => {}
}
target
}
fn bit_of(sq: i8) -> u128 {
let shift = u32::try_from(sq).expect("square index is non-negative");
1u128 << shift
}
fn pop_lsb(bb: &mut u128) -> Option<i8> {
if *bb == 0 {
return None;
}
let idx = bb.trailing_zeros();
*bb &= *bb - 1;
Some(i8::try_from(idx).expect("square index fits in i8"))
}
fn enemy_field_mask(us: usize) -> u128 {
let ranks: [i8; 3] = if us == 0 { [0, 1, 2] } else { [6, 7, 8] };
let mut mask = 0u128;
for rank in ranks {
for file in 0..9 {
let sq = file * 9 + rank;
mask |= bit_of(sq);
}
}
mask
}
fn pawn_attacks(color: usize, sq: i8) -> u128 {
generate_pawn_attack(sq, color)
}
fn knight_attacks(color: usize, sq: i8) -> u128 {
generate_knight_attack(sq, color)
}
fn silver_attacks(color: usize, sq: i8) -> u128 {
generate_silver_attack(sq, color)
}
fn gold_attacks(color: usize, sq: i8) -> u128 {
generate_gold_attack(sq, color)
}
fn king_attacks(sq: i8) -> u128 {
generate_king_attack(sq)
}
fn lance_step_attacks(color: usize, sq: i8) -> u128 {
let (black, white) = generate_lance_beams(sq);
if color == 0 { black } else { white }
}
fn bishop_step_attacks(sq: i8) -> u128 {
let (ne, se, sw, nw) = generate_bishop_beams(sq);
ne | se | sw | nw
}
fn horse_attacks(sq: i8) -> u128 {
bishop_step_attacks(sq) | king_attacks(sq)
}
fn generate_non_slider_attacks() -> String {
let mut output = String::new();
output.push_str("/// 歩の攻撃パターン \\[sq\\]\\[color\\]\n");
output
.push_str("pub const PAWN_ATTACKS: SquareTable<[Bitboard; 2], 81> = SquareTable::new([\n");
for sq in 0..81 {
output.push_str(" [\n");
for color in 0..2 {
let bb = generate_pawn_attack(sq, color);
let _ = writeln!(output, " Bitboard::from_packed_bits({bb}u128),");
}
output.push_str(" ],\n");
}
output.push_str("]);\n\n");
output.push_str("/// 桂馬の攻撃パターン \\[sq\\]\\[color\\]\n");
output.push_str(
"pub const KNIGHT_ATTACKS: SquareTable<[Bitboard; 2], 81> = SquareTable::new([\n",
);
for sq in 0..81 {
output.push_str(" [\n");
for color in 0..2 {
let bb = generate_knight_attack(sq, color);
let _ = writeln!(output, " Bitboard::from_packed_bits({bb}u128),");
}
output.push_str(" ],\n");
}
output.push_str("]);\n\n");
output.push_str("/// 銀の攻撃パターン \\[sq\\]\\[color\\]\n");
output.push_str(
"pub const SILVER_ATTACKS: SquareTable<[Bitboard; 2], 81> = SquareTable::new([\n",
);
for sq in 0..81 {
output.push_str(" [\n");
for color in 0..2 {
let bb = generate_silver_attack(sq, color);
let _ = writeln!(output, " Bitboard::from_packed_bits({bb}u128),");
}
output.push_str(" ],\n");
}
output.push_str("]);\n\n");
output.push_str("/// 金の攻撃パターン \\[sq\\]\\[color\\]\n");
output
.push_str("pub const GOLD_ATTACKS: SquareTable<[Bitboard; 2], 81> = SquareTable::new([\n");
for sq in 0..81 {
output.push_str(" [\n");
for color in 0..2 {
let bb = generate_gold_attack(sq, color);
let _ = writeln!(output, " Bitboard::from_packed_bits({bb}u128),");
}
output.push_str(" ],\n");
}
output.push_str("]);\n\n");
output.push_str("/// 玉の攻撃パターン \\[sq\\]\n");
output.push_str("pub const KING_ATTACKS: SquareTable<Bitboard, 81> = SquareTable::new([\n");
for sq in 0..81 {
let bb = generate_king_attack(sq);
let _ = writeln!(output, " Bitboard::from_packed_bits({bb}u128),");
}
output.push_str("]);\n\n");
output
}
fn generate_pawn_attack(sq: i8, color: usize) -> u128 {
let file = sq / 9;
let rank = sq % 9;
let forward_rank = if color == 0 { rank - 1 } else { rank + 1 };
if !(0..9).contains(&forward_rank) {
return 0; }
let target_sq = file * 9 + forward_rank;
1u128 << target_sq
}
fn generate_knight_attack(sq: i8, color: usize) -> u128 {
let file = sq / 9;
let rank = sq % 9;
let mut attacks = 0u128;
let forward_offset = if color == 0 { -2 } else { 2 };
let target_rank = rank + forward_offset;
if !(0..9).contains(&target_rank) {
return 0;
}
if (0..9).contains(&(file + 1)) {
let target_sq = (file + 1) * 9 + target_rank;
attacks |= 1u128 << target_sq;
}
if (0..9).contains(&(file - 1)) {
let target_sq = (file - 1) * 9 + target_rank;
attacks |= 1u128 << target_sq;
}
attacks
}
fn generate_silver_attack(sq: i8, color: usize) -> u128 {
let file = sq / 9;
let rank = sq % 9;
let mut attacks = 0u128;
let forward = if color == 0 { -1 } else { 1 };
let backward = -forward;
if let Some(target_sq) = add_direction(file, rank, 0, forward) {
attacks |= 1u128 << target_sq;
}
if let Some(target_sq) = add_direction(file, rank, 1, forward) {
attacks |= 1u128 << target_sq;
}
if let Some(target_sq) = add_direction(file, rank, -1, forward) {
attacks |= 1u128 << target_sq;
}
if let Some(target_sq) = add_direction(file, rank, 1, backward) {
attacks |= 1u128 << target_sq;
}
if let Some(target_sq) = add_direction(file, rank, -1, backward) {
attacks |= 1u128 << target_sq;
}
attacks
}
fn generate_gold_attack(sq: i8, color: usize) -> u128 {
let file = sq / 9;
let rank = sq % 9;
let mut attacks = 0u128;
#[allow(clippy::cast_possible_wrap)]
let black_offsets: &[(i8, i8)] = &[
(0, -1), (1, -1), (-1, -1), (1, 0), (-1, 0), (0, 1), ];
let white_offsets: &[(i8, i8)] = &[
(0, 1), (-1, 1), (1, 1), (-1, 0), (1, 0), (0, -1), ];
let offsets = if color == 0 { black_offsets } else { white_offsets };
for &(df, dr) in offsets {
if let Some(target_sq) = add_direction(file, rank, df, dr) {
attacks |= 1u128 << target_sq;
}
}
attacks
}
fn generate_king_attack(sq: i8) -> u128 {
let file = sq / 9;
let rank = sq % 9;
let mut attacks = 0u128;
for df in -1..=1 {
for dr in -1..=1 {
if df == 0 && dr == 0 {
continue; }
if let Some(target_sq) = add_direction(file, rank, df, dr) {
attacks |= 1u128 << target_sq;
}
}
}
attacks
}
fn add_direction(file: i8, rank: i8, df: i8, dr: i8) -> Option<i8> {
let new_file = file + df;
let new_rank = rank + dr;
if (0..9).contains(&new_file) && (0..9).contains(&new_rank) {
Some(new_file * 9 + new_rank)
} else {
None
}
}
fn generate_slider_beams() -> String {
fn raw_bitboard_bits(packed: u128) -> u128 {
let low = packed & ((1u128 << 63) - 1);
let high = packed >> 63;
low | (high << 64)
}
let mut output = String::new();
output.push_str("pub const LANCE_BEAMS: SquareTable<LanceBeams, 81> = SquareTable::new([\n");
for square in 0..81 {
let black = slider_beam(square, 0, -1);
let white = slider_beam(square, 0, 1);
let _ = writeln!(
output,
" LanceBeams {{ black: Bitboard::from_packed_bits({black}u128), white: Bitboard::from_packed_bits({white}u128) }},"
);
}
output.push_str("]);\n\n");
output.push_str("const LANCE_RAY_BITS: SquareTable<LanceRayBits, 81> = SquareTable::new([\n");
for square in 0..81 {
let black = slider_beam(square, 0, -1);
let white = slider_beam(square, 0, 1);
let _ = writeln!(output, " LanceRayBits {{ black: {black}u128, white: {white}u128 }},");
}
output.push_str("]);\n\n");
output.push_str("pub const BISHOP_BEAMS: SquareTable<BishopBeams, 81> = SquareTable::new([\n");
for square in 0..81 {
let ne = slider_beam(square, 1, -1);
let se = slider_beam(square, 1, 1);
let sw = slider_beam(square, -1, 1);
let nw = slider_beam(square, -1, -1);
let _ = writeln!(
output,
" BishopBeams {{ ne: Bitboard::from_packed_bits({ne}u128), se: Bitboard::from_packed_bits({se}u128), sw: Bitboard::from_packed_bits({sw}u128), nw: Bitboard::from_packed_bits({nw}u128) }},"
);
}
output.push_str("]);\n\n");
output.push_str("const BISHOP_RAY_BITS: SquareTable<BishopRayBits, 81> = SquareTable::new([\n");
for square in 0..81 {
let ne = slider_beam(square, 1, -1);
let se = slider_beam(square, 1, 1);
let sw = slider_beam(square, -1, 1);
let nw = slider_beam(square, -1, -1);
let ne = raw_bitboard_bits(ne);
let se = raw_bitboard_bits(se);
let sw_reversed = raw_bitboard_bits(sw).reverse_bits();
let nw_reversed = raw_bitboard_bits(nw).reverse_bits();
let _ = writeln!(
output,
" BishopRayBits {{ ne: {ne}u128, se: {se}u128, sw_reversed: {sw_reversed}u128, nw_reversed: {nw_reversed}u128 }},"
);
}
output.push_str("]);\n\n");
output.push_str("pub const ROOK_BEAMS: SquareTable<RookBeams, 81> = SquareTable::new([\n");
for square in 0..81 {
let north = slider_beam(square, 0, -1);
let east = slider_beam(square, 1, 0);
let south = slider_beam(square, 0, 1);
let west = slider_beam(square, -1, 0);
let _ = writeln!(
output,
" RookBeams {{ n: Bitboard::from_packed_bits({north}u128), e: Bitboard::from_packed_bits({east}u128), s: Bitboard::from_packed_bits({south}u128), w: Bitboard::from_packed_bits({west}u128) }},"
);
}
output.push_str("]);\n\n");
output.push_str("const ROOK_RAY_BITS: SquareTable<RookRayBits, 81> = SquareTable::new([\n");
for square in 0..81 {
let north = slider_beam(square, 0, -1);
let east = slider_beam(square, 1, 0);
let south = slider_beam(square, 0, 1);
let west = slider_beam(square, -1, 0);
let east_raw = raw_bitboard_bits(east);
let south_raw = raw_bitboard_bits(south);
let north_reversed = raw_bitboard_bits(north).reverse_bits();
let west_reversed = raw_bitboard_bits(west).reverse_bits();
let _ = writeln!(
output,
" RookRayBits {{ e: {east}u128, w: {west}u128, e_raw: {east_raw}u128, s_raw: {south_raw}u128, n_reversed: {north_reversed}u128, w_reversed: {west_reversed}u128 }},"
);
}
output.push_str("]);\n\n");
output
}
fn generate_rook_rank_extraction_masks() -> String {
String::new()
}
fn generate_bishop_diagonal_extraction_masks() -> String {
String::new()
}
fn generate_lance_beams(sq: i8) -> (u128, u128) {
(slider_beam(sq, 0, -1), slider_beam(sq, 0, 1))
}
fn generate_bishop_beams(sq: i8) -> (u128, u128, u128, u128) {
(slider_beam(sq, 1, -1), slider_beam(sq, 1, 1), slider_beam(sq, -1, 1), slider_beam(sq, -1, -1))
}
fn slider_beam(sq: i8, file_delta: i8, rank_delta: i8) -> u128 {
let mut file = sq / 9 + file_delta;
let mut rank = sq % 9 + rank_delta;
let mut bits = 0u128;
while (0..9).contains(&file) && (0..9).contains(&rank) {
let target = file * 9 + rank;
bits |= 1u128 << target;
file += file_delta;
rank += rank_delta;
}
bits
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_generate_square_tables() {
let temp_dir = std::env::temp_dir().join("rsshogi_build_test");
fs::create_dir_all(&temp_dir).unwrap();
generate_square_tables(&temp_dir);
let content = fs::read_to_string(temp_dir.join("square_tables.rs"))
.expect("Failed to read generated file");
assert!(content.contains("SQUARE_TO_FILE"));
assert!(content.contains("SQUARE_TO_RANK"));
assert!(content.contains("[File; 82]")); assert!(content.contains("[Rank; 82]"));
assert!(content.contains("File(0),")); assert!(content.contains("Rank(0),"));
assert!(content.contains("File(-1), // SQ_NONE"));
assert!(content.contains("Rank(-1), // SQ_NONE"));
let _ = fs::remove_dir_all(&temp_dir);
}
}