syd-format 0.2.1

binary format for chess game tree
Documentation
use std::{convert::TryFrom, iter::zip};
use bulletformat::ChessBoard;

#[repr(u8)]
#[allow(dead_code)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum PieceType{
    Pawn,
    Knight,
    Bishop,
    Rook,
    Queen,
    King,
    Castle,
    Void
}

#[repr(u8)]
#[allow(dead_code)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Pieces{
    WPawn, BPawn,
    WKnight, BKnight,
    WBishop, BBishop,
    WRook, BRook,
    WQueen, BQueen,
    WKing, BKing,
    WCASTLE, BCASLTE,
    Void
}

#[allow(dead_code)]
pub enum Color{White, Black}

impl TryFrom<u8> for Color {
    type Error = ();

    fn try_from(v: u8) -> Result<Self, Self::Error> {
        unsafe { std::mem::transmute(v) }
    }
}

impl TryFrom<u8> for PieceType {
    type Error = ();

    fn try_from(v: u8) -> Result<Self, Self::Error> {
        unsafe { std::mem::transmute(v) }
    }
}

impl Pieces {
    pub fn color(&self) -> Color {
        (*self as u8 % 2).try_into().unwrap()
    }
    pub fn piecetype(&self) -> PieceType {
        (*self as u8 / 2).try_into().unwrap()
    }
    pub fn from_viri(id: u8) -> Pieces {
        ((id >> 3) | (id & 0b111) << 1).try_into().unwrap()
    }
    pub fn from_pc_color(pc: PieceType, c: Color) -> Pieces {
        ((pc as u8) << 1 | c as u8).try_into().unwrap()
    }
}

impl TryFrom<u8> for Pieces {
    type Error = ();

    fn try_from(v: u8) -> Result<Self, Self::Error> {
        unsafe { std::mem::transmute(v) }
    }
}
const ZOBR_CASTLE: usize = 12*64;
const ZOBR_PASSANT: usize = ZOBR_CASTLE;
const ZOBR_TURN: usize = ZOBR_PASSANT+65;
#[cfg(feature = "hll")]
const ZOBR_TOT: usize = ZOBR_TURN+1;
#[cfg(feature = "hll")]
const ZOBRISTS: [u64; ZOBR_TOT] = {
    let mut res: [u64; ZOBR_TOT] = [0; ZOBR_TOT];

    let mut state: u64 = 42;
    cfor!(let mut idz: usize = 0; idz < ZOBR_TOT; idz += 1; {
        state = state.wrapping_add(0x9E3779B97F4A7C15);
        let mut z: u64 = state;
        z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
        z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
        res[idz] = z ^ (z >> 31);
    });
    res[ZOBR_CASTLE+64] = 0;
    res[ZOBR_PASSANT+64] = 0;
    res
};

#[derive(Clone, Copy)]
pub struct Position{
    pub bbs: [u64; 8],
    pub mailbox: [u8; 64],
    pub score: i16,
    pub bound: u8,
    pub rule50: u8,
    pub bm: u16,
    pub stm: bool,
    pub depth: u8,
    pub ep: u8,
    pub castle_mask: u64,
    #[cfg(feature = "hll")]
    pub hash: u64,
}

impl Default for Position {
    fn default() -> Self {
        Self {
            bbs: [0; 8],
            mailbox: [Pieces::Void as u8; 64],
            score: 0,
            bound: 0,
            rule50: 0,
            bm: 0,
            stm: true,
            depth: 0,
            ep: 64,
            castle_mask: 0,
            #[cfg(feature = "hll")]
            hash: 0,
        }
    }
}

const KINGPOS_CASTLE: [u8; 2] = [2, 6];
const ROOKPOS_CASTLE: [u8; 2] = [3, 5];

impl Position {
    pub fn occupancy(&self) -> u64 {
        return self.bbs[0] | self.bbs[1];
    }
    pub fn piece(&self, idx: usize) -> Pieces {
        self.mailbox[idx].try_into().unwrap()
    }
    fn update_zobr(&mut self, _idx: usize) {
#[cfg(feature = "hll")]
{
        self.hash ^= ZOBRISTS[_idx];
}
    }

    #[allow(dead_code)]
    pub fn print(&self) {
        for i in 0..64 {
            if self.mailbox[i]/2 == 7 {
                print!("## ");
            }else {
                print!("{:2} ", self.mailbox[i]);
            }
            if i % 8 == 7 {
                print!("\n");
            }
        }
    }

    pub fn push(&mut self, idx: u32, piece: Pieces) {
        assert!(self.mailbox[idx as usize] == Pieces::Void as u8);
        self.mailbox[idx as usize] = piece as u8;
        self.bbs[piece.color() as usize] |= 1_u64 << idx;
        self.bbs[piece.piecetype() as usize + 2] |= 1_u64 << idx;
        self.update_zobr((piece as u32 * 64 + idx) as usize);
    }

    pub fn update_ep(&mut self, newep: usize) {
        self.update_zobr(newep + ZOBR_PASSANT);
        self.ep = newep as u8;
    }
    pub fn update_stm(&mut self, newstm: usize) {
        #[cfg(feature = "hll")]
        if newstm != 0 {
            self.hash ^= ZOBRISTS[ZOBR_TURN];
        }
        self.stm = newstm != 0;
    }
    pub fn turn_stm(&mut self) {
        self.update_zobr(ZOBR_TURN);
        self.stm = !self.stm;
    }
    pub fn update_castle_rights(&mut self, castle_idx: usize) {
        self.update_zobr(castle_idx + ZOBR_CASTLE);
        self.castle_mask |= 1 << castle_idx as u64;
    }

    fn erase(&mut self, idx: usize){
        let erased: Pieces = self.piece(idx);
        if erased != Pieces::Void {
            self.bbs[erased.piecetype() as usize + 2] ^= 1_u64 << idx;
            self.bbs[erased.color() as usize] ^= 1_u64 << idx;
            self.update_zobr(erased as usize * 64 + idx);
        }
        self.mailbox[idx] = Pieces::Void as u8;
    }

    fn erasesure(&mut self, idx: usize){
        let erased: Pieces = self.piece(idx);
        self.bbs[erased.piecetype() as usize + 2] ^= 1_u64 << idx;
        self.bbs[erased.color() as usize] ^= 1_u64 << idx;
        self.update_zobr(erased as usize * 64 + idx);
        self.mailbox[idx] = Pieces::Void as u8;
    }

    #[allow(dead_code)]
    fn verify(&self) -> bool { // verify zobrist hashing and bbs from mailbox
        #[cfg(feature = "hll")]
        {
            let mut zobr: u64 = 0;
            for i in 0..64 {
                if self.mailbox[i] != Pieces::Void as u8 {
                    zobr ^= ZOBRISTS[i + 64 * self.mailbox[i] as usize];
                }
            }
            zobr ^= ZOBRISTS[ZOBR_TURN] * self.stm as u64;
            let mut mask = self.castle_mask;
            while mask != 0 {
                zobr ^= ZOBRISTS[ZOBR_CASTLE + mask.trailing_zeros() as usize];
                mask &= mask-1;
            }
            zobr ^= ZOBRISTS[ZOBR_PASSANT + self.ep as usize];
            if zobr != self.hash {
                return false;
            }
        }
        let mut newbbs: [u64; 8] = [0; 8];
        for i in 0..64 {
            if self.mailbox[i] != Pieces::Void as u8 {
                newbbs[self.mailbox[i] as usize % 2] |= 1_u64 << i;
                newbbs[self.mailbox[i] as usize / 2 + 2] |= 1_u64 << i;
            }
        }
        for (n, o) in zip(newbbs, self.bbs) {
            if n != o {
                return false;
            }
        }
        true
    }

    pub fn play(&mut self, mv: u16) {
        let from = (mv & 0x3f) as u8;
        let to = ((mv >> 6) & 0x3f) as u8;
        let promo = (mv >> 12) & 0b11;
        let flag = mv >> 14;
        let piece: Pieces = self.piece(from as usize);
        assert!(piece != Pieces::Void);
        let capture: Pieces = self.piece(to as usize);
        let _reset: bool = (capture != Pieces::Void && flag != 2) || piece.piecetype() == PieceType::Pawn;
        // println!("from = {} to = {} piece = {} (piecetype = {}) type = {} promo = {} ep = {}", from, to, piece as u8, piece.piecetype() as u8, flag, promo, self.ep);
        // println!("=> poss = {} & {}", piece.piecetype() == 0, ((to^from)&0b10000) != 0);

        if flag == 0 {

            let masktofrom: u64 = 
                (1_u64 << to as u64) | (1_u64 << from as u64) | 
                (piece.piecetype() == PieceType::King) as u64 * (0xff_u64 << piece.color() as u64 * 56);
            
            let mut mask: u64 = self.castle_mask & masktofrom;
            self.castle_mask ^= mask;

            self.update_zobr(ZOBR_CASTLE + mask.trailing_zeros() as usize);
            mask &= mask.wrapping_sub(1);

            self.update_zobr(ZOBR_CASTLE + mask.trailing_zeros() as usize);

            self.update_zobr(self.ep as usize + ZOBR_PASSANT);
            self.ep = (piece.piecetype() == PieceType::Pawn && ((to ^ from) & 0b10_000) != 0) as u8 * to as u8;
            self.ep += 64*(self.ep == 0) as u8;
            
            self.update_zobr(self.ep as usize + ZOBR_PASSANT);

            self.erasesure(from as usize);
            self.erase(to as usize);
            self.push(to as u32, piece);
        } else if flag == 1 {
            assert!(self.ep < 64);
            assert!(self.mailbox[self.ep as usize]/2 == 0);
            self.erasesure(from as usize);
            self.erasesure(self.ep as usize);
            self.push(to as u32, piece);
            self.update_zobr(self.ep as usize + ZOBR_PASSANT);
            self.ep = 64;
        } else if flag == 2 {

            let to_king = (from&56) | KINGPOS_CASTLE[(from < to) as usize];
            let to_rook = (from&56) | ROOKPOS_CASTLE[(from < to) as usize];

            let mut mask: u64 = (0xff << (from & 56) as u64) & self.castle_mask;
            self.castle_mask ^= mask;
            self.update_zobr(ZOBR_CASTLE + mask.trailing_zeros() as usize);
            mask &= mask.wrapping_sub(1);
            self.update_zobr(ZOBR_CASTLE + mask.trailing_zeros() as usize);

            self.erasesure(from as usize);
            self.erasesure(to as usize);
            self.push(to_king as u32, piece);
            self.push(to_rook as u32, Pieces::from_pc_color(PieceType::Rook, piece.color()));

            self.update_zobr(self.ep as usize + ZOBR_PASSANT);
            self.ep = 64;
        } else {
            assert!(flag == 3);
            let to_piece = Pieces::from_pc_color(((promo+1) as u8).try_into().unwrap(), piece.color());
            if capture.piecetype() == PieceType::Rook && (self.castle_mask & (1 << to as u64) != 0){
                self.update_zobr(ZOBR_CASTLE + to as usize);
                self.castle_mask &= !(1 << to as u64);
            }
            self.update_zobr(self.ep as usize + ZOBR_PASSANT);
            self.ep = 64;

            self.erasesure(from as usize);
            self.erase(to as usize);
            self.push(to as u32, to_piece);
        }

        self.turn_stm();
        // if !self.verify(){
        //     println!("from = {} to = {} piece = {} (piecetype = {}) type = {} promo = {} capture = {}", from, to, piece as u8, piece.piecetype() as u8, flag, promo, capture as u8);
        //     unreachable!();
        // }
    }

    pub fn tobullet(&self) -> ChessBoard {
        // modified code from bulletformat, function from_raw

        let occ;
        if self.stm {
            occ = self.occupancy().swap_bytes();
        } else {
            occ = self.occupancy();
        }
        let mut pcs = [0; 16];

        let mut idx = 0;
        let mut occ2 = occ;
        while occ2 > 0 {
            let sq = occ2.trailing_zeros();
            occ2 &= occ2 - 1;

            let fullpiece = self.mailbox[sq as usize ^ 56 * self.stm as usize];
            let colour = (fullpiece % 2) ^ self.stm as u8;
            let piece = fullpiece / 2;

            let pc = (colour << 3) | piece as u8;

            pcs[idx / 2] |= pc << (4 * (idx & 1));

            idx += 1;
        }
        let ksq     = (self.bbs[ self.stm as usize] & self.bbs[7]).trailing_zeros() as u8 ^ (56 *  self.stm as u8);
        let opp_ksq = (self.bbs[!self.stm as usize] & self.bbs[7]).trailing_zeros() as u8 ^ (56 * !self.stm as u8);

        ChessBoard {
            occ,
            pcs,
            score:self.score,
            result:self.bound,
            ksq,
            opp_ksq,
            extra: [0; 3],
        }
    }
}