#[cfg(feature = "sgf-parse")]
use sgf_parse::{SgfNode, go::Prop};
use crate::*;
#[derive(Debug, Clone)]
pub struct AnalysisRequest {
pub rules: Rules,
pub komi: Option<f64>,
pub white_handicap_bonus: Option<Bonus>,
pub board_x_size: u8,
pub board_y_size: u8,
pub initial_stones: Option<Vec<(Player, Coord)>>,
pub initial_player: Option<Player>,
pub moves: Vec<(Player, Move)>,
pub max_visits: Option<u32>,
pub root_policy_temperature: Option<f64>,
pub root_fpu_reduction_max: Option<f64>,
pub analysis_pv_len: Option<usize>,
pub include_ownership: bool,
pub include_ownership_stdev: bool,
pub include_moves_ownership: bool,
pub include_moves_ownership_stdev: bool,
pub include_policy: bool,
pub include_pv_visits: bool,
pub include_no_result_value: bool,
pub avoid_moves: Option<Vec<RestrictedMoves>>,
pub allow_moves: Option<Vec<RestrictedMoves>>,
pub override_settings: Option<Config>,
pub report_during_search_every: Option<f64>,
pub priority: Option<i32>,
}
impl AnalysisRequest {
pub fn new(
rules: Rules,
board_x_size: u8,
board_y_size: u8,
moves: Vec<(Player, Move)>,
) -> Self {
Self {
rules,
komi: None,
white_handicap_bonus: None,
board_x_size,
board_y_size,
initial_stones: None,
initial_player: None,
moves,
max_visits: None,
root_policy_temperature: None,
root_fpu_reduction_max: None,
analysis_pv_len: None,
include_ownership: false,
include_ownership_stdev: false,
include_moves_ownership: false,
include_moves_ownership_stdev: false,
include_policy: false,
include_pv_visits: false,
include_no_result_value: false,
avoid_moves: None,
allow_moves: None,
override_settings: None,
report_during_search_every: None,
priority: None,
}
}
pub fn into_engine_request(
self,
id: String,
analyze_turns: Vec<usize>,
priorities: Option<Vec<i32>>,
) -> engine::AnalysisRequest {
engine::AnalysisRequest {
id,
rules: self.rules,
komi: self.komi,
white_handicap_bonus: self.white_handicap_bonus,
board_x_size: self.board_x_size,
board_y_size: self.board_y_size,
initial_stones: self.initial_stones.map(|s| {
s.into_iter()
.map(|(p, c)| (p, c.to_gtp(self.board_y_size)))
.collect()
}),
initial_player: self.initial_player,
moves: self
.moves
.into_iter()
.map(|(p, m)| (p, m.to_gtp(self.board_y_size)))
.collect(),
analyze_turns: Some(analyze_turns),
max_visits: self.max_visits,
root_policy_temperature: self.root_policy_temperature,
root_fpu_reduction_max: self.root_fpu_reduction_max,
analysis_pv_len: self.analysis_pv_len,
include_ownership: self.include_ownership,
include_ownership_stdev: self.include_ownership_stdev,
include_moves_ownership: self.include_moves_ownership,
include_moves_ownership_stdev: self.include_moves_ownership_stdev,
include_policy: self.include_policy,
include_pv_visits: self.include_pv_visits,
include_no_result_value: self.include_no_result_value,
avoid_moves: self.avoid_moves.map(|m| {
m.into_iter()
.map(|rm| rm.into_engine_restricted_moves(self.board_y_size))
.collect()
}),
allow_moves: self.allow_moves.map(|m| {
m.into_iter()
.map(|rm| rm.into_engine_restricted_moves(self.board_y_size))
.collect()
}),
override_settings: self.override_settings,
report_during_search_every: self.report_during_search_every,
priority: self.priority,
priorities,
}
}
pub fn with_komi(mut self, komi: f64) -> Self {
self.komi = Some(komi);
self
}
pub fn with_white_handicap_bonus(mut self, bonus: Bonus) -> Self {
self.white_handicap_bonus = Some(bonus);
self
}
pub fn with_initial_stones(mut self, initial_stones: Vec<(Player, Coord)>) -> Self {
self.initial_stones = Some(initial_stones);
self
}
pub fn with_initial_player(mut self, initial_player: Player) -> Self {
self.initial_player = Some(initial_player);
self
}
pub fn with_max_visits(mut self, max_visits: u32) -> Self {
self.max_visits = Some(max_visits);
self
}
pub fn with_root_policy_temperature(mut self, root_policy_temperature: f64) -> Self {
self.root_policy_temperature = Some(root_policy_temperature);
self
}
pub fn with_root_fpu_reduction_max(mut self, root_fpu_reduction_max: f64) -> Self {
self.root_fpu_reduction_max = Some(root_fpu_reduction_max);
self
}
pub fn with_analysis_pv_len(mut self, analysis_pv_len: usize) -> Self {
self.analysis_pv_len = Some(analysis_pv_len);
self
}
pub fn with_ownership(mut self) -> Self {
self.include_ownership = true;
self
}
pub fn with_ownership_stdev(mut self) -> Self {
self.include_ownership_stdev = true;
self
}
pub fn with_moves_ownership(mut self) -> Self {
self.include_moves_ownership = true;
self
}
pub fn with_moves_ownership_stdev(mut self) -> Self {
self.include_moves_ownership_stdev = true;
self
}
pub fn with_policy(mut self) -> Self {
self.include_policy = true;
self
}
pub fn with_pv_visits(mut self) -> Self {
self.include_pv_visits = true;
self
}
pub fn with_no_result_value(mut self) -> Self {
self.include_no_result_value = true;
self
}
pub fn with_avoid_moves(mut self, avoid_moves: Vec<RestrictedMoves>) -> Self {
self.avoid_moves = Some(avoid_moves);
self
}
pub fn with_allow_moves(mut self, allow_moves: Vec<RestrictedMoves>) -> Self {
self.allow_moves = Some(allow_moves);
self
}
pub fn with_override_settings(mut self, config: Config) -> Self {
self.override_settings = Some(config);
self
}
pub fn with_report_during_search_every(mut self, seconds: f64) -> Self {
self.report_during_search_every = Some(seconds);
self
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = Some(priority);
self
}
}
#[cfg(feature = "sgf-parse")]
impl From<&SgfNode<Prop>> for AnalysisRequest {
fn from(root: &SgfNode<Prop>) -> Self {
let (width, height) = match root.get_property("SZ") {
Some(Prop::SZ((w, h))) => (*w, *h),
_ => (19, 19),
};
let komi = match root.get_property("KM") {
Some(Prop::KM(k)) => Some(*k),
_ => None,
};
let rules = match root.get_property("RU") {
Some(Prop::RU(r)) => Rules::Named(r.text.clone()),
_ => match komi {
Some(k) if k > 6.5 => Rules::chinese(),
_ => Rules::japanese(),
},
};
let moves: Vec<(Player, Move)> = root
.main_variation()
.filter_map(|m| match m.get_move() {
Some(Prop::B(m)) => Some((Player::Black, (*m).into())),
Some(Prop::W(m)) => Some((Player::White, (*m).into())),
_ => None,
})
.collect();
let mut initial_stones: Vec<(Player, Coord)> = vec![];
if let Some(Prop::AB(ps)) = root.get_property("AB") {
initial_stones.extend(ps.iter().map(|p| (Player::Black, (*p).into())));
}
if let Some(Prop::AW(ps)) = root.get_property("AW") {
initial_stones.extend(ps.iter().map(|p| (Player::White, (*p).into())));
}
let initial_player: Option<Player> = match root.get_property("PL") {
Some(Prop::PL(p)) => Some((*p).into()),
_ => None,
};
let mut request = Self::new(rules, width, height, moves);
request.komi = komi;
if !initial_stones.is_empty() {
request = request.with_initial_stones(initial_stones);
}
request.initial_player = initial_player;
request
}
}
#[derive(Debug, Clone)]
pub struct RestrictedMoves {
pub player: Player,
pub moves: Vec<Move>,
pub until_depth: u32,
}
impl RestrictedMoves {
pub fn into_engine_restricted_moves(self, height: u8) -> engine::RestrictedMoves {
engine::RestrictedMoves {
player: self.player,
moves: self.moves.into_iter().map(|m| m.to_gtp(height)).collect(),
until_depth: self.until_depth,
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "sgf-parse")]
mod sgf {
use std::collections::HashSet;
use crate::{AnalysisRequest, Coord, Move, Player, Rules};
#[test]
fn from_sgf() {
let sgf = "(;;B[pd](;B[dp];W[])(;W[dp];B[pp]))";
let request =
AnalysisRequest::from(sgf_parse::go::parse(sgf).unwrap().first().unwrap());
assert_eq!(request.rules, Rules::japanese());
assert_eq!(request.komi, None);
assert_eq!(request.board_x_size, 19);
assert_eq!(request.board_y_size, 19);
assert_eq!(request.initial_stones, None);
assert_eq!(request.initial_player, None);
assert_eq!(
request.moves,
vec![
(Player::Black, Move::Move(Coord(15, 3))),
(Player::Black, Move::Move(Coord(3, 15))),
(Player::White, Move::Pass),
]
);
}
#[test]
fn size() {
let sgf = "(;SZ[9:13];B[aa];W[im])";
let request =
AnalysisRequest::from(sgf_parse::go::parse(sgf).unwrap().first().unwrap());
assert_eq!(request.rules, Rules::japanese());
assert_eq!(request.komi, None);
assert_eq!(request.board_x_size, 9);
assert_eq!(request.board_y_size, 13);
assert_eq!(request.initial_stones, None);
assert_eq!(request.initial_player, None);
assert_eq!(
request.moves,
vec![
(Player::Black, Move::Move(Coord(0, 0))),
(Player::White, Move::Move(Coord(8, 12))),
]
);
}
#[test]
fn komi() {
let sgf = "(;KM[7.5];B[pd];W[dp])";
let request =
AnalysisRequest::from(sgf_parse::go::parse(sgf).unwrap().first().unwrap());
assert_eq!(request.rules, Rules::chinese());
assert_eq!(request.komi, Some(7.5));
assert_eq!(request.board_x_size, 19);
assert_eq!(request.board_y_size, 19);
assert_eq!(request.initial_stones, None);
assert_eq!(request.initial_player, None);
assert_eq!(
request.moves,
vec![
(Player::Black, Move::Move(Coord(15, 3))),
(Player::White, Move::Move(Coord(3, 15))),
]
);
}
#[test]
fn rules() {
let sgf = "(;RU[aga];B[pd];W[dp])";
let request =
AnalysisRequest::from(sgf_parse::go::parse(sgf).unwrap().first().unwrap());
assert_eq!(request.rules, Rules::Named("aga".to_string()));
assert_eq!(request.komi, None);
assert_eq!(request.board_x_size, 19);
assert_eq!(request.board_y_size, 19);
assert_eq!(request.initial_stones, None);
assert_eq!(request.initial_player, None);
assert_eq!(
request.moves,
vec![
(Player::Black, Move::Move(Coord(15, 3))),
(Player::White, Move::Move(Coord(3, 15))),
]
);
}
#[test]
fn initial_stones() {
let sgf = "(;AB[pd][dp]AW[dd][pp];B[cc];W[qc])";
let request =
AnalysisRequest::from(sgf_parse::go::parse(sgf).unwrap().first().unwrap());
assert_eq!(request.rules, Rules::japanese());
assert_eq!(request.komi, None);
assert_eq!(request.board_x_size, 19);
assert_eq!(request.board_y_size, 19);
assert_eq!(
request
.initial_stones
.map(|s| HashSet::<(Player, Coord)>::from_iter(s)),
Some(HashSet::from_iter(vec![
(Player::Black, Coord(15, 3)),
(Player::Black, Coord(3, 15)),
(Player::White, Coord(3, 3)),
(Player::White, Coord(15, 15)),
]))
);
assert_eq!(request.initial_player, None);
assert_eq!(
request.moves,
vec![
(Player::Black, Move::Move(Coord(2, 2))),
(Player::White, Move::Move(Coord(16, 2))),
]
);
}
#[test]
fn initial_player() {
let sgf = "(;PL[W];B[pd];W[dp])";
let request =
AnalysisRequest::from(sgf_parse::go::parse(sgf).unwrap().first().unwrap());
assert_eq!(request.rules, Rules::japanese());
assert_eq!(request.komi, None);
assert_eq!(request.board_x_size, 19);
assert_eq!(request.board_y_size, 19);
assert_eq!(request.initial_stones, None);
assert_eq!(request.initial_player, Some(Player::White));
assert_eq!(
request.moves,
vec![
(Player::Black, Move::Move(Coord(15, 3))),
(Player::White, Move::Move(Coord(3, 15))),
]
);
}
}
}