usiagent 0.7.0

USIAgent is a framework for Shogi AI development that supports the usi protocol.
Documentation
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::ops::{Add, AddAssign};
use std::path::Path;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use usiagent::math::Prng;
use usiagent::movepick::{MovePicker, RandomPicker};
use usiagent::protocol::{PositionParser};
use usiagent::rule::{EvasionsAll, LegalMove, NonEvasionsAll, Rule};
use usiagent::rule::State;
use usiagent::shogi::{MochigomaCollections, Teban};

#[derive(Default,Debug,PartialEq,Eq)]
struct PerftResult {
    pub nodes:usize,
    pub captures:usize,
    pub promotions:usize,
    pub checks:usize,
    pub mates:usize
}
impl Add for PerftResult {
    type Output = Self;
    fn add(self, rhs: Self) -> Self::Output {
        PerftResult {
            nodes: self.nodes + rhs.nodes,
            captures: self.captures + rhs.captures,
            promotions: self.promotions + rhs.promotions,
            checks: self.checks + rhs.checks,
            mates: self.mates + rhs.mates
        }
    }
}
impl AddAssign for PerftResult {
    fn add_assign(&mut self, rhs: Self) {
        *self = PerftResult {
            nodes: self.nodes + rhs.nodes,
            captures: self.captures + rhs.captures,
            promotions: self.promotions + rhs.promotions,
            checks: self.checks + rhs.checks,
            mates: self.mates + rhs.mates
        }
    }
}
impl From<String> for PerftResult {
    fn from(value: String) -> Self {
        let fields: Vec<_> = value.split(',').map(|f| f.parse().unwrap()).collect();

        PerftResult {
            nodes: fields[0],
            captures: fields[1],
            promotions: fields[2],
            checks: fields[3],
            mates: fields[4]
        }
    }
}
trait PerftSolver {
    fn perft(&self,teban:Teban,state:&State,mc:&MochigomaCollections,m:Option<LegalMove>,depth:usize) -> PerftResult;
}
struct PerftSolverByEvasions;

impl PerftSolver for PerftSolverByEvasions {
    fn perft(&self,teban:Teban,state: &State, mc:&MochigomaCollections, m: Option<LegalMove>, depth: usize) -> PerftResult {
        let mut result = PerftResult::default();

        if depth == 0 {
            result.nodes += 1;

            match m {
                Some(LegalMove::To(m)) => {
                    if m.obtained().is_some() {
                        result.captures += 1;
                    }
                    if m.is_nari() {
                        result.promotions += 1;
                    }
                },
                _ => ()
            };

            if Rule::in_check(teban.opposite(),state) {
                result.checks += 1;

                let mut rng = rand::thread_rng();
                let mut rng = XorShiftRng::from_seed(rng.gen());

                let mut buffer = RandomPicker::new(Prng::new(rng.gen()));

                Rule::generate_moves::<EvasionsAll>(teban, state, mc, &mut buffer).unwrap();

                if buffer.len() == 0 {
                    result.mates += 1;
                }
            }
        } else {
            let mut rng = rand::thread_rng();
            let mut rng = XorShiftRng::from_seed(rng.gen());

            let mut buffer = RandomPicker::new(Prng::new(rng.gen()));

            if Rule::in_check(teban.opposite(),state) {
                Rule::generate_moves::<EvasionsAll>(teban, state, mc, &mut buffer).unwrap();
            } else {
                Rule::generate_moves::<NonEvasionsAll>(teban, state, mc, &mut buffer).unwrap();
            }

            for m in buffer {
                let next = Rule::apply_move_none_check(state, teban, mc, m.to_applied_move());

                match next {
                    (state, mc, _) => {
                        if !Rule::in_check(teban.opposite(),&state) {
                            result += self.perft(teban.opposite(),&state,&mc,Some(m),depth - 1);
                        }
                    }
                }
            }
        }

        result
    }
}
#[ignore]
#[test]
fn test_perft_by_evasions() {
    let position_parser = PositionParser::new();

    for (n,(sfen,answer)) in BufReader::new(
        File::open(
            Path::new("data").join("floodgate").join("generatemoves").join("perft_sfen.txt")
        ).unwrap()).lines().zip(BufReader::new(
        File::open(
            Path::new("data").join("floodgate").join("generatemoves").join("answer_perft.txt")
        ).unwrap()).lines()).enumerate() {

        let expected = PerftResult::from(answer.unwrap());

        let sfen = format!("sfen {}",sfen.unwrap());

        let (teban, banmen, mc, _, _) = position_parser.parse(&sfen.split(' ').collect::<Vec<&str>>()).unwrap().extract();

        let state = State::new(banmen);

        let solver = PerftSolverByEvasions;

        let result = solver.perft(teban,&state,&mc,None,4);

        println!("line {} done...",n);

        if &expected != &result {
            println!("line {}: {}",n, sfen);
        }

        assert_eq!(expected, result);
    }
}
#[ignore]
#[test]
fn test_perft_by_evasions_lightweight() {
    let position_parser = PositionParser::new();

    for (n,(sfen,answer)) in BufReader::new(
        File::open(
            Path::new("data").join("floodgate").join("generatemoves").join("perft_sfen.txt")
        ).unwrap()).lines().zip(BufReader::new(
        File::open(
            Path::new("data").join("floodgate").join("generatemoves").join("answer_perft.txt")
        ).unwrap()).lines()).enumerate().take(10) {

        let expected = PerftResult::from(answer.unwrap());

        let sfen = format!("sfen {}",sfen.unwrap());

        let (teban, banmen, mc, _, _) = position_parser.parse(&sfen.split(' ').collect::<Vec<&str>>()).unwrap().extract();

        let state = State::new(banmen);

        let solver = PerftSolverByEvasions;

        let result = solver.perft(teban,&state,&mc,None,4);

        println!("line {} done...",n);

        if &expected != &result {
            println!("line {}: {}",n, sfen);
        }

        assert_eq!(expected, result);
    }
}
#[ignore]
#[test]
fn test_perft_once() {
    let position_parser = PositionParser::new();

    for (n,(sfen,answer)) in BufReader::new(
        File::open(
            Path::new("data").join("floodgate").join("generatemoves").join("perft_sfen.txt")
        ).unwrap()).lines().zip(BufReader::new(
        File::open(
            Path::new("data").join("floodgate").join("generatemoves").join("answer_perft.txt")
        ).unwrap()).lines()).enumerate().take(1) {

        let expected = PerftResult::from(answer.unwrap());

        let sfen = format!("sfen {}",sfen.unwrap());

        let (teban, banmen, mc, _, _) = position_parser.parse(&sfen.split(' ').collect::<Vec<&str>>()).unwrap().extract();

        let state = State::new(banmen);

        let solver = PerftSolverByEvasions;

        let result = solver.perft(teban,&state,&mc,None,4);

        println!("line {} done...",n);

        if &expected != &result {
            println!("line {}: {}",n, sfen);
        }

        assert_eq!(expected, result);
    }
}