use super::bit_io::{BitCursorOverflow, BitReader, BitWriter};
use super::{BoardArray, Ply, Position};
use crate::types::{Color, Hand, HandPiece, Piece, PieceType, Square};
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct HuffmanCodedPos {
pub data: [u8; 32],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HuffmanCodedPosError {
InvalidCursor,
InvalidPieceCode,
InvalidKingSquare(u16),
}
impl fmt::Display for HuffmanCodedPosError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidCursor => f.write_str("Huffman cursor overflow"),
Self::InvalidPieceCode => f.write_str("invalid Huffman piece code"),
Self::InvalidKingSquare(square) => write!(f, "invalid king square: {square}"),
}
}
}
impl std::error::Error for HuffmanCodedPosError {}
impl From<BitCursorOverflow> for HuffmanCodedPosError {
fn from(_: BitCursorOverflow) -> Self {
Self::InvalidCursor
}
}
const HAND_ORDER: [PieceType; 7] = [
PieceType::PAWN,
PieceType::LANCE,
PieceType::KNIGHT,
PieceType::SILVER,
PieceType::GOLD,
PieceType::BISHOP,
PieceType::ROOK,
];
const INVENTORY: [u8; 7] = [18, 4, 4, 4, 4, 2, 2];
fn board_code(piece: Piece) -> Option<(u16, u8)> {
let white = piece.color() == Color::WHITE;
let (code, bits) = match piece.piece_type() {
PieceType::NONE => return Some((0, 1)),
PieceType::PAWN => (if white { 0x05 } else { 0x01 }, 4),
PieceType::LANCE => (if white { 0x13 } else { 0x03 }, 6),
PieceType::KNIGHT => (if white { 0x17 } else { 0x07 }, 6),
PieceType::SILVER => (if white { 0x1b } else { 0x0b }, 6),
PieceType::GOLD => (if white { 0x2f } else { 0x0f }, 6),
PieceType::BISHOP => (if white { 0x5f } else { 0x1f }, 8),
PieceType::ROOK => (if white { 0x7f } else { 0x3f }, 8),
PieceType::PRO_PAWN => (if white { 0x0d } else { 0x09 }, 4),
PieceType::PRO_LANCE => (if white { 0x33 } else { 0x23 }, 6),
PieceType::PRO_KNIGHT => (if white { 0x37 } else { 0x27 }, 6),
PieceType::PRO_SILVER => (if white { 0x3b } else { 0x2b }, 6),
PieceType::HORSE => (if white { 0xdf } else { 0x9f }, 8),
PieceType::DRAGON => (if white { 0xff } else { 0xbf }, 8),
_ => return None,
};
Some((code, bits))
}
fn hand_code(color: Color, piece: PieceType) -> (u16, u8) {
let white = color == Color::WHITE;
match piece {
PieceType::PAWN => (if white { 0x04 } else { 0x00 }, 3),
PieceType::LANCE => (if white { 0x11 } else { 0x01 }, 5),
PieceType::KNIGHT => (if white { 0x13 } else { 0x03 }, 5),
PieceType::SILVER => (if white { 0x15 } else { 0x05 }, 5),
PieceType::GOLD => (if white { 0x17 } else { 0x07 }, 5),
PieceType::BISHOP => (if white { 0x5f } else { 0x1f }, 7),
PieceType::ROOK => (if white { 0x7f } else { 0x3f }, 7),
_ => unreachable!("only hand piece types are encoded"),
}
}
fn box_code(piece: PieceType) -> (u16, u8) {
match piece {
PieceType::PAWN => (0x02, 3),
PieceType::LANCE => (0x09, 5),
PieceType::KNIGHT => (0x0b, 5),
PieceType::SILVER => (0x0d, 5),
PieceType::GOLD => (0x1d, 5),
PieceType::BISHOP => (0x0f, 7),
PieceType::ROOK => (0x2f, 7),
_ => unreachable!("only box piece types are encoded"),
}
}
fn decode_code<T: Copy>(
reader: &mut BitReader<'_>,
table: &[(u16, u8, T)],
) -> Result<T, HuffmanCodedPosError> {
let mut value = 0;
for bits in 1..=8 {
if reader.read_one_bit()? {
value |= 1 << (bits - 1);
}
if let Some((_, _, item)) =
table.iter().find(|(code, width, _)| *width == bits && *code == value)
{
return Ok(*item);
}
}
Err(HuffmanCodedPosError::InvalidPieceCode)
}
fn decode_board_piece(reader: &mut BitReader<'_>) -> Result<Piece, HuffmanCodedPosError> {
let mut table = [(0, 0, Piece::NONE); 29];
let mut next = 0;
table[next] = (0, 1, Piece::NONE);
next += 1;
for color in [Color::BLACK, Color::WHITE] {
for piece_type in [
PieceType::PAWN,
PieceType::LANCE,
PieceType::KNIGHT,
PieceType::SILVER,
PieceType::GOLD,
PieceType::BISHOP,
PieceType::ROOK,
PieceType::PRO_PAWN,
PieceType::PRO_LANCE,
PieceType::PRO_KNIGHT,
PieceType::PRO_SILVER,
PieceType::HORSE,
PieceType::DRAGON,
] {
let piece = Piece::from_parts(color, piece_type);
let (code, bits) = board_code(piece).expect("declared board code");
table[next] = (code, bits, piece);
next += 1;
}
}
decode_code(reader, &table)
}
#[derive(Clone, Copy)]
enum SuffixPiece {
Hand(Color, PieceType),
Box(PieceType),
}
fn decode_suffix_piece(reader: &mut BitReader<'_>) -> Result<SuffixPiece, HuffmanCodedPosError> {
let mut table = [(0, 0, SuffixPiece::Box(PieceType::PAWN)); 21];
let mut next = 0;
for color in [Color::BLACK, Color::WHITE] {
for piece_type in HAND_ORDER {
let (code, bits) = hand_code(color, piece_type);
table[next] = (code, bits, SuffixPiece::Hand(color, piece_type));
next += 1;
}
}
for piece_type in HAND_ORDER {
let (code, bits) = box_code(piece_type);
table[next] = (code, bits, SuffixPiece::Box(piece_type));
next += 1;
}
decode_code(reader, &table)
}
fn inventory_index(piece_type: PieceType) -> Option<usize> {
HAND_ORDER.iter().position(|&piece| piece == piece_type.demote())
}
fn hands_from_counts(
counts: [[u8; 7]; Color::COUNT],
) -> Result<[Hand; Color::COUNT], HuffmanCodedPosError> {
let mut hands = [Hand::ZERO; Color::COUNT];
for color in [Color::BLACK, Color::WHITE] {
for (index, piece_type) in HAND_ORDER.into_iter().enumerate() {
let hand_piece = HandPiece::from_piece_type(piece_type).expect("declared hand type");
hands[color.to_index()] = hands[color.to_index()]
.checked_add(hand_piece, u32::from(counts[color.to_index()][index]))
.ok_or(HuffmanCodedPosError::InvalidPieceCode)?;
}
}
Ok(hands)
}
impl Position {
pub fn to_huffman_coded_pos(&self) -> HuffmanCodedPos {
let mut packed = HuffmanCodedPos::default();
let mut writer = BitWriter::new(&mut packed.data);
writer.write_one_bit(self.turn() == Color::WHITE);
writer.write_n_bits(self.king_square(Color::BLACK).raw() as u16, 7);
writer.write_n_bits(self.king_square(Color::WHITE).raw() as u16, 7);
let mut used = [0u8; 7];
for raw in 0..Square::COUNT {
let square = Square::new(raw as i8);
if square == self.king_square(Color::BLACK) || square == self.king_square(Color::WHITE)
{
continue;
}
let piece = self.piece_on(square);
let (code, bits) = board_code(piece).expect("serializable board piece");
writer.write_n_bits(code, bits);
if !piece.is_empty() {
let base = piece.piece_type().demote();
if let Some(hand) = HandPiece::from_piece_type(base) {
used[HAND_ORDER.iter().position(|&p| p == base).expect("base inventory")] += 1;
let _ = hand;
}
}
}
for color in [Color::BLACK, Color::WHITE] {
for (index, piece_type) in HAND_ORDER.into_iter().enumerate() {
let count = self
.hand(color)
.count(HandPiece::from_piece_type(piece_type).expect("hand type"));
used[index] += count as u8;
let (code, bits) = hand_code(color, piece_type);
for _ in 0..count {
writer.write_n_bits(code, bits);
}
}
}
for (index, piece_type) in HAND_ORDER.into_iter().enumerate() {
let (code, bits) = box_code(piece_type);
for _ in used[index]..INVENTORY[index] {
writer.write_n_bits(code, bits);
}
}
debug_assert_eq!(writer.cursor(), 256);
packed
}
pub fn huffman_coded_pos_unpack(
packed: &HuffmanCodedPos,
) -> Result<String, HuffmanCodedPosError> {
let mut position = Position::empty();
position.set_huffman_coded_pos(packed, 0)?;
Ok(position.to_sfen(None))
}
pub fn set_huffman_coded_pos(
&mut self,
packed: &HuffmanCodedPos,
ply: Ply,
) -> Result<(), HuffmanCodedPosError> {
let mut reader = BitReader::new(&packed.data);
let side = if reader.read_one_bit()? { Color::WHITE } else { Color::BLACK };
let black_king = reader.read_n_bits(7)?;
if black_king >= Square::COUNT as u16 {
return Err(HuffmanCodedPosError::InvalidKingSquare(black_king));
}
let white_king = reader.read_n_bits(7)?;
if white_king >= Square::COUNT as u16 {
return Err(HuffmanCodedPosError::InvalidKingSquare(white_king));
}
if black_king == white_king {
return Err(HuffmanCodedPosError::InvalidPieceCode);
}
let black_king = Square::new(black_king as i8);
let white_king = Square::new(white_king as i8);
let mut board = BoardArray::empty();
let mut inventory = [0u8; 7];
let mut hand_counts = [[0u8; 7]; Color::COUNT];
board.set(black_king, Piece::from_parts(Color::BLACK, PieceType::KING));
board.set(white_king, Piece::from_parts(Color::WHITE, PieceType::KING));
for raw in 0..Square::COUNT {
let square = Square::new(raw as i8);
if square == black_king || square == white_king {
continue;
}
let piece = decode_board_piece(&mut reader)?;
if !piece.is_empty() {
let Some(index) = inventory_index(piece.piece_type()) else {
return Err(HuffmanCodedPosError::InvalidPieceCode);
};
inventory[index] = inventory[index]
.checked_add(1)
.ok_or(HuffmanCodedPosError::InvalidPieceCode)?;
}
board.set(square, piece);
}
while reader.cursor() < 256 {
match decode_suffix_piece(&mut reader)? {
SuffixPiece::Hand(color, piece_type) => {
let index = inventory_index(piece_type).expect("declared hand type");
inventory[index] = inventory[index]
.checked_add(1)
.ok_or(HuffmanCodedPosError::InvalidPieceCode)?;
hand_counts[color.to_index()][index] = hand_counts[color.to_index()][index]
.checked_add(1)
.ok_or(HuffmanCodedPosError::InvalidPieceCode)?;
}
SuffixPiece::Box(piece_type) => {
let index = inventory_index(piece_type).expect("declared box type");
inventory[index] = inventory[index]
.checked_add(1)
.ok_or(HuffmanCodedPosError::InvalidPieceCode)?;
}
}
}
if reader.cursor() != 256 {
return Err(HuffmanCodedPosError::InvalidCursor);
}
if inventory != INVENTORY {
return Err(HuffmanCodedPosError::InvalidPieceCode);
}
let hands = hands_from_counts(hand_counts)?;
self.board = board;
self.hands = hands;
self.side_to_move = side;
self.ply = ply;
self.entering_king_rule = crate::types::EnteringKingRule::None;
self.entering_king_point = [0, 0];
self.rebuild_bitboards();
self.reset_state_stack_to_current_position();
Ok(())
}
}