use super::Feature;
use super::TriggerEvent;
use crate::nnue::accumulator::{DirtyPiece, IndexList, MAX_ACTIVE_FEATURES, MAX_CHANGED_FEATURES};
use crate::nnue::bona_piece::{BonaPiece, FE_END, halfkp_index};
use crate::nnue::piece_list::PieceNumber;
use crate::position::Position;
use crate::types::{Color, Square};
pub struct HalfKP;
impl Feature for HalfKP {
const DIMENSIONS: usize = 81 * FE_END;
const MAX_ACTIVE: usize = 52;
const REFRESH_TRIGGER: TriggerEvent = TriggerEvent::FriendKingMoved;
#[inline]
fn append_active_indices(
pos: &Position,
perspective: Color,
active: &mut IndexList<MAX_ACTIVE_FEATURES>,
) {
let raw_king_sq = pos.king_square(perspective);
let king_sq = if perspective == Color::Black {
raw_king_sq
} else {
raw_king_sq.inverse()
};
let pieces = if perspective == Color::Black {
pos.piece_list().piece_list_fb()
} else {
pos.piece_list().piece_list_fw()
};
for bp in &pieces[..PieceNumber::KING as usize] {
if *bp != BonaPiece::ZERO {
let _ = active.push(halfkp_index(king_sq, *bp));
}
}
}
#[inline]
fn append_changed_indices(
dirty_piece: &DirtyPiece,
perspective: Color,
king_sq: Square,
removed: &mut IndexList<MAX_CHANGED_FEATURES>,
added: &mut IndexList<MAX_CHANGED_FEATURES>,
) {
let king_sq = if perspective == Color::Black {
king_sq
} else {
king_sq.inverse()
};
for i in 0..dirty_piece.dirty_num as usize {
let cp = &dirty_piece.changed_piece[i];
let old_bp = if perspective == Color::Black {
cp.old_piece.fb
} else {
cp.old_piece.fw
};
let new_bp = if perspective == Color::Black {
cp.new_piece.fb
} else {
cp.new_piece.fw
};
if old_bp != BonaPiece::ZERO && (old_bp.value() as usize) < FE_END {
let _ = removed.push(halfkp_index(king_sq, old_bp));
}
if new_bp != BonaPiece::ZERO && (new_bp.value() as usize) < FE_END {
let _ = added.push(halfkp_index(king_sq, new_bp));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nnue::accumulator::{ChangedBonaPiece, DirtyPiece};
use crate::nnue::bona_piece::ExtBonaPiece;
use crate::nnue::piece_list::PieceNumber;
use crate::position::Position;
use crate::types::{Color, File, Piece, PieceType, Rank, Square};
#[test]
fn test_halfkp_dimensions() {
assert_eq!(HalfKP::DIMENSIONS, 81 * FE_END);
}
#[test]
fn test_halfkp_max_active() {
assert_eq!(HalfKP::MAX_ACTIVE, 52);
}
#[test]
fn test_halfkp_refresh_trigger() {
assert_eq!(HalfKP::REFRESH_TRIGGER, TriggerEvent::FriendKingMoved);
}
#[test]
fn test_append_active_indices_startpos() {
let mut pos = Position::new();
pos.set_sfen("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1")
.unwrap();
let mut active = IndexList::new();
HalfKP::append_active_indices(&pos, Color::Black, &mut active);
assert_eq!(active.len(), 38);
}
#[test]
fn test_feature_indices_in_range() {
let mut pos = Position::new();
pos.set_sfen("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1")
.unwrap();
for perspective in [Color::Black, Color::White] {
let mut active = IndexList::new();
HalfKP::append_active_indices(&pos, perspective, &mut active);
let max_valid_index = 81 * FE_END - 1;
for (i, &index) in active.iter().enumerate() {
assert!(
index <= max_valid_index,
"Feature index {} at position {} exceeds max {} for perspective {:?}",
index,
i,
max_valid_index,
perspective
);
}
}
}
#[test]
fn test_bona_piece_values() {
use crate::nnue::bona_piece::{E_DRAGON, E_GOLD, E_PAWN, F_DRAGON, F_GOLD, F_PAWN};
assert_eq!(F_PAWN, 90, "f_pawn should be 90");
assert_eq!(E_PAWN, 171, "e_pawn should be 171");
assert_eq!(F_GOLD, 738, "f_gold should be 738");
assert_eq!(E_GOLD, 819, "e_gold should be 819");
assert_eq!(F_DRAGON, 1386, "f_dragon should be 1386");
assert_eq!(E_DRAGON, 1467, "e_dragon should be 1467");
assert_eq!(FE_END, 1548, "fe_end should be 1548");
}
#[test]
fn test_append_changed_indices_piece_move() {
let sq_77 = Square::new(File::File7, Rank::Rank7);
let sq_76 = Square::new(File::File7, Rank::Rank6);
let king_sq = Square::new(File::File5, Rank::Rank9);
let old_bp = ExtBonaPiece::from_board(Piece::B_PAWN, sq_77);
let new_bp = ExtBonaPiece::from_board(Piece::B_PAWN, sq_76);
let mut dirty_piece = DirtyPiece::new();
dirty_piece.dirty_num = 1;
dirty_piece.piece_no[0] = PieceNumber(0);
dirty_piece.changed_piece[0] = ChangedBonaPiece {
old_piece: old_bp,
new_piece: new_bp,
};
let mut removed = IndexList::new();
let mut added = IndexList::new();
HalfKP::append_changed_indices(
&dirty_piece,
Color::Black,
king_sq,
&mut removed,
&mut added,
);
assert_eq!(removed.len(), 1);
assert_eq!(added.len(), 1);
let removed_idx: Vec<_> = removed.iter().copied().collect();
let added_idx: Vec<_> = added.iter().copied().collect();
assert_ne!(removed_idx[0], added_idx[0]);
}
#[test]
fn test_append_changed_indices_capture() {
let sq_24 = Square::new(File::File2, Rank::Rank4);
let sq_23 = Square::new(File::File2, Rank::Rank3);
let king_sq = Square::new(File::File5, Rank::Rank9);
let mut dirty_piece = DirtyPiece::new();
dirty_piece.dirty_num = 2;
dirty_piece.piece_no[0] = PieceNumber(0);
dirty_piece.changed_piece[0] = ChangedBonaPiece {
old_piece: ExtBonaPiece::from_board(Piece::B_PAWN, sq_24),
new_piece: ExtBonaPiece::from_board(Piece::B_PAWN, sq_23),
};
dirty_piece.piece_no[1] = PieceNumber(1);
dirty_piece.changed_piece[1] = ChangedBonaPiece {
old_piece: ExtBonaPiece::from_board(Piece::W_PAWN, sq_23),
new_piece: ExtBonaPiece::from_hand(Color::Black, PieceType::Pawn, 1),
};
let mut removed = IndexList::new();
let mut added = IndexList::new();
HalfKP::append_changed_indices(
&dirty_piece,
Color::Black,
king_sq,
&mut removed,
&mut added,
);
assert_eq!(removed.len(), 2);
assert_eq!(added.len(), 2);
}
#[test]
fn test_append_changed_indices_drop() {
let king_sq = Square::new(File::File5, Rank::Rank9);
let mut dirty_piece = DirtyPiece::new();
dirty_piece.dirty_num = 1;
dirty_piece.piece_no[0] = PieceNumber(0);
dirty_piece.changed_piece[0] = ChangedBonaPiece {
old_piece: ExtBonaPiece::from_hand(Color::Black, PieceType::Pawn, 1),
new_piece: ExtBonaPiece::from_board(Piece::B_PAWN, Square::SQ_55),
};
let mut removed = IndexList::new();
let mut added = IndexList::new();
HalfKP::append_changed_indices(
&dirty_piece,
Color::Black,
king_sq,
&mut removed,
&mut added,
);
assert_eq!(removed.len(), 1);
assert_eq!(added.len(), 1);
}
#[test]
fn test_append_changed_indices_hand_change() {
let king_sq = Square::new(File::File5, Rank::Rank9);
let mut dirty_piece = DirtyPiece::new();
dirty_piece.dirty_num = 1;
dirty_piece.piece_no[0] = PieceNumber(0);
dirty_piece.changed_piece[0] = ChangedBonaPiece {
old_piece: ExtBonaPiece::ZERO, new_piece: ExtBonaPiece::from_hand(Color::Black, PieceType::Pawn, 1),
};
let mut removed = IndexList::new();
let mut added = IndexList::new();
HalfKP::append_changed_indices(
&dirty_piece,
Color::Black,
king_sq,
&mut removed,
&mut added,
);
assert_eq!(removed.len(), 0);
assert_eq!(added.len(), 1);
}
#[test]
fn test_append_changed_indices_hand_change_increment() {
let king_sq = Square::new(File::File5, Rank::Rank9);
let mut dirty_piece = DirtyPiece::new();
dirty_piece.dirty_num = 1;
dirty_piece.piece_no[0] = PieceNumber(0);
dirty_piece.changed_piece[0] = ChangedBonaPiece {
old_piece: ExtBonaPiece::from_hand(Color::Black, PieceType::Pawn, 1),
new_piece: ExtBonaPiece::from_hand(Color::Black, PieceType::Pawn, 2),
};
let mut removed = IndexList::new();
let mut added = IndexList::new();
HalfKP::append_changed_indices(
&dirty_piece,
Color::Black,
king_sq,
&mut removed,
&mut added,
);
assert_eq!(removed.len(), 1);
assert_eq!(added.len(), 1);
}
#[test]
fn test_debug_feature_indices() {
use crate::nnue::accumulator::{IndexList, MAX_ACTIVE_FEATURES};
use crate::nnue::bona_piece::{E_PAWN, F_PAWN};
let mut pos = Position::new();
pos.set_sfen("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1")
.unwrap();
let king_sq_b = pos.king_square(Color::Black);
let king_sq_w = pos.king_square(Color::White);
let king_sq_w_inv = king_sq_w.inverse();
eprintln!("Black King: {:?} (index={})", king_sq_b, king_sq_b.index());
eprintln!(
"White King: {:?} (index={}), inverted: {:?} (index={})",
king_sq_w,
king_sq_w.index(),
king_sq_w_inv,
king_sq_w_inv.index()
);
let sq_77 = Square::new(File::File7, Rank::Rank7);
let bp_77_black = crate::nnue::bona_piece::BonaPiece::from_piece_square(
Piece::B_PAWN,
sq_77,
Color::Black,
);
let bp_77_white = crate::nnue::bona_piece::BonaPiece::from_piece_square(
Piece::B_PAWN,
sq_77,
Color::White,
);
eprintln!(
"7七先手歩: sq_index={}, Black view BP={}, White view BP={}",
sq_77.index(),
bp_77_black.value(),
bp_77_white.value()
);
eprintln!(
" Expected Black: F_PAWN({}) + {} = {}",
F_PAWN,
sq_77.index(),
F_PAWN as usize + sq_77.index()
);
eprintln!(
" Expected White: E_PAWN({}) + {} = {}",
E_PAWN,
sq_77.inverse().index(),
E_PAWN as usize + sq_77.inverse().index()
);
let mut active_b: IndexList<MAX_ACTIVE_FEATURES> = IndexList::new();
HalfKP::append_active_indices(&pos, Color::Black, &mut active_b);
let mut active_w: IndexList<MAX_ACTIVE_FEATURES> = IndexList::new();
HalfKP::append_active_indices(&pos, Color::White, &mut active_w);
eprintln!("Black perspective: {} features", active_b.len());
eprintln!("White perspective: {} features", active_w.len());
let max_b = active_b.iter().max().copied().unwrap_or(0);
let max_w = active_w.iter().max().copied().unwrap_or(0);
let max_valid = 81 * FE_END - 1;
eprintln!("Max index (Black): {}", max_b);
eprintln!("Max index (White): {}", max_w);
eprintln!("Max valid index: {}", max_valid);
assert!(max_b <= max_valid, "Black max index out of range");
assert!(max_w <= max_valid, "White max index out of range");
}
}