packedsfen 0.3.1

packedsfen is a library for processing Huffman-encoded supervised phases for Shogi AI.
Documentation
use usiagent::shogi::*;

use crate::BitStreamReader;
use super::super::error::*;
use super::super::traits;
use super::haffman_code::ExtendFields;
use crate::hcpe::haffman_code::{HuffmanCode, HuffmanCodedPosAndEval, Hcpe, GameResult};
use crate::traits::TryFrom;

const MOVE_NONE:u16 = 0;
const MOVE_NULL:u16 = 129;
const MOVE_PVS_END:u16 = 1 << 15;
const MOVE_PROMOTE:u16 = 1 << 14;

const DROP_FROM_ORIGN:u16 = 81;

const MOCHIGOMA_MAP:[MochigomaKind; 7] = [
    MochigomaKind::Fu,
    MochigomaKind::Kyou,
    MochigomaKind::Kei,
    MochigomaKind::Gin,
    MochigomaKind::Kaku,
    MochigomaKind::Hisha,
    MochigomaKind::Kin
];

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BestMove {
    None,
    Null,
    MovePVsEnd,
    MoveTo(u32,u32,u32,u32,bool),
    MovePut(MochigomaKind,u32,u32)
}
impl traits::TryFrom<u16> for BestMove {
    type Error = ReadError;
    fn try_from(value: u16) -> Result<BestMove,ReadError> {
        Ok(match value {
            MOVE_NONE => BestMove::None,
            MOVE_NULL => BestMove::Null,
            MOVE_PVS_END => BestMove::MovePVsEnd,
            v => {
                let from = (v >> 7) & 0x7f;

                if from > 80 {
                    let index = from - DROP_FROM_ORIGN;

                    if index >= 7 {
                        return Err(ReadError::InvalidFormat(String::from("piece kind is invalid.")))
                    }

                    let kind = MOCHIGOMA_MAP[index as usize];

                    let sq = v & 0x7f;

                    if sq > 80 {
                        return Err(ReadError::InvalidFormat(String::from("move put position is invalid.")))
                    }

                    let x = sq / 9;
                    let y = sq - 9 * x;

                    BestMove::MovePut(kind, x as u32, y as u32)
                } else {
                    let n = v & MOVE_PROMOTE != 0;

                    let sq = from;

                    if sq > 80 {
                        return Err(ReadError::InvalidFormat(String::from("move from position is invalid.")))
                    }

                    let sx = sq / 9;
                    let sy = sq - 9 * sx;

                    let sq = v & 0x7f;

                    if sq > 80 {
                        return Err(ReadError::InvalidFormat(String::from("move to position is invalid.")))
                    }

                    let dx = sq / 9;
                    let dy = sq - 9 * dx;

                    BestMove::MoveTo(sx as u32, sy as u32, dx as u32, dy as u32, n)
                }
            }
        })
    }
}
pub struct HcpeReader {
}
impl HcpeReader {
    pub fn new() -> HcpeReader {
        HcpeReader {
        }
    }

    fn read_raw_sfen_with_extended(&mut self, buf:Vec<u8>) -> Result<HuffmanCodedPosAndEval,ReadError> {
        if buf.len() != 38 {
            Err(ReadError::InvalidFormat(format!("input size is incorrect. actual size = {}",buf.len())))
        } else {
            let mut buf = buf;
            let remained = buf.split_off(32);

            let packed_sfen = buf;

            let mut buf = remained;

            let remained = buf.split_off(2);

            let score_value:i16 = buf[0] as i16 | (buf[1] as i16) << 8;

            let mut buf = remained;

            let remained = buf.split_off(2);

            let best_move16 = buf[0] as u16 | (buf[1] as u16) << 8;

            let mut buf = remained;

            let _ = buf.split_off(1);

            let game_result:i8 = buf[0] as i8;

            let mut sfen_buf = [0; 32];

            for i in 0..32 {
                sfen_buf[i] = packed_sfen[i];
            }

            Ok(HuffmanCodedPosAndEval {
                packed: Hcpe { buf: sfen_buf },
                eval: score_value,
                best_move16: best_move16,
                game_result: game_result,
                padding: 0
            })
        }
    }
}
impl traits::Reader<ExtendFields> for HcpeReader {
    fn read_sfen(&mut self, buf:&[u8]) -> Result<(Teban,Banmen,MochigomaCollections),ReadError> {
        if buf.len() != 32 {
            Err(ReadError::InvalidFormat(format!("input size is incorrect. actual size = {}",buf.len())))
        } else {
            let mut bs = BitStreamReader::new(buf);

            let mut banmen = Banmen([[KomaKind::Blank; 9]; 9]);
            let mut ms:Mochigoma = Mochigoma::new();
            let mut mg:Mochigoma = Mochigoma::new();

            let teban = if bs.get_bit_from_lsb()? == 0 {
                Teban::Sente
            } else {
                Teban::Gote
            };

            let sq = bs.get_bits_from_lsb(7)? as u32;

            if sq > 80 {
                return Err(ReadError::InvalidFormat(String::from("sente ou position is out of range.")));
            }

            let x = sq as usize / 9;
            let y = sq as usize - 9 * x;

            banmen.0[y][x] = KomaKind::SOu;

            let sq = bs.get_bits_from_lsb(7)? as u32;

            if sq > 80 {
                return Err(ReadError::InvalidFormat(String::from("gote ou position is out of range.")));
            }

            let x = sq as usize / 9;
            let y = sq as usize - 9 * x;

            banmen.0[y][x] = KomaKind::GOu;

            for x in 0..9 {
                for y in 0..9 {
                    if banmen.0[y][x] == KomaKind::SOu || banmen.0[y][x] == KomaKind::GOu {
                        continue;
                    }

                    let mut hc = HuffmanCode { value: 0, bit_length: 0 };

                    loop {
                        hc.value |= bs.get_bit_from_lsb()? << hc.bit_length;
                        hc.bit_length += 1;

                        match hc.defined() {
                            Ok(true) => {
                                let kind = KomaKind::try_from(&hc)?;

                                if banmen.0[y][x] == KomaKind::SOu || banmen.0[y][x] == KomaKind::GOu {
                                    break;
                                } else {
                                    banmen.0[y][x] = kind;
                                    break;
                                }
                            },
                            Ok(false) => (),
                            Err(e) => {
                                return Err(e);
                            }
                        }
                    }
                }
            }

            while bs.get_cursor() < 256 {
                let mut hc = HuffmanCode { value: 0, bit_length: 0 };

                loop {
                    hc.value |= bs.get_bit_from_lsb()? << hc.bit_length;
                    hc.bit_length += 1;

                    match hc.defined_mochigoma() {
                        Ok(true) => {
                            let kind = MochigomaKind::try_from(&hc)?;
                            let teban = Teban::try_from(&hc)?;

                            if teban == Teban::Sente {
                                ms.put(kind);
                            } else {
                                mg.put(kind);
                            }
                            break;
                        },
                        Ok(false) => (),
                        Err(e) => {
                            return Err(e);
                        }
                    }
                }
            }

            Ok((teban,banmen,MochigomaCollections::Pair(ms,mg)))
        }
    }

    fn read_sfen_with_extended(&mut self, buf:Vec<u8>) -> Result<((Teban,Banmen,MochigomaCollections),ExtendFields),ReadError> {
        let raw_packed_sfen_with_extended = self.read_raw_sfen_with_extended(buf)?;

        let game_result = match raw_packed_sfen_with_extended.game_result {
            0 => GameResult::Draw,
            1 => GameResult::SenteWin,
            2 => GameResult::GoteWin,
            r => {
                return Err(ReadError::InvalidFormat(format!("unknown game_result {}.",r)));
            }
        };

        let gamestate = self.read_sfen(&raw_packed_sfen_with_extended.packed.buf)?;

        Ok((gamestate,ExtendFields {
            eval: raw_packed_sfen_with_extended.eval,
            best_move: BestMove::try_from(raw_packed_sfen_with_extended.best_move16)?,
            game_result:  game_result
        }))
    }
}