From 71789da16d00f4f35d739ed3602ba1626dad9010 Mon Sep 17 00:00:00 2001 From: sugyan Date: Sun, 10 Jul 2022 00:09:27 +0900 Subject: [PATCH 01/12] Update yasai to 0.5.0 --- Cargo.lock | 24 +++++++++-- Cargo.toml | 4 +- src/backend/yasai.rs | 100 ++++++++++++------------------------------- 3 files changed, 51 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69897dc..391bd64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,21 @@ dependencies = [ "nom 7.1.0", ] +[[package]] +name = "shogi_core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c99c07667dfb58bac4cd9156e3d1ead9f3c78223a282301bc51f49b4efcf2d" + +[[package]] +name = "shogi_usi_parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f39adab2055cf0bc5c9523b237e14ad9aba385bc7ebf90c9385876a48cef8c62" +dependencies = [ + "shogi_core", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -482,6 +497,8 @@ dependencies = [ "dfpn-extended", "shogi", "shogi-converter", + "shogi_core", + "shogi_usi_parser", "thiserror", "yasai", ] @@ -543,11 +560,12 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" [[package]] name = "yasai" -version = "0.2.0" -source = "git+https://github.com/sugyan/yasai?tag=0.1.3#ed6cb8507d23e79a775c7db51abbbfedd93a7e34" +version = "0.5.0" +source = "git+https://github.com/sugyan/yasai?tag=0.5.0#ef8e5cb81fdd5b216432d6e09d67772ddf27207e" dependencies = [ "arrayvec 0.7.2", - "bitintr", + "cfg-if", "once_cell", "rand", + "shogi_core", ] diff --git a/Cargo.toml b/Cargo.toml index 7be6483..22619fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,9 @@ csa = "1.0.1" clap = { version = "3.1.1", features = ["derive"] } shogi = "0.12.2" thiserror = "1.0.30" -yasai = { git = "https://github.com/sugyan/yasai", tag = "0.1.3" } +yasai = { git = "https://github.com/sugyan/yasai", tag = "0.5.0", features = ["simd"] } +shogi_core = "0.1.4" +shogi_usi_parser = "0.1.0" [profile.release] lto = true diff --git a/src/backend/yasai.rs b/src/backend/yasai.rs index a16e0cc..9e3ed2e 100644 --- a/src/backend/yasai.rs +++ b/src/backend/yasai.rs @@ -1,66 +1,17 @@ use crate::solver::CalculateResult; use dfpn::Node; -use yasai::{Move, MoveType, PieceType, Position}; +use shogi_core::{Hand, Move, PartialPosition, ToUsi}; +use shogi_usi_parser::FromUsi; +use yasai::Position; pub struct YasaiPosition(Position); impl From<&str> for YasaiPosition { fn from(sfen: &str) -> Self { - let mut pos = shogi::Position::new(); - pos.set_sfen(sfen).expect("failed to set sfen"); - - let board = yasai::Square::ALL.map(|sq| { - pos.piece_at(shogi::Square::from_index(sq.index() as u8).unwrap()) - .map(|p| { - let color = match p.color { - shogi::Color::Black => yasai::Color::Black, - shogi::Color::White => yasai::Color::White, - }; - let piece_type = match p.piece_type { - shogi::PieceType::King => yasai::PieceType::OU, - shogi::PieceType::Rook => yasai::PieceType::HI, - shogi::PieceType::Bishop => yasai::PieceType::KA, - shogi::PieceType::Gold => yasai::PieceType::KI, - shogi::PieceType::Silver => yasai::PieceType::GI, - shogi::PieceType::Knight => yasai::PieceType::KE, - shogi::PieceType::Lance => yasai::PieceType::KY, - shogi::PieceType::Pawn => yasai::PieceType::FU, - shogi::PieceType::ProRook => yasai::PieceType::RY, - shogi::PieceType::ProBishop => yasai::PieceType::UM, - shogi::PieceType::ProSilver => yasai::PieceType::NG, - shogi::PieceType::ProKnight => yasai::PieceType::NK, - shogi::PieceType::ProLance => yasai::PieceType::NY, - shogi::PieceType::ProPawn => yasai::PieceType::TO, - }; - yasai::Piece::from_cp(color, piece_type) - }) - }); - let mut hand_nums = [[0; yasai::PieceType::NUM_HAND]; yasai::Color::NUM]; - for c in yasai::Color::ALL { - for (i, &pt) in yasai::PieceType::ALL_HAND.iter().enumerate() { - let piece_type = match pt { - yasai::PieceType::FU => shogi::PieceType::Pawn, - yasai::PieceType::KY => shogi::PieceType::Lance, - yasai::PieceType::KE => shogi::PieceType::Knight, - yasai::PieceType::GI => shogi::PieceType::Silver, - yasai::PieceType::KI => shogi::PieceType::Gold, - yasai::PieceType::KA => shogi::PieceType::Bishop, - yasai::PieceType::HI => shogi::PieceType::Rook, - _ => unreachable!(), - }; - let color = match c { - yasai::Color::Black => shogi::Color::Black, - yasai::Color::White => shogi::Color::White, - }; - hand_nums[c.index()][i] = pos.hand(shogi::Piece { piece_type, color }); - } - } - let side_to_move = match pos.side_to_move() { - shogi::Color::Black => yasai::Color::Black, - shogi::Color::White => yasai::Color::White, - }; - let ply = pos.ply() as u32; - Self(Position::new(board, hand_nums, side_to_move, ply)) + let s = String::from("sfen ") + sfen; + Self(Position::new( + PartialPosition::from_usi(&s).expect("failed to set sfen"), + )) } } @@ -92,24 +43,22 @@ impl dfpn::Position for YasaiPosition { impl CalculateResult for YasaiPosition { fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize) { let (mut ret, mut len) = (Vec::new(), moves.len()); - let mut total_hands = PieceType::ALL_HAND - .map(|pt| self.0.hand(!self.0.side_to_move()).num(pt)) - .iter() + let mut total_hands = Hand::all_hand_pieces() + .filter_map(|pk| self.0.hand(self.0.side_to_move().flip()).count(pk)) .sum::(); // 最終2手が「合駒→同」の場合は、合駒無効の詰みなので削除 while len > 2 { if let ( - MoveType::Drop { + Move::Drop { to: drop_to, piece: _, }, - MoveType::Normal { + Move::Normal { from: _, to: move_to, - is_promotion: _, - piece: _, + promote: _, }, - ) = (moves[len - 2].move_type(), moves[len - 1].move_type()) + ) = (moves[len - 2], moves[len - 1]) { if drop_to == move_to { len -= 2; @@ -126,24 +75,29 @@ impl CalculateResult for YasaiPosition { let mut zero = false; for (i, m) in moves.iter().enumerate().take(len) { if i % 2 == 0 { - if let MoveType::Normal { + if let Move::Normal { from: _, to, - is_promotion: _, - piece: _, - } = m.move_type() + promote: _, + } = m { - if let Some(piece_type) = drops[to.index()].take() { - if self.0.hand(!self.0.side_to_move()).num(piece_type) > 0 { + if let Some(piece_type) = drops[to.array_index()].take() { + if self + .0 + .hand(self.0.side_to_move().flip()) + .count(piece_type) + .unwrap_or_default() + > 0 + { // TODO: 候補から除外したいが このパターンだけが候補になる場合もある zero = true; } } } - } else if let MoveType::Drop { to, piece } = m.move_type() { - drops[to.index()] = Some(piece.piece_type()); + } else if let Move::Drop { to, piece } = m { + drops[to.array_index()] = Some(piece.piece_kind()); } - ret.push(m.to_string()); + ret.push(m.to_usi_owned()); } let score = if zero { 0 From 9dcf1ff4cb69d51342dc765b7858058544b5e567 Mon Sep 17 00:00:00 2001 From: sugyan Date: Sun, 10 Jul 2022 23:54:10 +0900 Subject: [PATCH 02/12] Remove shogi backend --- benches/bench.rs | 29 +---- src/backend.rs | 2 - src/backend/shogi.rs | 254 ------------------------------------------- src/lib.rs | 2 +- src/main.rs | 16 +-- src/solver.rs | 132 +++++++--------------- 6 files changed, 43 insertions(+), 392 deletions(-) delete mode 100644 src/backend/shogi.rs diff --git a/benches/bench.rs b/benches/bench.rs index 9d41966..c8080bd 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -3,8 +3,7 @@ extern crate test; use dfpn::search::Search; use dfpn::DefaultSearcher; use dfpn::{impl_hashmap_table::HashMapTable, impl_vec_table::VecTable}; -use shogi::bitboard::Factory; -use tsumeshogi_solver::backend::{ShogiPosition, YasaiPosition}; +use tsumeshogi_solver::backend::YasaiPosition; fn test_cases() -> Vec { vec![ @@ -57,29 +56,3 @@ fn bench_yasai_vec(b: &mut test::Bencher) { } }) } - -#[bench] -fn bench_shogi_hashmap(b: &mut test::Bencher) { - Factory::init(); - - b.iter(|| { - for sfen in test_cases() { - let mut searcher = - DefaultSearcher::<_, HashMapTable>::new(ShogiPosition::from(sfen.as_str())); - searcher.dfpn_search(); - } - }) -} - -#[bench] -fn bench_shogi_vec(b: &mut test::Bencher) { - Factory::init(); - - b.iter(|| { - for sfen in test_cases() { - let mut searcher = - DefaultSearcher::<_, VecTable>::new(ShogiPosition::from(sfen.as_str())); - searcher.dfpn_search(); - } - }) -} diff --git a/src/backend.rs b/src/backend.rs index 5066daa..dc45b4d 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,5 +1,3 @@ -mod shogi; mod yasai; -pub use self::shogi::ShogiPosition; pub use self::yasai::YasaiPosition; diff --git a/src/backend/shogi.rs b/src/backend/shogi.rs deleted file mode 100644 index 89088e7..0000000 --- a/src/backend/shogi.rs +++ /dev/null @@ -1,254 +0,0 @@ -use crate::solver::CalculateResult; -use dfpn::{Node, Position as _}; -use shogi::{Color, Move, MoveError, Piece, PieceType, Position, Square}; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; - -pub struct ShogiPosition(Position); - -impl ShogiPosition { - fn try_legal_move(&mut self, m: Move, node: Node) -> Result { - match self.0.make_move(m) { - Ok(_) => { - let mut hash = None; - if node == Node::And || self.0.in_check(self.0.side_to_move()) { - hash = Some(self.hash_key()); - } - self.0.unmake_move().expect("failed to unmake move"); - if let Some(h) = hash { - Ok(h) - } else { - Err(MoveError::Inconsistent("Not legal move for tsumeshogi")) - } - } - Err(e) => Err(e), - } - } - fn p8(p: Piece) -> u8 { - let piece_type = match p.piece_type { - PieceType::King => 0, - PieceType::Rook => 1, - PieceType::Bishop => 2, - PieceType::Gold => 3, - PieceType::Silver => 4, - PieceType::Knight => 5, - PieceType::Lance => 6, - PieceType::Pawn => 7, - PieceType::ProRook => 8, - PieceType::ProBishop => 9, - PieceType::ProSilver => 10, - PieceType::ProKnight => 11, - PieceType::ProLance => 12, - PieceType::ProPawn => 13, - }; - let color = match p.color { - Color::Black => 0, - Color::White => 14, - }; - piece_type + color - } -} - -impl From<&str> for ShogiPosition { - fn from(sfen: &str) -> Self { - let mut pos = Position::new(); - pos.set_sfen(sfen).expect("failed to set sfen"); - ShogiPosition(pos) - } -} - -impl dfpn::Position for ShogiPosition { - type M = Move; - - fn hash_key(&self) -> u64 { - let mut s = DefaultHasher::new(); - self.hash(&mut s); - s.finish() - } - fn generate_legal_moves(&mut self, node: dfpn::Node) -> Vec<(Self::M, u64)> { - let mut moves = Vec::new(); - // normal moves - for from in *self.0.player_bb(self.0.side_to_move()) { - if let Some(p) = *self.0.piece_at(from) { - for to in self.0.move_candidates(from, p) { - for promote in [true, false] { - let m = Move::Normal { from, to, promote }; - if let Ok(h) = self.try_legal_move(m, node) { - moves.push((m, h)); - } - } - } - } - } - // drop moves - let target_color = match node { - Node::Or => self.0.side_to_move().flip(), - Node::And => self.0.side_to_move(), - }; - if let Some(king_sq) = self.0.find_king(target_color) { - match node { - Node::Or => { - for piece_type in PieceType::iter().filter(|pt| pt.is_hand_piece()) { - if self.0.hand(Piece { - piece_type, - color: target_color.flip(), - }) == 0 - { - continue; - } - // 玉をその駒で狙える位置のみ探索 - for to in self.0.move_candidates( - king_sq, - Piece { - piece_type, - color: target_color, - }, - ) { - let m = Move::Drop { to, piece_type }; - if let Ok(h) = self.try_legal_move(m, node) { - moves.push((m, h)); - } - } - } - } - Node::And => { - // 玉から飛車角で狙われ得る位置の候補 - let mut candidates = &self.0.move_candidates( - king_sq, - Piece { - piece_type: PieceType::Rook, - color: target_color, - }, - ) | &self.0.move_candidates( - king_sq, - Piece { - piece_type: PieceType::Bishop, - color: target_color, - }, - ); - for piece_type in PieceType::iter().filter(|pt| pt.is_hand_piece()) { - if self.0.hand(Piece { - piece_type, - color: target_color, - }) == 0 - { - continue; - } - for to in candidates { - let m = Move::Drop { to, piece_type }; - match self.try_legal_move(m, node) { - Ok(h) => moves.push((m, h)), - Err(MoveError::InCheck) => { - // 合駒として機能しない位置は候補から外す - candidates.clear_at(to); - } - Err(_) => { - // ignore - } - } - } - } - } - } - } - moves - } - fn do_move(&mut self, m: Self::M) { - self.0.make_move(m).expect("failed to make move"); - } - fn undo_move(&mut self, _m: Self::M) { - self.0.unmake_move().expect("failed to unmake move"); - } -} - -impl Hash for ShogiPosition { - fn hash(&self, state: &mut H) { - Square::iter().for_each(|sq| { - self.0 - .piece_at(sq) - .map_or(28, ShogiPosition::p8) - .hash(state); - }); - PieceType::iter().for_each(|piece_type| { - Color::iter().for_each(|color| self.0.hand(Piece { piece_type, color }).hash(state)) - }); - match self.0.side_to_move() { - Color::Black => 0.hash(state), - Color::White => 1.hash(state), - }; - } -} - -impl CalculateResult for ShogiPosition { - fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize) { - let (mut ret, mut len) = (Vec::new(), moves.len()); - let mut total_hands = PieceType::iter() - .filter_map(|piece_type| { - if piece_type.is_hand_piece() { - Some(self.0.hand(Piece { - piece_type, - color: self.0.side_to_move().flip(), - })) - } else { - None - } - }) - .sum::(); - while len > 2 { - if let ( - Move::Drop { - to: drop_to, - piece_type: _, - }, - Move::Normal { - from: _, - to: move_to, - promote: _, - }, - ) = (moves[len - 2], moves[len - 1]) - { - if drop_to == move_to { - len -= 2; - total_hands -= 1; - continue; - } - } - break; - } - // 1. 玉方が合駒として打った駒が後に取られて - // 2. 最終的に攻方の持駒に入っている - // を満たす場合、無駄合駒とみなす - let mut drops = vec![None; 81]; - let mut zero = false; - for (i, m) in moves.iter().enumerate().take(len) { - if i % 2 == 0 { - if let Move::Normal { - from: _, - to, - promote: _, - } = m - { - if let Some(piece_type) = drops[to.index()].take() { - if self.0.hand(Piece { - piece_type, - color: self.0.side_to_move().flip(), - }) > 0 - { - // TODO: 候補から除外したいが このパターンだけが候補になる場合もある - zero = true; - } - } - } - } else if let Move::Drop { to, piece_type } = m { - drops[to.index()] = Some(*piece_type); - } - ret.push(m.to_string()); - } - let score = if zero { - 0 - } else { - len * 100 - total_hands as usize - }; - (ret, score) - } -} diff --git a/src/lib.rs b/src/lib.rs index 8a0450d..a12ba59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ pub mod backend; mod solver; -pub use solver::{solve, Backend}; +pub use solver::solve; diff --git a/src/main.rs b/src/main.rs index 2506eab..83aa28a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ use clap::{ArgEnum, Parser}; use csa::{parse_csa, CsaError}; -use shogi::{bitboard::Factory, Position}; +use shogi::Position; use shogi_converter::kif_converter::{parse_kif, KifError}; use shogi_converter::Record; use std::io::{BufRead, Read}; use std::time::{Duration, Instant}; use std::{fs::File, str}; use thiserror::Error; -use tsumeshogi_solver::{solve, Backend}; +use tsumeshogi_solver::solve; #[derive(Error, Debug)] enum ParseError { @@ -58,9 +58,6 @@ struct Args { /// Time limit to solve (seconds) #[clap(short, long)] timeout: Option, - /// Backend implementation - #[clap(long = "backend", arg_enum, value_name = "BACKEND", default_value_t = Backend::Yasai)] - backend: Backend, /// Input files or SFEN strings #[clap(required(true))] inputs: Vec, @@ -75,9 +72,6 @@ enum Format { fn main() -> Result<(), std::io::Error> { let args = Args::parse(); - if args.backend == Backend::Shogi { - Factory::init(); - } match args.format { Format::Sfen => run_sfen(&args), Format::Csa => run_parse(CsaParser, &args), @@ -164,11 +158,7 @@ fn run(sfen: &str, input: &str, args: &Args) { let now = Instant::now(); println!( "{:?}", - solve( - sfen, - args.backend, - args.timeout.map(Duration::from_secs_f32), - ) + solve(sfen, args.timeout.map(Duration::from_secs_f32),) ); if args.verbose { println!("elapsed: {:?}", now.elapsed()); diff --git a/src/solver.rs b/src/solver.rs index defbd7c..83321cd 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,5 +1,4 @@ -use crate::backend::{ShogiPosition, YasaiPosition}; -use clap::ArgEnum; +use crate::backend::YasaiPosition; use dfpn::search::Search; use dfpn::{Node, Position, INF}; use dfpn_extended::{CancelableSearcher, CanceledError}; @@ -10,27 +9,8 @@ pub(crate) trait CalculateResult: Position { fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize); } -#[derive(Clone, Copy, Debug, ArgEnum, PartialEq, Eq)] -pub enum Backend { - Shogi, - Yasai, -} - -impl Backend { - pub fn all() -> [Backend; 2] { - [Backend::Shogi, Backend::Yasai] - } -} - -pub fn solve( - sfen: &str, - backend: Backend, - timeout: Option, -) -> Result, CanceledError> { - match backend { - Backend::Shogi => solve_impl(ShogiPosition::from(sfen), timeout), - Backend::Yasai => solve_impl(YasaiPosition::from(sfen), timeout), - } +pub fn solve(sfen: &str, timeout: Option) -> Result, CanceledError> { + solve_impl(YasaiPosition::from(sfen), timeout) } fn solve_impl

(pos: P, timeout: Option) -> Result, CanceledError> @@ -90,7 +70,6 @@ fn search_all_mates

( #[cfg(test)] mod tests { use super::solve; - use crate::Backend; use shogi::bitboard::Factory; use std::time::Duration; @@ -134,20 +113,13 @@ mod tests { // TODO: "ln3kgRl/2s1g2p1/2ppppn1p/p5p2/6b2/P3P4/1+rPP1PP1P/1P4S2/LNSK1G1NL w GPbsp 50", "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", ]; - for backend in Backend::all() { - for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, backend, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!( - ret.len() % 2 == 1, - "failed to solve #{}, by backend {:?}", - i, - backend - ); - } - Err(e) => { - panic!("canceled #{}, by backend {:?}: {}", i, backend, e); - } + for (i, &sfen) in test_cases.iter().enumerate() { + match solve(sfen, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); } } } @@ -162,20 +134,13 @@ mod tests { "3Bp2n1/5+R2+B/p2p1GSp1/8p/Pn5l1/1n2SNP2/2pPPS1Pk/1P1SK1G2/L1G1G4 b RL3Pl3p 131", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate7.sfen:71 "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html ]; - for backend in Backend::all() { - for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, backend, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!( - ret.len() % 2 == 1, - "failed to solve #{}, by backend {:?}", - i, - backend - ); - } - Err(e) => { - panic!("canceled #{}, by backend {:?}: {}", i, backend, e); - } + for (i, &sfen) in test_cases.iter().enumerate() { + match solve(sfen, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); } } } @@ -188,20 +153,13 @@ mod tests { let test_cases = vec![ "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 ]; - for backend in Backend::all() { - for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, backend, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!( - ret.len() % 2 == 1, - "failed to solve #{}, by backend {:?}", - i, - backend - ); - } - Err(e) => { - panic!("canceled #{}, by backend {:?}: {}", i, backend, e); - } + for (i, &sfen) in test_cases.iter().enumerate() { + match solve(sfen, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); } } } @@ -214,20 +172,13 @@ mod tests { let test_cases = vec![ "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, ]; - for backend in Backend::all() { - for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, backend, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!( - ret.len() % 2 == 1, - "failed to solve #{}, by backend {:?}", - i, - backend - ); - } - Err(e) => { - panic!("canceled #{}, by backend {:?}: {}", i, backend, e); - } + for (i, &sfen) in test_cases.iter().enumerate() { + match solve(sfen, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); } } } @@ -240,20 +191,13 @@ mod tests { let test_cases = vec![ "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position ]; - for backend in Backend::all() { - for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, backend, Some(Duration::from_secs(1))) { - Ok(ret) => { - assert!( - ret.is_empty(), - "failed to solve #{}, by backend {:?}", - i, - backend - ); - } - Err(e) => { - panic!("canceled #{}, by backend {:?}: {}", i, backend, e); - } + for (i, &sfen) in test_cases.iter().enumerate() { + match solve(sfen, Some(Duration::from_secs(1))) { + Ok(ret) => { + assert!(ret.is_empty(), "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); } } } From 7ec918d1a124d920e0a3c59279ec9cc51a5035bd Mon Sep 17 00:00:00 2001 From: sugyan Date: Tue, 12 Jul 2022 10:25:24 +0900 Subject: [PATCH 03/12] Remove `shogi` crate from dependencies --- Cargo.lock | 108 ++-------------------------- Cargo.toml | 3 +- benches/bench.rs | 12 ++-- src/backend/yasai.rs | 10 +-- src/main.rs | 166 ++++++++++++++++++++++++++++++------------- src/solver.rs | 42 ++++++----- 6 files changed, 157 insertions(+), 184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 391bd64..6ca20fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.2" @@ -37,24 +31,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitintr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba5a5c4df8ac8673f22698f443ef1ce3853d7f22d5a15ebf66b9a7553b173dd" - -[[package]] -name = "bitvec" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -93,11 +69,11 @@ dependencies = [ [[package]] name = "csa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ae283f59c5d9162b9bacd521ecd80510d495f328b994691abee7ae83796fd27" +checksum = "74e0a2bdcd19171bf127a6558cd393a16d2cddb283fb2c98b31b1a8897286973" dependencies = [ - "nom 6.1.2", + "nom", "time", ] @@ -129,12 +105,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "getrandom" version = "0.2.4" @@ -207,19 +177,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lexical-core" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" -dependencies = [ - "arrayvec 0.5.2", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.119" @@ -238,19 +195,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "nom" -version = "6.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" -dependencies = [ - "bitvec", - "funty", - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "nom" version = "7.1.0" @@ -334,12 +278,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - [[package]] name = "rand" version = "0.8.5" @@ -370,23 +308,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "shogi" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e290784f248a15668b131dbcd110b2863ee2b6e1fba797e1a1e0e9da59acc7" -dependencies = [ - "bitintr", - "itertools", - "thiserror", -] - [[package]] name = "shogi-converter" version = "0.1.0" @@ -394,7 +315,7 @@ dependencies = [ "csa", "encoding_rs", "itertools", - "nom 7.1.0", + "nom", ] [[package]] @@ -412,12 +333,6 @@ dependencies = [ "shogi_core", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" @@ -435,12 +350,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "termcolor" version = "1.1.2" @@ -495,7 +404,6 @@ dependencies = [ "csa", "dfpn", "dfpn-extended", - "shogi", "shogi-converter", "shogi_core", "shogi_usi_parser", @@ -552,18 +460,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "yasai" version = "0.5.0" source = "git+https://github.com/sugyan/yasai?tag=0.5.0#ef8e5cb81fdd5b216432d6e09d67772ddf27207e" dependencies = [ - "arrayvec 0.7.2", + "arrayvec", "cfg-if", "once_cell", "rand", diff --git a/Cargo.toml b/Cargo.toml index 22619fa..216e0e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,8 @@ edition = "2021" dfpn = { path = "./dfpn" } dfpn-extended = { path = "./dfpn-extended" } shogi-converter = { path = "./shogi-converter" } -csa = "1.0.1" +csa = "1.0.2" clap = { version = "3.1.1", features = ["derive"] } -shogi = "0.12.2" thiserror = "1.0.30" yasai = { git = "https://github.com/sugyan/yasai", tag = "0.5.0", features = ["simd"] } shogi_core = "0.1.4" diff --git a/benches/bench.rs b/benches/bench.rs index c8080bd..5a30a0e 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -3,6 +3,8 @@ extern crate test; use dfpn::search::Search; use dfpn::DefaultSearcher; use dfpn::{impl_hashmap_table::HashMapTable, impl_vec_table::VecTable}; +use shogi_core::PartialPosition; +use shogi_usi_parser::FromUsi; use tsumeshogi_solver::backend::YasaiPosition; fn test_cases() -> Vec { @@ -39,8 +41,9 @@ fn test_cases() -> Vec { fn bench_yasai_hashmap(b: &mut test::Bencher) { b.iter(|| { for sfen in test_cases() { - let mut searcher = - DefaultSearcher::<_, HashMapTable>::new(YasaiPosition::from(sfen.as_str())); + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + let mut searcher = DefaultSearcher::<_, HashMapTable>::new(YasaiPosition::from(pos)); searcher.dfpn_search(); } }) @@ -50,8 +53,9 @@ fn bench_yasai_hashmap(b: &mut test::Bencher) { fn bench_yasai_vec(b: &mut test::Bencher) { b.iter(|| { for sfen in test_cases() { - let mut searcher = - DefaultSearcher::<_, VecTable>::new(YasaiPosition::from(sfen.as_str())); + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + let mut searcher = DefaultSearcher::<_, VecTable>::new(YasaiPosition::from(pos)); searcher.dfpn_search(); } }) diff --git a/src/backend/yasai.rs b/src/backend/yasai.rs index 9e3ed2e..5dd5c57 100644 --- a/src/backend/yasai.rs +++ b/src/backend/yasai.rs @@ -1,17 +1,13 @@ use crate::solver::CalculateResult; use dfpn::Node; use shogi_core::{Hand, Move, PartialPosition, ToUsi}; -use shogi_usi_parser::FromUsi; use yasai::Position; pub struct YasaiPosition(Position); -impl From<&str> for YasaiPosition { - fn from(sfen: &str) -> Self { - let s = String::from("sfen ") + sfen; - Self(Position::new( - PartialPosition::from_usi(&s).expect("failed to set sfen"), - )) +impl From for YasaiPosition { + fn from(pos: PartialPosition) -> Self { + Self(Position::new(pos)) } } diff --git a/src/main.rs b/src/main.rs index 83aa28a..ed2ceb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ use clap::{ArgEnum, Parser}; use csa::{parse_csa, CsaError}; -use shogi::Position; use shogi_converter::kif_converter::{parse_kif, KifError}; use shogi_converter::Record; +use shogi_core::{Color, Hand, PartialPosition, PieceKind, Square}; +use shogi_usi_parser::FromUsi; +use std::fs::File; use std::io::{BufRead, Read}; use std::time::{Duration, Instant}; -use std::{fs::File, str}; use thiserror::Error; use tsumeshogi_solver::solve; @@ -16,7 +17,11 @@ enum ParseError { #[error("failed to parse kif: {0}")] Kif(KifError), #[error(transparent)] - Utf8(#[from] str::Utf8Error), + Usi(#[from] shogi_usi_parser::Error), + #[error(transparent)] + Utf8(#[from] std::str::Utf8Error), + #[error(transparent)] + Io(#[from] std::io::Error), } trait Parse { @@ -27,7 +32,7 @@ struct CsaParser; impl Parse for CsaParser { fn parse(&self, input: &[u8]) -> Result { - match parse_csa(str::from_utf8(input).map_err(ParseError::Utf8)?) { + match parse_csa(std::str::from_utf8(input).map_err(ParseError::Utf8)?) { Ok(record) => Ok(Record::from(record).pos.to_sfen()), Err(e) => Err(ParseError::Csa(e)), } @@ -70,7 +75,7 @@ enum Format { Kif, } -fn main() -> Result<(), std::io::Error> { +fn main() -> Result<(), ParseError> { let args = Args::parse(); match args.format { Format::Sfen => run_sfen(&args), @@ -79,36 +84,22 @@ fn main() -> Result<(), std::io::Error> { } } -fn run_sfen(args: &Args) -> Result<(), std::io::Error> { +fn run_sfen(args: &Args) -> Result<(), ParseError> { if args.inputs == ["-"] { let stdin = std::io::stdin(); for line in stdin.lock().lines() { let sfen = line?; - let mut pos = Position::new(); - match pos.set_sfen(&sfen) { - Ok(()) => run(&sfen, &sfen, args), - Err(e) => { - eprintln!("failed to parse SFEN string: {}", e); - std::process::exit(1); - } - } + run(&sfen, &sfen, args)?; } } else { for input in &args.inputs { - let mut pos = Position::new(); - match pos.set_sfen(input) { - Ok(()) => run(input, input.trim(), args), - Err(e) => { - eprintln!("failed to parse SFEN string: {}", e); - std::process::exit(1); - } - } + run(input, input.trim(), args)?; } } Ok(()) } -fn run_parse(parser: T, args: &Args) -> Result<(), std::io::Error> +fn run_parse(parser: T, args: &Args) -> Result<(), ParseError> where T: Parse, { @@ -116,51 +107,126 @@ where let stdin = std::io::stdin(); let mut buf = Vec::new(); stdin.lock().read_to_end(&mut buf)?; - match parser.parse(&buf) { - Ok(sfen) => run(&sfen, "-", args), - Err(e) => { - eprintln!("failed to parse input: {}", e); - std::process::exit(1); - } - } + run(&parser.parse(&buf)?, "-", args)? } else { for input in &args.inputs { let mut buf = Vec::new(); - let mut file = match File::open(input) { - Ok(file) => file, - Err(e) => { - eprintln!("{}: {}", e, input); - std::process::exit(1); - } - }; + let mut file = File::open(input)?; file.read_to_end(&mut buf)?; - match parser.parse(&buf) { - Ok(sfen) => run(&sfen, input, args), - Err(e) => { - eprintln!("failed to parse input {}: {}", input, e); - std::process::exit(1); - } - } + run(&parser.parse(&buf)?, input, args)? } } Ok(()) } -fn run(sfen: &str, input: &str, args: &Args) { +fn run(sfen: &str, input: &str, args: &Args) -> Result<(), ParseError> { print!("{}: ", input); + let pos = PartialPosition::from_usi(&format!("sfen {sfen}"))?; if args.verbose { - let mut pos = Position::new(); - pos.set_sfen(sfen).expect("failed to parse SFEN string"); - println!(); - println!("{pos}"); println!(); + println!("{}", pos2csa(&pos)); } let now = Instant::now(); println!( "{:?}", - solve(sfen, args.timeout.map(Duration::from_secs_f32),) + solve(pos, args.timeout.map(Duration::from_secs_f32)) ); if args.verbose { println!("elapsed: {:?}", now.elapsed()); } + Ok(()) +} + +fn pos2csa(pos: &PartialPosition) -> String { + let mut remains = Hand::default(); + for (pk, num) in [ + (PieceKind::Pawn, 18), + (PieceKind::Lance, 4), + (PieceKind::Knight, 4), + (PieceKind::Silver, 4), + (PieceKind::Gold, 4), + (PieceKind::Bishop, 2), + (PieceKind::Rook, 2), + ] { + for _ in 0..num { + remains = remains.added(pk).unwrap(); + } + } + let mut board = [[None; 9]; 9]; + for sq in Square::all() { + if let Some(p) = pos.piece_at(sq) { + board[sq.rank() as usize - 1][9 - sq.file() as usize] = + Some((c2c(p.color()), pk2pt(p.piece_kind()))); + if p.piece_kind() != PieceKind::King { + remains = remains + .removed(p.unpromote().unwrap_or(p).piece_kind()) + .unwrap(); + } + } + } + let mut add_pieces = Vec::new(); + for &pk in Hand::all_hand_pieces().collect::>().iter().rev() { + for _ in 0..pos + .hand_of_a_player(pos.side_to_move()) + .count(pk) + .unwrap_or_default() + { + add_pieces.push((c2c(pos.side_to_move()), csa::Square::new(0, 0), pk2pt(pk))); + remains = remains.removed(pk).unwrap(); + } + } + if remains == pos.hand_of_a_player(pos.side_to_move().flip()) { + add_pieces.push(( + c2c(pos.side_to_move().flip()), + csa::Square::new(0, 0), + csa::PieceType::All, + )); + } else { + for &pk in Hand::all_hand_pieces().collect::>().iter().rev() { + for _ in 0..pos + .hand_of_a_player(pos.side_to_move().flip()) + .count(pk) + .unwrap_or_default() + { + add_pieces.push(( + c2c(pos.side_to_move().flip()), + csa::Square::new(0, 0), + pk2pt(pk), + )); + } + } + } + csa::Position { + drop_pieces: Vec::new(), + bulk: Some(board), + add_pieces, + side_to_move: c2c(pos.side_to_move()), + } + .to_string() +} + +fn pk2pt(pk: PieceKind) -> csa::PieceType { + match pk { + PieceKind::Pawn => csa::PieceType::Pawn, + PieceKind::Lance => csa::PieceType::Lance, + PieceKind::Knight => csa::PieceType::Knight, + PieceKind::Silver => csa::PieceType::Silver, + PieceKind::Gold => csa::PieceType::Gold, + PieceKind::Bishop => csa::PieceType::Bishop, + PieceKind::Rook => csa::PieceType::Rook, + PieceKind::King => csa::PieceType::King, + PieceKind::ProPawn => csa::PieceType::ProPawn, + PieceKind::ProLance => csa::PieceType::ProLance, + PieceKind::ProKnight => csa::PieceType::ProKnight, + PieceKind::ProSilver => csa::PieceType::ProSilver, + PieceKind::ProBishop => csa::PieceType::Horse, + PieceKind::ProRook => csa::PieceType::Dragon, + } +} + +fn c2c(c: Color) -> csa::Color { + match c { + Color::Black => csa::Color::Black, + Color::White => csa::Color::White, + } } diff --git a/src/solver.rs b/src/solver.rs index 83321cd..1192b92 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -2,6 +2,7 @@ use crate::backend::YasaiPosition; use dfpn::search::Search; use dfpn::{Node, Position, INF}; use dfpn_extended::{CancelableSearcher, CanceledError}; +use shogi_core::PartialPosition; use std::collections::HashSet; use std::time::Duration; @@ -9,8 +10,11 @@ pub(crate) trait CalculateResult: Position { fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize); } -pub fn solve(sfen: &str, timeout: Option) -> Result, CanceledError> { - solve_impl(YasaiPosition::from(sfen), timeout) +pub fn solve( + position: PartialPosition, + timeout: Option, +) -> Result, CanceledError> { + solve_impl(YasaiPosition::from(position), timeout) } fn solve_impl

(pos: P, timeout: Option) -> Result, CanceledError> @@ -69,14 +73,14 @@ fn search_all_mates

( #[cfg(test)] mod tests { + use shogi_core::PartialPosition; + use shogi_usi_parser::FromUsi; + use super::solve; - use shogi::bitboard::Factory; use std::time::Duration; #[test] fn solve_mates() { - Factory::init(); - // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ let test_cases = vec![ // head -10 mate3.sfen @@ -114,7 +118,9 @@ mod tests { "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", ]; for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -127,15 +133,15 @@ mod tests { #[test] fn ghi_problems() { - Factory::init(); - let test_cases = vec![ "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:1 "3Bp2n1/5+R2+B/p2p1GSp1/8p/Pn5l1/1n2SNP2/2pPPS1Pk/1P1SK1G2/L1G1G4 b RL3Pl3p 131", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate7.sfen:71 "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html ]; for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -148,13 +154,13 @@ mod tests { #[test] fn other_problems() { - Factory::init(); - let test_cases = vec![ "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 ]; for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -167,13 +173,13 @@ mod tests { #[test] fn 無駄合駒() { - Factory::init(); - let test_cases = vec![ "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, ]; for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -186,13 +192,13 @@ mod tests { #[test] fn 不詰() { - Factory::init(); - let test_cases = vec![ "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position ]; for (i, &sfen) in test_cases.iter().enumerate() { - match solve(sfen, Some(Duration::from_secs(1))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve(pos, Some(Duration::from_secs(1))) { Ok(ret) => { assert!(ret.is_empty(), "failed to solve #{i}"); } From 4fccc6f1b60912ef5a81dd523da16ebf3bbb5c82 Mon Sep 17 00:00:00 2001 From: sugyan Date: Tue, 12 Jul 2022 23:40:20 +0900 Subject: [PATCH 04/12] Update interfaces --- benches/bench.rs | 3 +- dfpn-extended/src/cancelable_searcher.rs | 24 +++++++- dfpn/src/lib.rs | 8 +-- dfpn/src/{default_searcher.rs => searcher.rs} | 3 +- src/backend.rs | 3 - src/{ => bin}/main.rs | 6 +- src/implementations.rs | 7 +++ .../implementations/hashmap_table.rs | 3 +- .../implementations/vec_table.rs | 2 +- .../yasai_position.rs} | 0 src/lib.rs | 2 +- src/solver.rs | 59 +++++++++---------- 12 files changed, 70 insertions(+), 50 deletions(-) rename dfpn/src/{default_searcher.rs => searcher.rs} (90%) delete mode 100644 src/backend.rs rename src/{ => bin}/main.rs (96%) create mode 100644 src/implementations.rs rename dfpn/src/impl_hashmap_table.rs => src/implementations/hashmap_table.rs (93%) rename dfpn/src/impl_vec_table.rs => src/implementations/vec_table.rs (96%) rename src/{backend/yasai.rs => implementations/yasai_position.rs} (100%) diff --git a/benches/bench.rs b/benches/bench.rs index 5a30a0e..deec595 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,10 +2,9 @@ extern crate test; use dfpn::search::Search; use dfpn::DefaultSearcher; -use dfpn::{impl_hashmap_table::HashMapTable, impl_vec_table::VecTable}; use shogi_core::PartialPosition; use shogi_usi_parser::FromUsi; -use tsumeshogi_solver::backend::YasaiPosition; +use tsumeshogi_solver::implementations::{HashMapTable, VecTable, YasaiPosition}; fn test_cases() -> Vec { vec![ diff --git a/dfpn-extended/src/cancelable_searcher.rs b/dfpn-extended/src/cancelable_searcher.rs index a68c0f0..904e522 100644 --- a/dfpn-extended/src/cancelable_searcher.rs +++ b/dfpn-extended/src/cancelable_searcher.rs @@ -1,5 +1,4 @@ use crate::SearchOrCancel; -use dfpn::impl_hashmap_table::HashMapTable; use dfpn::search::Search; use dfpn::{Node, Position, Table, U}; use instant::Instant; @@ -12,7 +11,7 @@ pub enum CanceledError { Timeout, } -pub struct CancelableSearcher { +pub struct CancelableSearcher { pub pos: P, table: T, timeout: Option, @@ -85,6 +84,7 @@ where #[cfg(test)] mod tests { use super::*; + use std::collections::HashMap; struct InfinityPosition(u64); @@ -104,9 +104,27 @@ mod tests { } } + #[derive(Default)] + struct HashMapTable { + table: HashMap, + } + + impl Table for HashMapTable { + fn look_up_hash(&self, key: &u64) -> (U, U) { + if let Some(&v) = self.table.get(key) { + v + } else { + (1, 1) + } + } + fn put_in_hash(&mut self, key: u64, value: (U, U)) { + self.table.insert(key, value); + } + } + #[test] fn timeout() { - let mut searcher: CancelableSearcher<_> = + let mut searcher: CancelableSearcher<_, HashMapTable> = CancelableSearcher::new(InfinityPosition(1), Some(Duration::from_millis(10))); match searcher.dfpn_search() { Err(CanceledError::Timeout) => {} diff --git a/dfpn/src/lib.rs b/dfpn/src/lib.rs index c97ae82..de717a6 100644 --- a/dfpn/src/lib.rs +++ b/dfpn/src/lib.rs @@ -1,10 +1,8 @@ -mod default_searcher; -pub mod impl_hashmap_table; -pub mod impl_vec_table; pub mod search; -mod types; +mod searcher; +pub mod types; -pub use default_searcher::*; +pub use searcher::*; pub use types::*; pub type U = u32; diff --git a/dfpn/src/default_searcher.rs b/dfpn/src/searcher.rs similarity index 90% rename from dfpn/src/default_searcher.rs rename to dfpn/src/searcher.rs index fa1e98e..ed8c125 100644 --- a/dfpn/src/default_searcher.rs +++ b/dfpn/src/searcher.rs @@ -1,8 +1,7 @@ -use crate::impl_hashmap_table::HashMapTable; use crate::search::Search; use crate::{Node, Position, Table, U}; -pub struct DefaultSearcher { +pub struct DefaultSearcher { pub pos: P, table: T, } diff --git a/src/backend.rs b/src/backend.rs deleted file mode 100644 index dc45b4d..0000000 --- a/src/backend.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod yasai; - -pub use self::yasai::YasaiPosition; diff --git a/src/main.rs b/src/bin/main.rs similarity index 96% rename from src/main.rs rename to src/bin/main.rs index ed2ceb0..fb255b6 100644 --- a/src/main.rs +++ b/src/bin/main.rs @@ -8,6 +8,7 @@ use std::fs::File; use std::io::{BufRead, Read}; use std::time::{Duration, Instant}; use thiserror::Error; +use tsumeshogi_solver::implementations::{HashMapTable, YasaiPosition}; use tsumeshogi_solver::solve; #[derive(Error, Debug)] @@ -129,7 +130,10 @@ fn run(sfen: &str, input: &str, args: &Args) -> Result<(), ParseError> { let now = Instant::now(); println!( "{:?}", - solve(pos, args.timeout.map(Duration::from_secs_f32)) + solve::<_, HashMapTable>( + YasaiPosition::from(pos), + args.timeout.map(Duration::from_secs_f32) + ) ); if args.verbose { println!("elapsed: {:?}", now.elapsed()); diff --git a/src/implementations.rs b/src/implementations.rs new file mode 100644 index 0000000..581ed96 --- /dev/null +++ b/src/implementations.rs @@ -0,0 +1,7 @@ +mod hashmap_table; +mod vec_table; +mod yasai_position; + +pub use self::hashmap_table::HashMapTable; +pub use self::vec_table::VecTable; +pub use self::yasai_position::YasaiPosition; diff --git a/dfpn/src/impl_hashmap_table.rs b/src/implementations/hashmap_table.rs similarity index 93% rename from dfpn/src/impl_hashmap_table.rs rename to src/implementations/hashmap_table.rs index a4517ac..556b46f 100644 --- a/dfpn/src/impl_hashmap_table.rs +++ b/src/implementations/hashmap_table.rs @@ -1,5 +1,4 @@ -use crate::types::Table; -use crate::U; +use dfpn::{Table, U}; use std::collections::HashMap; #[derive(Default)] diff --git a/dfpn/src/impl_vec_table.rs b/src/implementations/vec_table.rs similarity index 96% rename from dfpn/src/impl_vec_table.rs rename to src/implementations/vec_table.rs index 9c23ba1..d3074cb 100644 --- a/dfpn/src/impl_vec_table.rs +++ b/src/implementations/vec_table.rs @@ -1,4 +1,4 @@ -use crate::{Table, U}; +use dfpn::{Table, U}; pub struct VecTable { table: Vec>, diff --git a/src/backend/yasai.rs b/src/implementations/yasai_position.rs similarity index 100% rename from src/backend/yasai.rs rename to src/implementations/yasai_position.rs diff --git a/src/lib.rs b/src/lib.rs index a12ba59..01141e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub mod backend; +pub mod implementations; mod solver; pub use solver::solve; diff --git a/src/solver.rs b/src/solver.rs index 1192b92..c379404 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,27 +1,19 @@ -use crate::backend::YasaiPosition; use dfpn::search::Search; -use dfpn::{Node, Position, INF}; +use dfpn::{Node, Position, Table, INF}; use dfpn_extended::{CancelableSearcher, CanceledError}; -use shogi_core::PartialPosition; use std::collections::HashSet; use std::time::Duration; -pub(crate) trait CalculateResult: Position { +pub trait CalculateResult: Position { fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize); } -pub fn solve( - position: PartialPosition, - timeout: Option, -) -> Result, CanceledError> { - solve_impl(YasaiPosition::from(position), timeout) -} - -fn solve_impl

(pos: P, timeout: Option) -> Result, CanceledError> +pub fn solve(position: P, timeout: Option) -> Result, CanceledError> where P: CalculateResult, + T: Table, { - let mut searcher: CancelableSearcher

= CancelableSearcher::new(pos, timeout); + let mut searcher: CancelableSearcher = CancelableSearcher::new(position, timeout); searcher.dfpn_search().map(|_| { let mut solutions = Vec::new(); search_all_mates( @@ -38,13 +30,14 @@ where }) } -fn search_all_mates

( - searcher: &mut CancelableSearcher

, +fn search_all_mates( + searcher: &mut CancelableSearcher, moves: &mut Vec, hashes: &mut HashSet, solutions: &mut Vec<(Vec, usize)>, ) where P: CalculateResult, + T: Table, { let (node, mate_pd) = if moves.len() & 1 == 0 { (Node::Or, (INF, 0)) @@ -73,6 +66,7 @@ fn search_all_mates

( #[cfg(test)] mod tests { + use crate::implementations::{HashMapTable, YasaiPosition}; use shogi_core::PartialPosition; use shogi_usi_parser::FromUsi; @@ -118,9 +112,10 @@ mod tests { "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve(pos, Some(Duration::from_secs(5))) { + let pos = YasaiPosition::from( + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), + ); + match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -139,9 +134,10 @@ mod tests { "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve(pos, Some(Duration::from_secs(5))) { + let pos = YasaiPosition::from( + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), + ); + match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -158,9 +154,10 @@ mod tests { "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve(pos, Some(Duration::from_secs(5))) { + let pos = YasaiPosition::from( + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), + ); + match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -177,9 +174,10 @@ mod tests { "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve(pos, Some(Duration::from_secs(5))) { + let pos = YasaiPosition::from( + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), + ); + match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -196,9 +194,10 @@ mod tests { "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve(pos, Some(Duration::from_secs(1))) { + let pos = YasaiPosition::from( + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), + ); + match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(1))) { Ok(ret) => { assert!(ret.is_empty(), "failed to solve #{i}"); } From 3127aebe1ebf6f2c1c4c57c7f0b0d010db78db33 Mon Sep 17 00:00:00 2001 From: sugyan Date: Wed, 13 Jul 2022 00:06:44 +0900 Subject: [PATCH 05/12] Update interfaces --- Cargo.lock | 4 ++ dfpn-extended/Cargo.toml | 1 + dfpn-extended/src/cancelable_searcher.rs | 7 +-- dfpn/Cargo.toml | 1 + dfpn/src/lib.rs | 8 ++-- dfpn/src/node.rs | 18 ++++++++ dfpn/src/search.rs | 13 +++--- dfpn/src/searcher.rs | 7 +-- dfpn/src/traits.rs | 14 ++++++ dfpn/src/types.rs | 33 --------------- src/bin/main.rs | 5 +-- src/implementations/yasai_position.rs | 10 ++--- src/solver.rs | 54 ++++++++++++------------ 13 files changed, 90 insertions(+), 85 deletions(-) create mode 100644 dfpn/src/node.rs create mode 100644 dfpn/src/traits.rs delete mode 100644 dfpn/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 6ca20fc..65caf44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,9 @@ dependencies = [ [[package]] name = "dfpn" version = "0.3.0" +dependencies = [ + "shogi_core", +] [[package]] name = "dfpn-extended" @@ -87,6 +90,7 @@ version = "0.1.0" dependencies = [ "dfpn", "instant", + "shogi_core", "thiserror", ] diff --git a/dfpn-extended/Cargo.toml b/dfpn-extended/Cargo.toml index dabe738..7a783dd 100644 --- a/dfpn-extended/Cargo.toml +++ b/dfpn-extended/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +shogi_core = "0.1.4" thiserror = "1.0.30" instant = { version = "0.1" } dfpn = { path = "../dfpn" } diff --git a/dfpn-extended/src/cancelable_searcher.rs b/dfpn-extended/src/cancelable_searcher.rs index 904e522..138ac06 100644 --- a/dfpn-extended/src/cancelable_searcher.rs +++ b/dfpn-extended/src/cancelable_searcher.rs @@ -2,6 +2,7 @@ use crate::SearchOrCancel; use dfpn::search::Search; use dfpn::{Node, Position, Table, U}; use instant::Instant; +use shogi_core::Move; use std::time::Duration; use thiserror::Error; @@ -49,13 +50,13 @@ where fn hash_key(&self) -> u64 { self.pos.hash_key() } - fn generate_legal_moves(&mut self, node: Node) -> Vec<(P::M, u64)> { + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)> { self.pos.generate_legal_moves(node) } - fn do_move(&mut self, m: P::M) { + fn do_move(&mut self, m: Move) { self.pos.do_move(m) } - fn undo_move(&mut self, m: P::M) { + fn undo_move(&mut self, m: Move) { self.pos.undo_move(m) } fn look_up_hash(&self, key: &u64) -> (U, U) { diff --git a/dfpn/Cargo.toml b/dfpn/Cargo.toml index 56f578c..ee473e0 100644 --- a/dfpn/Cargo.toml +++ b/dfpn/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +shogi_core = "0.1.4" diff --git a/dfpn/src/lib.rs b/dfpn/src/lib.rs index de717a6..8cea3b3 100644 --- a/dfpn/src/lib.rs +++ b/dfpn/src/lib.rs @@ -1,9 +1,11 @@ +mod node; pub mod search; mod searcher; -pub mod types; +mod traits; -pub use searcher::*; -pub use types::*; +pub use node::Node; +pub use searcher::DefaultSearcher; +pub use traits::*; pub type U = u32; pub const INF: U = U::MAX; diff --git a/dfpn/src/node.rs b/dfpn/src/node.rs new file mode 100644 index 0000000..b80e84b --- /dev/null +++ b/dfpn/src/node.rs @@ -0,0 +1,18 @@ +use std::ops; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Node { + Or, + And, +} + +impl ops::Not for Node { + type Output = Node; + + fn not(self) -> Self::Output { + match self { + Node::Or => Node::And, + Node::And => Node::Or, + } + } +} diff --git a/dfpn/src/search.rs b/dfpn/src/search.rs index 4808219..0071281 100644 --- a/dfpn/src/search.rs +++ b/dfpn/src/search.rs @@ -1,4 +1,5 @@ use crate::{Node, Position, Table, INF, U}; +use shogi_core::Move; // 「df-pnアルゴリズムの詰将棋を解くプログラムへの応用」 // https://ci.nii.ac.jp/naid/110002726401 @@ -8,9 +9,9 @@ where T: Table, { fn hash_key(&self) -> u64; - fn generate_legal_moves(&mut self, node: Node) -> Vec<(P::M, u64)>; - fn do_move(&mut self, m: P::M); - fn undo_move(&mut self, m: P::M); + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)>; + fn do_move(&mut self, m: Move); + fn undo_move(&mut self, m: Move); // ハッシュを引く (本当は優越関係が使える) fn look_up_hash(&self, key: &u64) -> (U, U); // ハッシュに記録 @@ -82,7 +83,7 @@ where } } // 子ノードの選択 - fn select_child(&mut self, children: &[(P::M, u64)]) -> (Option<(P::M, u64)>, U, U, U) { + fn select_child(&mut self, children: &[(Move, u64)]) -> (Option<(Move, u64)>, U, U, U) { let (mut delta_c, mut delta_2) = (INF, INF); let mut best = None; let mut phi_c = None; // not optional? @@ -103,7 +104,7 @@ where (best, phi_c.expect("phi_c"), delta_c, delta_2) } // n の子ノード の δ の最小を計算 - fn min_delta(&mut self, children: &[(P::M, u64)]) -> U { + fn min_delta(&mut self, children: &[(Move, u64)]) -> U { let mut min = INF; for &(_, h) in children { let (_, d) = self.look_up_hash(&h); @@ -112,7 +113,7 @@ where min } // nの子ノードのφの和を計算 - fn sum_phi(&mut self, children: &[(P::M, u64)]) -> U { + fn sum_phi(&mut self, children: &[(Move, u64)]) -> U { let mut sum: U = 0; for &(_, h) in children { let (p, _) = self.look_up_hash(&h); diff --git a/dfpn/src/searcher.rs b/dfpn/src/searcher.rs index ed8c125..2b72d9e 100644 --- a/dfpn/src/searcher.rs +++ b/dfpn/src/searcher.rs @@ -1,5 +1,6 @@ use crate::search::Search; use crate::{Node, Position, Table, U}; +use shogi_core::Move; pub struct DefaultSearcher { pub pos: P, @@ -27,13 +28,13 @@ where fn hash_key(&self) -> u64 { self.pos.hash_key() } - fn generate_legal_moves(&mut self, node: Node) -> Vec<(P::M, u64)> { + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)> { self.pos.generate_legal_moves(node) } - fn do_move(&mut self, m: P::M) { + fn do_move(&mut self, m: Move) { self.pos.do_move(m) } - fn undo_move(&mut self, m: P::M) { + fn undo_move(&mut self, m: Move) { self.pos.undo_move(m) } fn look_up_hash(&self, key: &u64) -> (U, U) { diff --git a/dfpn/src/traits.rs b/dfpn/src/traits.rs new file mode 100644 index 0000000..85ada51 --- /dev/null +++ b/dfpn/src/traits.rs @@ -0,0 +1,14 @@ +use crate::{Node, U}; +use shogi_core::Move; + +pub trait Position { + fn hash_key(&self) -> u64; + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)>; + fn do_move(&mut self, m: Move); + fn undo_move(&mut self, m: Move); +} + +pub trait Table: Default { + fn look_up_hash(&self, key: &u64) -> (U, U); + fn put_in_hash(&mut self, key: u64, value: (U, U)); +} diff --git a/dfpn/src/types.rs b/dfpn/src/types.rs deleted file mode 100644 index c746285..0000000 --- a/dfpn/src/types.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::U; -use std::ops; - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum Node { - Or, - And, -} - -impl ops::Not for Node { - type Output = Node; - - fn not(self) -> Self::Output { - match self { - Node::Or => Node::And, - Node::And => Node::Or, - } - } -} - -pub trait Position { - type M: Copy + PartialEq; - - fn hash_key(&self) -> u64; - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Self::M, u64)>; - fn do_move(&mut self, m: Self::M); - fn undo_move(&mut self, m: Self::M); -} - -pub trait Table: Default { - fn look_up_hash(&self, key: &u64) -> (U, U); - fn put_in_hash(&mut self, key: u64, value: (U, U)); -} diff --git a/src/bin/main.rs b/src/bin/main.rs index fb255b6..62053bf 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -130,10 +130,7 @@ fn run(sfen: &str, input: &str, args: &Args) -> Result<(), ParseError> { let now = Instant::now(); println!( "{:?}", - solve::<_, HashMapTable>( - YasaiPosition::from(pos), - args.timeout.map(Duration::from_secs_f32) - ) + solve::(pos, args.timeout.map(Duration::from_secs_f32)) ); if args.verbose { println!("elapsed: {:?}", now.elapsed()); diff --git a/src/implementations/yasai_position.rs b/src/implementations/yasai_position.rs index 5dd5c57..82a4e1b 100644 --- a/src/implementations/yasai_position.rs +++ b/src/implementations/yasai_position.rs @@ -12,12 +12,10 @@ impl From for YasaiPosition { } impl dfpn::Position for YasaiPosition { - type M = Move; - fn hash_key(&self) -> u64 { self.0.key() } - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Self::M, u64)> { + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)> { let mut moves = Vec::new(); for m in self.0.legal_moves() { if node == Node::And || self.0.is_check_move(m) { @@ -28,16 +26,16 @@ impl dfpn::Position for YasaiPosition { } moves } - fn do_move(&mut self, m: Self::M) { + fn do_move(&mut self, m: Move) { self.0.do_move(m); } - fn undo_move(&mut self, m: Self::M) { + fn undo_move(&mut self, m: Move) { self.0.undo_move(m); } } impl CalculateResult for YasaiPosition { - fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize) { + fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize) { let (mut ret, mut len) = (Vec::new(), moves.len()); let mut total_hands = Hand::all_hand_pieces() .filter_map(|pk| self.0.hand(self.0.side_to_move().flip()).count(pk)) diff --git a/src/solver.rs b/src/solver.rs index c379404..a92fc36 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,19 +1,24 @@ use dfpn::search::Search; use dfpn::{Node, Position, Table, INF}; use dfpn_extended::{CancelableSearcher, CanceledError}; +use shogi_core::{Move, PartialPosition}; use std::collections::HashSet; use std::time::Duration; -pub trait CalculateResult: Position { - fn calculate_result_and_score(&mut self, moves: &[Self::M]) -> (Vec, usize); +pub trait CalculateResult { + fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize); } -pub fn solve(position: P, timeout: Option) -> Result, CanceledError> +pub fn solve( + position: PartialPosition, + timeout: Option, +) -> Result, CanceledError> where - P: CalculateResult, + P: Position + From + CalculateResult, T: Table, { - let mut searcher: CancelableSearcher = CancelableSearcher::new(position, timeout); + let pos = P::from(position); + let mut searcher: CancelableSearcher = CancelableSearcher::new(pos, timeout); searcher.dfpn_search().map(|_| { let mut solutions = Vec::new(); search_all_mates( @@ -32,11 +37,11 @@ where fn search_all_mates( searcher: &mut CancelableSearcher, - moves: &mut Vec, + moves: &mut Vec, hashes: &mut HashSet, solutions: &mut Vec<(Vec, usize)>, ) where - P: CalculateResult, + P: Position + CalculateResult, T: Table, { let (node, mate_pd) = if moves.len() & 1 == 0 { @@ -112,10 +117,9 @@ mod tests { "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = YasaiPosition::from( - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), - ); - match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -134,10 +138,9 @@ mod tests { "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = YasaiPosition::from( - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), - ); - match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -154,10 +157,9 @@ mod tests { "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = YasaiPosition::from( - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), - ); - match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -174,10 +176,9 @@ mod tests { "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = YasaiPosition::from( - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), - ); - match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(5))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { Ok(ret) => { assert!(ret.len() % 2 == 1, "failed to solve #{i}"); } @@ -194,10 +195,9 @@ mod tests { "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position ]; for (i, &sfen) in test_cases.iter().enumerate() { - let pos = YasaiPosition::from( - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"), - ); - match solve::<_, HashMapTable>(pos, Some(Duration::from_secs(1))) { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(1))) { Ok(ret) => { assert!(ret.is_empty(), "failed to solve #{i}"); } From f29f6752b0d8e036bdb1ca862ee65b9756da7a67 Mon Sep 17 00:00:00 2001 From: sugyan Date: Wed, 13 Jul 2022 14:49:32 +0900 Subject: [PATCH 06/12] Fix tests --- dfpn-extended/src/cancelable_searcher.rs | 39 +++++++++++++++++++----- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/dfpn-extended/src/cancelable_searcher.rs b/dfpn-extended/src/cancelable_searcher.rs index 138ac06..ee224fd 100644 --- a/dfpn-extended/src/cancelable_searcher.rs +++ b/dfpn-extended/src/cancelable_searcher.rs @@ -85,23 +85,48 @@ where #[cfg(test)] mod tests { use super::*; + use shogi_core::{Color, Piece, Square}; use std::collections::HashMap; struct InfinityPosition(u64); impl Position for InfinityPosition { - type M = u64; fn hash_key(&self) -> u64 { self.0 } - fn generate_legal_moves(&mut self, _node: Node) -> Vec<(u64, u64)> { - vec![(0, self.0 << 1), (1, (self.0 << 1) + 1)] + fn generate_legal_moves(&mut self, _node: Node) -> Vec<(Move, u64)> { + vec![ + ( + Move::Drop { + to: Square::SQ_1A, + piece: Piece::B_P, + }, + self.0 + 1, + ), + ( + Move::Drop { + to: Square::SQ_1A, + piece: Piece::W_P, + }, + self.0 + 2, + ), + ] } - fn do_move(&mut self, m: u64) { - self.0 = (self.0 << 1) + m; + fn do_move(&mut self, m: Move) { + if let Move::Drop { to: _, piece } = m { + self.0 += match piece.color() { + Color::Black => 1, + Color::White => 2, + }; + } } - fn undo_move(&mut self, _m: u64) { - self.0 >>= 1; + fn undo_move(&mut self, m: Move) { + if let Move::Drop { to: _, piece } = m { + self.0 -= match piece.color() { + Color::Black => 1, + Color::White => 2, + }; + } } } From 62cdd2e8558749e54a22dc3d4eb20c17ef45aef0 Mon Sep 17 00:00:00 2001 From: sugyan Date: Wed, 13 Jul 2022 23:15:41 +0900 Subject: [PATCH 07/12] Update interfaces --- dfpn-extended/src/cancelable_searcher.rs | 66 ++++++++++-------------- dfpn/src/search.rs | 13 +++-- dfpn/src/searcher.rs | 7 ++- dfpn/src/traits.rs | 8 +-- src/bin/main.rs | 16 ++++-- src/implementations/yasai_position.rs | 14 ++--- src/solver.rs | 8 +-- 7 files changed, 64 insertions(+), 68 deletions(-) diff --git a/dfpn-extended/src/cancelable_searcher.rs b/dfpn-extended/src/cancelable_searcher.rs index ee224fd..4777e53 100644 --- a/dfpn-extended/src/cancelable_searcher.rs +++ b/dfpn-extended/src/cancelable_searcher.rs @@ -2,7 +2,6 @@ use crate::SearchOrCancel; use dfpn::search::Search; use dfpn::{Node, Position, Table, U}; use instant::Instant; -use shogi_core::Move; use std::time::Duration; use thiserror::Error; @@ -50,13 +49,13 @@ where fn hash_key(&self) -> u64 { self.pos.hash_key() } - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)> { + fn generate_legal_moves(&mut self, node: Node) -> Vec<(P::M, u64)> { self.pos.generate_legal_moves(node) } - fn do_move(&mut self, m: Move) { + fn do_move(&mut self, m: P::M) { self.pos.do_move(m) } - fn undo_move(&mut self, m: Move) { + fn undo_move(&mut self, m: P::M) { self.pos.undo_move(m) } fn look_up_hash(&self, key: &u64) -> (U, U) { @@ -85,48 +84,37 @@ where #[cfg(test)] mod tests { use super::*; - use shogi_core::{Color, Piece, Square}; + use shogi_core::{Move, Piece, Square}; use std::collections::HashMap; - struct InfinityPosition(u64); + #[derive(Clone, Copy)] + struct M(u64); + + impl Into for M { + fn into(self) -> Move { + Move::Drop { + to: Square::SQ_1A, + piece: Piece::B_P, + } + } + } + + struct InfinityPosition(M); impl Position for InfinityPosition { + type M = M; + fn hash_key(&self) -> u64 { - self.0 + self.0 .0 } - fn generate_legal_moves(&mut self, _node: Node) -> Vec<(Move, u64)> { - vec![ - ( - Move::Drop { - to: Square::SQ_1A, - piece: Piece::B_P, - }, - self.0 + 1, - ), - ( - Move::Drop { - to: Square::SQ_1A, - piece: Piece::W_P, - }, - self.0 + 2, - ), - ] + fn generate_legal_moves(&mut self, _node: Node) -> Vec<(M, u64)> { + vec![(M(1), self.0 .0 + 1), (M(2), self.0 .0 + 2)] } - fn do_move(&mut self, m: Move) { - if let Move::Drop { to: _, piece } = m { - self.0 += match piece.color() { - Color::Black => 1, - Color::White => 2, - }; - } + fn do_move(&mut self, m: M) { + self.0 .0 += m.0; } - fn undo_move(&mut self, m: Move) { - if let Move::Drop { to: _, piece } = m { - self.0 -= match piece.color() { - Color::Black => 1, - Color::White => 2, - }; - } + fn undo_move(&mut self, m: M) { + self.0 .0 -= m.0; } } @@ -151,7 +139,7 @@ mod tests { #[test] fn timeout() { let mut searcher: CancelableSearcher<_, HashMapTable> = - CancelableSearcher::new(InfinityPosition(1), Some(Duration::from_millis(10))); + CancelableSearcher::new(InfinityPosition(M(1)), Some(Duration::from_millis(10))); match searcher.dfpn_search() { Err(CanceledError::Timeout) => {} _ => panic!("expected timeout"), diff --git a/dfpn/src/search.rs b/dfpn/src/search.rs index 0071281..4808219 100644 --- a/dfpn/src/search.rs +++ b/dfpn/src/search.rs @@ -1,5 +1,4 @@ use crate::{Node, Position, Table, INF, U}; -use shogi_core::Move; // 「df-pnアルゴリズムの詰将棋を解くプログラムへの応用」 // https://ci.nii.ac.jp/naid/110002726401 @@ -9,9 +8,9 @@ where T: Table, { fn hash_key(&self) -> u64; - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)>; - fn do_move(&mut self, m: Move); - fn undo_move(&mut self, m: Move); + fn generate_legal_moves(&mut self, node: Node) -> Vec<(P::M, u64)>; + fn do_move(&mut self, m: P::M); + fn undo_move(&mut self, m: P::M); // ハッシュを引く (本当は優越関係が使える) fn look_up_hash(&self, key: &u64) -> (U, U); // ハッシュに記録 @@ -83,7 +82,7 @@ where } } // 子ノードの選択 - fn select_child(&mut self, children: &[(Move, u64)]) -> (Option<(Move, u64)>, U, U, U) { + fn select_child(&mut self, children: &[(P::M, u64)]) -> (Option<(P::M, u64)>, U, U, U) { let (mut delta_c, mut delta_2) = (INF, INF); let mut best = None; let mut phi_c = None; // not optional? @@ -104,7 +103,7 @@ where (best, phi_c.expect("phi_c"), delta_c, delta_2) } // n の子ノード の δ の最小を計算 - fn min_delta(&mut self, children: &[(Move, u64)]) -> U { + fn min_delta(&mut self, children: &[(P::M, u64)]) -> U { let mut min = INF; for &(_, h) in children { let (_, d) = self.look_up_hash(&h); @@ -113,7 +112,7 @@ where min } // nの子ノードのφの和を計算 - fn sum_phi(&mut self, children: &[(Move, u64)]) -> U { + fn sum_phi(&mut self, children: &[(P::M, u64)]) -> U { let mut sum: U = 0; for &(_, h) in children { let (p, _) = self.look_up_hash(&h); diff --git a/dfpn/src/searcher.rs b/dfpn/src/searcher.rs index 2b72d9e..ed8c125 100644 --- a/dfpn/src/searcher.rs +++ b/dfpn/src/searcher.rs @@ -1,6 +1,5 @@ use crate::search::Search; use crate::{Node, Position, Table, U}; -use shogi_core::Move; pub struct DefaultSearcher { pub pos: P, @@ -28,13 +27,13 @@ where fn hash_key(&self) -> u64 { self.pos.hash_key() } - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)> { + fn generate_legal_moves(&mut self, node: Node) -> Vec<(P::M, u64)> { self.pos.generate_legal_moves(node) } - fn do_move(&mut self, m: Move) { + fn do_move(&mut self, m: P::M) { self.pos.do_move(m) } - fn undo_move(&mut self, m: Move) { + fn undo_move(&mut self, m: P::M) { self.pos.undo_move(m) } fn look_up_hash(&self, key: &u64) -> (U, U) { diff --git a/dfpn/src/traits.rs b/dfpn/src/traits.rs index 85ada51..e0369e7 100644 --- a/dfpn/src/traits.rs +++ b/dfpn/src/traits.rs @@ -2,10 +2,12 @@ use crate::{Node, U}; use shogi_core::Move; pub trait Position { + type M: Copy + Into; + fn hash_key(&self) -> u64; - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)>; - fn do_move(&mut self, m: Move); - fn undo_move(&mut self, m: Move); + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Self::M, u64)>; + fn do_move(&mut self, m: Self::M); + fn undo_move(&mut self, m: Self::M); } pub trait Table: Default { diff --git a/src/bin/main.rs b/src/bin/main.rs index 62053bf..dd7e8fc 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -2,7 +2,7 @@ use clap::{ArgEnum, Parser}; use csa::{parse_csa, CsaError}; use shogi_converter::kif_converter::{parse_kif, KifError}; use shogi_converter::Record; -use shogi_core::{Color, Hand, PartialPosition, PieceKind, Square}; +use shogi_core::{Color, Hand, PartialPosition, PieceKind, Square, ToUsi}; use shogi_usi_parser::FromUsi; use std::fs::File; use std::io::{BufRead, Read}; @@ -128,10 +128,16 @@ fn run(sfen: &str, input: &str, args: &Args) -> Result<(), ParseError> { println!("{}", pos2csa(&pos)); } let now = Instant::now(); - println!( - "{:?}", - solve::(pos, args.timeout.map(Duration::from_secs_f32)) - ); + let result = + solve::(pos, args.timeout.map(Duration::from_secs_f32)).map( + |res| { + res.iter() + .map(|m| m.to_usi_owned()) + .collect::>() + .join(" ") + }, + ); + println!("{:?}", result); if args.verbose { println!("elapsed: {:?}", now.elapsed()); } diff --git a/src/implementations/yasai_position.rs b/src/implementations/yasai_position.rs index 82a4e1b..3320f23 100644 --- a/src/implementations/yasai_position.rs +++ b/src/implementations/yasai_position.rs @@ -1,6 +1,6 @@ use crate::solver::CalculateResult; use dfpn::Node; -use shogi_core::{Hand, Move, PartialPosition, ToUsi}; +use shogi_core::{Hand, Move, PartialPosition}; use yasai::Position; pub struct YasaiPosition(Position); @@ -12,10 +12,12 @@ impl From for YasaiPosition { } impl dfpn::Position for YasaiPosition { + type M = Move; + fn hash_key(&self) -> u64 { self.0.key() } - fn generate_legal_moves(&mut self, node: Node) -> Vec<(Move, u64)> { + fn generate_legal_moves(&mut self, node: Node) -> Vec<(Self::M, u64)> { let mut moves = Vec::new(); for m in self.0.legal_moves() { if node == Node::And || self.0.is_check_move(m) { @@ -26,16 +28,16 @@ impl dfpn::Position for YasaiPosition { } moves } - fn do_move(&mut self, m: Move) { + fn do_move(&mut self, m: Self::M) { self.0.do_move(m); } - fn undo_move(&mut self, m: Move) { + fn undo_move(&mut self, m: Self::M) { self.0.undo_move(m); } } impl CalculateResult for YasaiPosition { - fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize) { + fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize) { let (mut ret, mut len) = (Vec::new(), moves.len()); let mut total_hands = Hand::all_hand_pieces() .filter_map(|pk| self.0.hand(self.0.side_to_move().flip()).count(pk)) @@ -91,7 +93,7 @@ impl CalculateResult for YasaiPosition { } else if let Move::Drop { to, piece } = m { drops[to.array_index()] = Some(piece.piece_kind()); } - ret.push(m.to_usi_owned()); + ret.push(m.clone()); } let score = if zero { 0 diff --git a/src/solver.rs b/src/solver.rs index a92fc36..be4cd78 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -6,13 +6,13 @@ use std::collections::HashSet; use std::time::Duration; pub trait CalculateResult { - fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize); + fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize); } pub fn solve( position: PartialPosition, timeout: Option, -) -> Result, CanceledError> +) -> Result, CanceledError> where P: Position + From + CalculateResult, T: Table, @@ -39,7 +39,7 @@ fn search_all_mates( searcher: &mut CancelableSearcher, moves: &mut Vec, hashes: &mut HashSet, - solutions: &mut Vec<(Vec, usize)>, + solutions: &mut Vec<(Vec, usize)>, ) where P: Position + CalculateResult, T: Table, @@ -58,7 +58,7 @@ fn search_all_mates( solutions.push(searcher.pos.calculate_result_and_score(moves)); } else { for &(m, h) in &mate_moves { - moves.push(m); + moves.push(m.into()); hashes.insert(h); searcher.do_move(m); search_all_mates(searcher, moves, hashes, solutions); From c52dc5b4e05795feccd1927dfbc82d771d2a5ae0 Mon Sep 17 00:00:00 2001 From: sugyan Date: Wed, 13 Jul 2022 23:55:21 +0900 Subject: [PATCH 08/12] Separate packages --- Cargo.lock | 11 +- Cargo.toml | 2 +- solver/Cargo.toml | 11 ++ solver/src/lib.rs | 3 + solver/src/solver.rs | 70 +++++++++ src/implementations/yasai_position.rs | 2 +- src/lib.rs | 140 ++++++++++++++++- src/{bin => }/main.rs | 2 +- src/solver.rs | 210 -------------------------- 9 files changed, 235 insertions(+), 216 deletions(-) create mode 100644 solver/Cargo.toml create mode 100644 solver/src/lib.rs create mode 100644 solver/src/solver.rs rename src/{bin => }/main.rs (99%) delete mode 100644 src/solver.rs diff --git a/Cargo.lock b/Cargo.lock index 65caf44..26c638c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,15 @@ dependencies = [ "shogi_core", ] +[[package]] +name = "solver" +version = "0.1.0" +dependencies = [ + "dfpn", + "dfpn-extended", + "shogi_core", +] + [[package]] name = "strsim" version = "0.10.0" @@ -407,10 +416,10 @@ dependencies = [ "clap", "csa", "dfpn", - "dfpn-extended", "shogi-converter", "shogi_core", "shogi_usi_parser", + "solver", "thiserror", "yasai", ] diff --git a/Cargo.toml b/Cargo.toml index 216e0e3..d6c6fc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" [dependencies] dfpn = { path = "./dfpn" } -dfpn-extended = { path = "./dfpn-extended" } shogi-converter = { path = "./shogi-converter" } +solver = { path = "./solver" } csa = "1.0.2" clap = { version = "3.1.1", features = ["derive"] } thiserror = "1.0.30" diff --git a/solver/Cargo.toml b/solver/Cargo.toml new file mode 100644 index 0000000..509887a --- /dev/null +++ b/solver/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "solver" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dfpn = { path = "../dfpn" } +dfpn-extended = { path = "../dfpn-extended" } +shogi_core = "0.1.4" diff --git a/solver/src/lib.rs b/solver/src/lib.rs new file mode 100644 index 0000000..35a6d78 --- /dev/null +++ b/solver/src/lib.rs @@ -0,0 +1,3 @@ +mod solver; + +pub use solver::*; diff --git a/solver/src/solver.rs b/solver/src/solver.rs new file mode 100644 index 0000000..e706a03 --- /dev/null +++ b/solver/src/solver.rs @@ -0,0 +1,70 @@ +use dfpn::search::Search; +use dfpn::{Node, Position, Table, INF}; +use dfpn_extended::{CancelableSearcher, CanceledError}; +use shogi_core::{Move, PartialPosition}; +use std::collections::HashSet; +use std::time::Duration; + +pub trait CalculateResult { + fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize); +} + +pub fn solve( + position: PartialPosition, + timeout: Option, +) -> Result, CanceledError> +where + P: Position + From + CalculateResult, + T: Table, +{ + let pos = P::from(position); + let mut searcher: CancelableSearcher = CancelableSearcher::new(pos, timeout); + searcher.dfpn_search().map(|_| { + let mut solutions = Vec::new(); + search_all_mates( + &mut searcher, + &mut Vec::new(), + &mut HashSet::new(), + &mut solutions, + ); + solutions.sort_by_cached_key(|&(_, score)| score); + solutions.dedup(); + solutions + .last() + .map_or(Vec::new(), |(moves, _)| moves.clone()) + }) +} + +fn search_all_mates( + searcher: &mut CancelableSearcher, + moves: &mut Vec, + hashes: &mut HashSet, + solutions: &mut Vec<(Vec, usize)>, +) where + P: Position + CalculateResult, + T: Table, +{ + let (node, mate_pd) = if moves.len() & 1 == 0 { + (Node::Or, (INF, 0)) + } else { + (Node::And, (0, INF)) + }; + let mate_moves = searcher + .generate_legal_moves(node) + .into_iter() + .filter(|(_, h)| !hashes.contains(h) && searcher.look_up_hash(h) == mate_pd) + .collect::>(); + if node == Node::And && mate_moves.is_empty() { + solutions.push(searcher.pos.calculate_result_and_score(moves)); + } else { + for &(m, h) in &mate_moves { + moves.push(m.into()); + hashes.insert(h); + searcher.do_move(m); + search_all_mates(searcher, moves, hashes, solutions); + searcher.undo_move(m); + moves.pop(); + hashes.remove(&h); + } + } +} diff --git a/src/implementations/yasai_position.rs b/src/implementations/yasai_position.rs index 3320f23..4a0eb90 100644 --- a/src/implementations/yasai_position.rs +++ b/src/implementations/yasai_position.rs @@ -1,6 +1,6 @@ -use crate::solver::CalculateResult; use dfpn::Node; use shogi_core::{Hand, Move, PartialPosition}; +use solver::CalculateResult; use yasai::Position; pub struct YasaiPosition(Position); diff --git a/src/lib.rs b/src/lib.rs index 01141e9..f632faf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,140 @@ pub mod implementations; -mod solver; -pub use solver::solve; +#[cfg(test)] +mod tests { + use crate::implementations::{HashMapTable, YasaiPosition}; + use shogi_core::PartialPosition; + use shogi_usi_parser::FromUsi; + use solver::solve; + use std::time::Duration; + + #[test] + fn solve_mates() { + // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ + let test_cases = vec![ + // head -10 mate3.sfen + "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", + "l3kgsnl/9/p1pS+Bp3/7pp/6PP1/9/PPPPPPn1P/1B1GG2+r1/LNS1K3L w RG3Psnp 54", + "l3k2nl/4g1gb1/1+S1pspp+P1/p1p6/3n4p/2PPR1P2/P2bPP2P/5GS2/LN1K4L w R2Pgsn2p 50", + "lns+R4l/1p1p5/p1pkppB1p/6p2/1R7/6P1P/P1PPnPS2/2+b1G1g2/L3K1sNL b 2GS3Pnp 51", + "1+P1gkg2l/2s3s+P1/3ppp2p/P1p2npp1/l2N1+b3/3KP1P2/N2P1PS1P/2+p1G2R1/L1+r3sNL w Pbgp 58", + "lnsG5/4g4/prpp1p1pp/1p4p2/4+B3k/2P1P4/P+b1PSP1LP/4K2SL/2G2G1r1 b SP3nl3p 71", + "l5+R1l/4kS3/p4pnpp/2Pppb3/6p1P/P2s5/NP2+nPPR1/2+bS2GK1/L6NL b 3GSP4p 93", + "lR5nl/5k1b1/2gp3p1/2s1p1P2/p4N2p/P3PpR2/1PPP1P2P/2G1K2s1/LN6L b GSN2Pbgs2p 83", + "l1+R5l/2pS5/p2pp+P1pp/2k3p2/2N4P1/PP2R1P1P/2+pPP1N2/2GSG1bs1/LN1K4L b 2GSNPbp 73", + "lnsg4l/1r1b5/p1pp1+N1+R1/4p3p/9/P3SSk2/NpPPPPg1P/2GK5/L1S4NL b 2Pbg4p 91", + // head -10 mate5.sfen + "l2gkg2l/2s3s2/p1nppp1pp/2p3p2/P4P1P1/4n3P/1PPPG1N2/1BKS2+s2/LN3+r3 w RBgl3p 72", + "lnsgs2+Pl/3kg4/p1pppN2p/6pp1/9/7R1/P1PP1Sg1P/1S3+b3/LN5KL w Nrbg6p 58", + "lnG4nl/5k3/p1p+R1g1p1/1p1p3sp/5N3/2P1p1p2/PP1GP3P/1SG2+p1+b1/LN1K4L w Srbs4p 60", + "ln4knl/4+N2b1/4ppsG1/p1P5p/2G3pp1/3P1P2P/P2+pP1P2/2+srSK3/L+r3G1NL w G4Pbs 78", + "ln5+Pl/3s1kg+R1/p2ppl2p/2ps1Bp2/P8/2P3P1P/N2gP4/5KS2/L+r3G1N+b b GS3Pn3p 57", + "l3k3l/1r1sg1B2/3p2+R1p/2p1pN2P/9/pPP1PP3/3PK1P2/2G1sg3/LNS2+n1NL b 5Pbgsp 69", + "ln1g4l/2s2+R3/2kp2G2/p1r2pp1p/4S4/Pp1Gp1P2/BP1P4P/2GKs1+bP1/LNN5L w SN4Pp 88", + "ln1+P1GBnl/s8/p1p1p1kpp/3P2p2/5p3/Pp3PPP1/1P1SP3P/2R6/1N1GKG1NL b BGLr2sp 67", + "ln1s3nl/1+S4+B2/p1p1k2pp/3p2P2/7P1/P1Pnpp1R1/1P1P+lP2P/2G1G4/L1SKP2N+b b R2GPsp 73", + "lnB2k1+Pl/4g1g2/p1pp2ppp/5r3/3s2P1P/NpPnPP3/P5Sb1/3S5/LNG1KG2L w rs4p 74", + // head -10 mate7.sfen + "ln1g3+Rl/2sk1s+P2/2ppppb1p/p1b3p2/8P/P4P3/2PPP1P2/1+r2GS3/LN+p2KGNL w GN2Ps 36", + "ln1g2B+Rl/2s6/pPppppk2/6p1p/9/4P1P1P/P1PPSP3/3+psK3/L+r3G1NL b SNb2gn2p 39", + "ln+P3s+Pl/2+R1Gsk2/p3pp1g1/4r1ppp/1NS6/6P2/PP1+bPPS1P/3+p1K3/LG3G1NL w Nb3p 72", + "lnsgk2+Pl/6+N2/p1pp2p1p/4p2R1/9/2P3P2/P2PPPN1P/4s1g1K/L4+r2L w 2B2SN4P2g 56", + "l+P1g2+S1l/2sk5/p1ppppngp/6p2/9/6P2/P1+bPPP2P/2+r2S3/+rN2GK1NL w SNbgl4p 56", + "l2R2snl/4gkg2/p+P1ppp2p/2p3pp1/9/1nPPP4/P1G1GPP1P/3K1Ss2/+r3Bb1NL w N2Psl 68", + "l6nl/3k2+B2/p1n1g2pp/2G1ppp2/2P2N1P1/3P2P1P/Ps1GP4/1+rSK2R2/LN6L b G3Pb2s2p 77", + "+N5snl/4+N1gp1/1b1p1pkP1/1s1l2pLp/4p+b3/P1P6/1P1PPPP1P/2+rSK2L1/2+r1S1GN1 w 2P2gp 84", + // TODO: "ln3kgRl/2s1g2p1/2ppppn1p/p5p2/6b2/P3P4/1+rPP1PP1P/1P4S2/LNSK1G1NL w GPbsp 50", + "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn ghi_problems() { + let test_cases = vec![ + "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:1 + "3Bp2n1/5+R2+B/p2p1GSp1/8p/Pn5l1/1n2SNP2/2pPPS1Pk/1P1SK1G2/L1G1G4 b RL3Pl3p 131", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate7.sfen:71 + "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn other_problems() { + let test_cases = vec![ + "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn 無駄合駒() { + let test_cases = vec![ + "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn 不詰() { + let test_cases = vec![ + "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(1))) { + Ok(ret) => { + assert!(ret.is_empty(), "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } +} diff --git a/src/bin/main.rs b/src/main.rs similarity index 99% rename from src/bin/main.rs rename to src/main.rs index dd7e8fc..f414d25 100644 --- a/src/bin/main.rs +++ b/src/main.rs @@ -4,12 +4,12 @@ use shogi_converter::kif_converter::{parse_kif, KifError}; use shogi_converter::Record; use shogi_core::{Color, Hand, PartialPosition, PieceKind, Square, ToUsi}; use shogi_usi_parser::FromUsi; +use solver::solve; use std::fs::File; use std::io::{BufRead, Read}; use std::time::{Duration, Instant}; use thiserror::Error; use tsumeshogi_solver::implementations::{HashMapTable, YasaiPosition}; -use tsumeshogi_solver::solve; #[derive(Error, Debug)] enum ParseError { diff --git a/src/solver.rs b/src/solver.rs deleted file mode 100644 index be4cd78..0000000 --- a/src/solver.rs +++ /dev/null @@ -1,210 +0,0 @@ -use dfpn::search::Search; -use dfpn::{Node, Position, Table, INF}; -use dfpn_extended::{CancelableSearcher, CanceledError}; -use shogi_core::{Move, PartialPosition}; -use std::collections::HashSet; -use std::time::Duration; - -pub trait CalculateResult { - fn calculate_result_and_score(&self, moves: &[Move]) -> (Vec, usize); -} - -pub fn solve( - position: PartialPosition, - timeout: Option, -) -> Result, CanceledError> -where - P: Position + From + CalculateResult, - T: Table, -{ - let pos = P::from(position); - let mut searcher: CancelableSearcher = CancelableSearcher::new(pos, timeout); - searcher.dfpn_search().map(|_| { - let mut solutions = Vec::new(); - search_all_mates( - &mut searcher, - &mut Vec::new(), - &mut HashSet::new(), - &mut solutions, - ); - solutions.sort_by_cached_key(|&(_, score)| score); - solutions.dedup(); - solutions - .last() - .map_or(Vec::new(), |(moves, _)| moves.clone()) - }) -} - -fn search_all_mates( - searcher: &mut CancelableSearcher, - moves: &mut Vec, - hashes: &mut HashSet, - solutions: &mut Vec<(Vec, usize)>, -) where - P: Position + CalculateResult, - T: Table, -{ - let (node, mate_pd) = if moves.len() & 1 == 0 { - (Node::Or, (INF, 0)) - } else { - (Node::And, (0, INF)) - }; - let mate_moves = searcher - .generate_legal_moves(node) - .into_iter() - .filter(|(_, h)| !hashes.contains(h) && searcher.look_up_hash(h) == mate_pd) - .collect::>(); - if node == Node::And && mate_moves.is_empty() { - solutions.push(searcher.pos.calculate_result_and_score(moves)); - } else { - for &(m, h) in &mate_moves { - moves.push(m.into()); - hashes.insert(h); - searcher.do_move(m); - search_all_mates(searcher, moves, hashes, solutions); - searcher.undo_move(m); - moves.pop(); - hashes.remove(&h); - } - } -} - -#[cfg(test)] -mod tests { - use crate::implementations::{HashMapTable, YasaiPosition}; - use shogi_core::PartialPosition; - use shogi_usi_parser::FromUsi; - - use super::solve; - use std::time::Duration; - - #[test] - fn solve_mates() { - // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ - let test_cases = vec![ - // head -10 mate3.sfen - "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", - "l3kgsnl/9/p1pS+Bp3/7pp/6PP1/9/PPPPPPn1P/1B1GG2+r1/LNS1K3L w RG3Psnp 54", - "l3k2nl/4g1gb1/1+S1pspp+P1/p1p6/3n4p/2PPR1P2/P2bPP2P/5GS2/LN1K4L w R2Pgsn2p 50", - "lns+R4l/1p1p5/p1pkppB1p/6p2/1R7/6P1P/P1PPnPS2/2+b1G1g2/L3K1sNL b 2GS3Pnp 51", - "1+P1gkg2l/2s3s+P1/3ppp2p/P1p2npp1/l2N1+b3/3KP1P2/N2P1PS1P/2+p1G2R1/L1+r3sNL w Pbgp 58", - "lnsG5/4g4/prpp1p1pp/1p4p2/4+B3k/2P1P4/P+b1PSP1LP/4K2SL/2G2G1r1 b SP3nl3p 71", - "l5+R1l/4kS3/p4pnpp/2Pppb3/6p1P/P2s5/NP2+nPPR1/2+bS2GK1/L6NL b 3GSP4p 93", - "lR5nl/5k1b1/2gp3p1/2s1p1P2/p4N2p/P3PpR2/1PPP1P2P/2G1K2s1/LN6L b GSN2Pbgs2p 83", - "l1+R5l/2pS5/p2pp+P1pp/2k3p2/2N4P1/PP2R1P1P/2+pPP1N2/2GSG1bs1/LN1K4L b 2GSNPbp 73", - "lnsg4l/1r1b5/p1pp1+N1+R1/4p3p/9/P3SSk2/NpPPPPg1P/2GK5/L1S4NL b 2Pbg4p 91", - // head -10 mate5.sfen - "l2gkg2l/2s3s2/p1nppp1pp/2p3p2/P4P1P1/4n3P/1PPPG1N2/1BKS2+s2/LN3+r3 w RBgl3p 72", - "lnsgs2+Pl/3kg4/p1pppN2p/6pp1/9/7R1/P1PP1Sg1P/1S3+b3/LN5KL w Nrbg6p 58", - "lnG4nl/5k3/p1p+R1g1p1/1p1p3sp/5N3/2P1p1p2/PP1GP3P/1SG2+p1+b1/LN1K4L w Srbs4p 60", - "ln4knl/4+N2b1/4ppsG1/p1P5p/2G3pp1/3P1P2P/P2+pP1P2/2+srSK3/L+r3G1NL w G4Pbs 78", - "ln5+Pl/3s1kg+R1/p2ppl2p/2ps1Bp2/P8/2P3P1P/N2gP4/5KS2/L+r3G1N+b b GS3Pn3p 57", - "l3k3l/1r1sg1B2/3p2+R1p/2p1pN2P/9/pPP1PP3/3PK1P2/2G1sg3/LNS2+n1NL b 5Pbgsp 69", - "ln1g4l/2s2+R3/2kp2G2/p1r2pp1p/4S4/Pp1Gp1P2/BP1P4P/2GKs1+bP1/LNN5L w SN4Pp 88", - "ln1+P1GBnl/s8/p1p1p1kpp/3P2p2/5p3/Pp3PPP1/1P1SP3P/2R6/1N1GKG1NL b BGLr2sp 67", - "ln1s3nl/1+S4+B2/p1p1k2pp/3p2P2/7P1/P1Pnpp1R1/1P1P+lP2P/2G1G4/L1SKP2N+b b R2GPsp 73", - "lnB2k1+Pl/4g1g2/p1pp2ppp/5r3/3s2P1P/NpPnPP3/P5Sb1/3S5/LNG1KG2L w rs4p 74", - // head -10 mate7.sfen - "ln1g3+Rl/2sk1s+P2/2ppppb1p/p1b3p2/8P/P4P3/2PPP1P2/1+r2GS3/LN+p2KGNL w GN2Ps 36", - "ln1g2B+Rl/2s6/pPppppk2/6p1p/9/4P1P1P/P1PPSP3/3+psK3/L+r3G1NL b SNb2gn2p 39", - "ln+P3s+Pl/2+R1Gsk2/p3pp1g1/4r1ppp/1NS6/6P2/PP1+bPPS1P/3+p1K3/LG3G1NL w Nb3p 72", - "lnsgk2+Pl/6+N2/p1pp2p1p/4p2R1/9/2P3P2/P2PPPN1P/4s1g1K/L4+r2L w 2B2SN4P2g 56", - "l+P1g2+S1l/2sk5/p1ppppngp/6p2/9/6P2/P1+bPPP2P/2+r2S3/+rN2GK1NL w SNbgl4p 56", - "l2R2snl/4gkg2/p+P1ppp2p/2p3pp1/9/1nPPP4/P1G1GPP1P/3K1Ss2/+r3Bb1NL w N2Psl 68", - "l6nl/3k2+B2/p1n1g2pp/2G1ppp2/2P2N1P1/3P2P1P/Ps1GP4/1+rSK2R2/LN6L b G3Pb2s2p 77", - "+N5snl/4+N1gp1/1b1p1pkP1/1s1l2pLp/4p+b3/P1P6/1P1PPPP1P/2+rSK2L1/2+r1S1GN1 w 2P2gp 84", - // TODO: "ln3kgRl/2s1g2p1/2ppppn1p/p5p2/6b2/P3P4/1+rPP1PP1P/1P4S2/LNSK1G1NL w GPbsp 50", - "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn ghi_problems() { - let test_cases = vec![ - "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:1 - "3Bp2n1/5+R2+B/p2p1GSp1/8p/Pn5l1/1n2SNP2/2pPPS1Pk/1P1SK1G2/L1G1G4 b RL3Pl3p 131", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate7.sfen:71 - "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn other_problems() { - let test_cases = vec![ - "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn 無駄合駒() { - let test_cases = vec![ - "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn 不詰() { - let test_cases = vec![ - "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(1))) { - Ok(ret) => { - assert!(ret.is_empty(), "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } -} From 72d25bb9aed167b3dc223318e5c164b8a0cf8065 Mon Sep 17 00:00:00 2001 From: sugyan Date: Thu, 14 Jul 2022 23:35:56 +0900 Subject: [PATCH 09/12] Add --output-format option --- Cargo.lock | 20 +++++++++++++ Cargo.toml | 1 + src/main.rs | 83 ++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26c638c..071cf0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,25 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c99c07667dfb58bac4cd9156e3d1ead9f3c78223a282301bc51f49b4efcf2d" +[[package]] +name = "shogi_legality_lite" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1b3b7f5092269af9fad7d3c6978824179d27f04f4bbaecad6ac4adf6f94f16" +dependencies = [ + "shogi_core", +] + +[[package]] +name = "shogi_official_kifu" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191aa04128cc27c9ee8b2f3d1ebc5df0764e8a874c5c9631d30d8f232b87d0c0" +dependencies = [ + "shogi_core", + "shogi_legality_lite", +] + [[package]] name = "shogi_usi_parser" version = "0.1.0" @@ -418,6 +437,7 @@ dependencies = [ "dfpn", "shogi-converter", "shogi_core", + "shogi_official_kifu", "shogi_usi_parser", "solver", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index d6c6fc5..fa77b9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ thiserror = "1.0.30" yasai = { git = "https://github.com/sugyan/yasai", tag = "0.5.0", features = ["simd"] } shogi_core = "0.1.4" shogi_usi_parser = "0.1.0" +shogi_official_kifu = "0.1.1" [profile.release] lto = true diff --git a/src/main.rs b/src/main.rs index f414d25..d279b77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ use clap::{ArgEnum, Parser}; use csa::{parse_csa, CsaError}; use shogi_converter::kif_converter::{parse_kif, KifError}; use shogi_converter::Record; -use shogi_core::{Color, Hand, PartialPosition, PieceKind, Square, ToUsi}; +use shogi_core::{Color, Hand, Move, PartialPosition, PieceKind, Square, ToUsi}; +use shogi_official_kifu::display_single_move_kansuji; use shogi_usi_parser::FromUsi; use solver::solve; use std::fs::File; @@ -59,8 +60,11 @@ struct Args { #[clap(short, long)] verbose: bool, /// Input format - #[clap(short, long, arg_enum, value_name = "FORMAT", default_value_t = Format::Sfen)] - format: Format, + #[clap(short, long, arg_enum, value_name = "FORMAT", default_value_t = InputFormat::Sfen)] + input_format: InputFormat, + /// Output format + #[clap(short, long, arg_enum, value_name = "FORMAT", default_value_t = OutputFormat::Usi)] + output_format: OutputFormat, /// Time limit to solve (seconds) #[clap(short, long)] timeout: Option, @@ -70,18 +74,25 @@ struct Args { } #[derive(Clone, ArgEnum)] -enum Format { +enum InputFormat { Sfen, Csa, Kif, } +#[derive(Clone, Copy, ArgEnum)] +enum OutputFormat { + Usi, + Csa, + Kifu, +} + fn main() -> Result<(), ParseError> { let args = Args::parse(); - match args.format { - Format::Sfen => run_sfen(&args), - Format::Csa => run_parse(CsaParser, &args), - Format::Kif => run_parse(KifParser, &args), + match args.input_format { + InputFormat::Sfen => run_sfen(&args), + InputFormat::Csa => run_parse(CsaParser, &args), + InputFormat::Kif => run_parse(KifParser, &args), } } @@ -128,15 +139,11 @@ fn run(sfen: &str, input: &str, args: &Args) -> Result<(), ParseError> { println!("{}", pos2csa(&pos)); } let now = Instant::now(); - let result = - solve::(pos, args.timeout.map(Duration::from_secs_f32)).map( - |res| { - res.iter() - .map(|m| m.to_usi_owned()) - .collect::>() - .join(" ") - }, - ); + let result = solve::( + pos.clone(), + args.timeout.map(Duration::from_secs_f32), + ) + .map(|v| output(pos, v, args.output_format).join(" ")); println!("{:?}", result); if args.verbose { println!("elapsed: {:?}", now.elapsed()); @@ -144,6 +151,28 @@ fn run(sfen: &str, input: &str, args: &Args) -> Result<(), ParseError> { Ok(()) } +fn output(pos: PartialPosition, v: Vec, format: OutputFormat) -> Vec { + match format { + OutputFormat::Usi => v.iter().map(|m| m.to_usi_owned()).collect(), + OutputFormat::Csa => v + .iter() + .scan(pos, |pos, &m| { + let ret = move2action(pos, m).to_string(); + pos.make_move(m); + Some(ret) + }) + .collect(), + OutputFormat::Kifu => v + .iter() + .scan(pos, |pos, &m| { + let ret = display_single_move_kansuji(pos, m); + pos.make_move(m); + ret + }) + .collect(), + } +} + fn pos2csa(pos: &PartialPosition) -> String { let mut remains = Hand::default(); for (pk, num) in [ @@ -212,6 +241,22 @@ fn pos2csa(pos: &PartialPosition) -> String { .to_string() } +fn move2action(pos: &PartialPosition, m: Move) -> csa::Action { + match m { + Move::Normal { from, to, promote } => { + let p = pos.piece_at(from).expect("piece not found"); + let pk = if promote { p.promote().unwrap_or(p) } else { p }.piece_kind(); + csa::Action::Move(c2c(pos.side_to_move()), sq2sq(from), sq2sq(to), pk2pt(pk)) + } + Move::Drop { to, piece } => csa::Action::Move( + c2c(pos.side_to_move()), + csa::Square::new(0, 0), + sq2sq(to), + pk2pt(piece.piece_kind()), + ), + } +} + fn pk2pt(pk: PieceKind) -> csa::PieceType { match pk { PieceKind::Pawn => csa::PieceType::Pawn, @@ -237,3 +282,7 @@ fn c2c(c: Color) -> csa::Color { Color::White => csa::Color::White, } } + +fn sq2sq(sq: Square) -> csa::Square { + csa::Square::new(sq.file(), sq.rank()) +} From 39171af22cf546d757793ddf3b38236f84b1bc16 Mon Sep 17 00:00:00 2001 From: sugyan Date: Thu, 14 Jul 2022 23:43:15 +0900 Subject: [PATCH 10/12] Move modules --- Cargo.lock | 11 +- Cargo.toml | 1 - solver/Cargo.toml | 2 + {src => solver/src}/implementations.rs | 0 .../src}/implementations/hashmap_table.rs | 0 .../src}/implementations/vec_table.rs | 0 .../src}/implementations/yasai_position.rs | 2 +- solver/src/lib.rs | 140 ++++++++++++++++++ src/lib.rs | 140 ------------------ src/main.rs | 2 +- 10 files changed, 150 insertions(+), 148 deletions(-) rename {src => solver/src}/implementations.rs (100%) rename {src => solver/src}/implementations/hashmap_table.rs (100%) rename {src => solver/src}/implementations/vec_table.rs (100%) rename {src => solver/src}/implementations/yasai_position.rs (99%) delete mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 071cf0c..6172a6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if", "libc", @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "os_str_bytes" @@ -363,6 +363,8 @@ dependencies = [ "dfpn", "dfpn-extended", "shogi_core", + "shogi_usi_parser", + "yasai", ] [[package]] @@ -441,7 +443,6 @@ dependencies = [ "shogi_usi_parser", "solver", "thiserror", - "yasai", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fa77b9e..45cbcc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ solver = { path = "./solver" } csa = "1.0.2" clap = { version = "3.1.1", features = ["derive"] } thiserror = "1.0.30" -yasai = { git = "https://github.com/sugyan/yasai", tag = "0.5.0", features = ["simd"] } shogi_core = "0.1.4" shogi_usi_parser = "0.1.0" shogi_official_kifu = "0.1.1" diff --git a/solver/Cargo.toml b/solver/Cargo.toml index 509887a..5c6350f 100644 --- a/solver/Cargo.toml +++ b/solver/Cargo.toml @@ -9,3 +9,5 @@ edition = "2021" dfpn = { path = "../dfpn" } dfpn-extended = { path = "../dfpn-extended" } shogi_core = "0.1.4" +shogi_usi_parser = "0.1.0" +yasai = { git = "https://github.com/sugyan/yasai", tag = "0.5.0", features = ["simd"] } diff --git a/src/implementations.rs b/solver/src/implementations.rs similarity index 100% rename from src/implementations.rs rename to solver/src/implementations.rs diff --git a/src/implementations/hashmap_table.rs b/solver/src/implementations/hashmap_table.rs similarity index 100% rename from src/implementations/hashmap_table.rs rename to solver/src/implementations/hashmap_table.rs diff --git a/src/implementations/vec_table.rs b/solver/src/implementations/vec_table.rs similarity index 100% rename from src/implementations/vec_table.rs rename to solver/src/implementations/vec_table.rs diff --git a/src/implementations/yasai_position.rs b/solver/src/implementations/yasai_position.rs similarity index 99% rename from src/implementations/yasai_position.rs rename to solver/src/implementations/yasai_position.rs index 4a0eb90..6a0b5f1 100644 --- a/src/implementations/yasai_position.rs +++ b/solver/src/implementations/yasai_position.rs @@ -1,6 +1,6 @@ +use crate::CalculateResult; use dfpn::Node; use shogi_core::{Hand, Move, PartialPosition}; -use solver::CalculateResult; use yasai::Position; pub struct YasaiPosition(Position); diff --git a/solver/src/lib.rs b/solver/src/lib.rs index 35a6d78..04d1690 100644 --- a/solver/src/lib.rs +++ b/solver/src/lib.rs @@ -1,3 +1,143 @@ +pub mod implementations; mod solver; pub use solver::*; + +#[cfg(test)] +mod tests { + use crate::implementations::{HashMapTable, YasaiPosition}; + use crate::solve; + use shogi_core::PartialPosition; + use shogi_usi_parser::FromUsi; + use std::time::Duration; + + #[test] + fn solve_mates() { + // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ + let test_cases = vec![ + // head -10 mate3.sfen + "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", + "l3kgsnl/9/p1pS+Bp3/7pp/6PP1/9/PPPPPPn1P/1B1GG2+r1/LNS1K3L w RG3Psnp 54", + "l3k2nl/4g1gb1/1+S1pspp+P1/p1p6/3n4p/2PPR1P2/P2bPP2P/5GS2/LN1K4L w R2Pgsn2p 50", + "lns+R4l/1p1p5/p1pkppB1p/6p2/1R7/6P1P/P1PPnPS2/2+b1G1g2/L3K1sNL b 2GS3Pnp 51", + "1+P1gkg2l/2s3s+P1/3ppp2p/P1p2npp1/l2N1+b3/3KP1P2/N2P1PS1P/2+p1G2R1/L1+r3sNL w Pbgp 58", + "lnsG5/4g4/prpp1p1pp/1p4p2/4+B3k/2P1P4/P+b1PSP1LP/4K2SL/2G2G1r1 b SP3nl3p 71", + "l5+R1l/4kS3/p4pnpp/2Pppb3/6p1P/P2s5/NP2+nPPR1/2+bS2GK1/L6NL b 3GSP4p 93", + "lR5nl/5k1b1/2gp3p1/2s1p1P2/p4N2p/P3PpR2/1PPP1P2P/2G1K2s1/LN6L b GSN2Pbgs2p 83", + "l1+R5l/2pS5/p2pp+P1pp/2k3p2/2N4P1/PP2R1P1P/2+pPP1N2/2GSG1bs1/LN1K4L b 2GSNPbp 73", + "lnsg4l/1r1b5/p1pp1+N1+R1/4p3p/9/P3SSk2/NpPPPPg1P/2GK5/L1S4NL b 2Pbg4p 91", + // head -10 mate5.sfen + "l2gkg2l/2s3s2/p1nppp1pp/2p3p2/P4P1P1/4n3P/1PPPG1N2/1BKS2+s2/LN3+r3 w RBgl3p 72", + "lnsgs2+Pl/3kg4/p1pppN2p/6pp1/9/7R1/P1PP1Sg1P/1S3+b3/LN5KL w Nrbg6p 58", + "lnG4nl/5k3/p1p+R1g1p1/1p1p3sp/5N3/2P1p1p2/PP1GP3P/1SG2+p1+b1/LN1K4L w Srbs4p 60", + "ln4knl/4+N2b1/4ppsG1/p1P5p/2G3pp1/3P1P2P/P2+pP1P2/2+srSK3/L+r3G1NL w G4Pbs 78", + "ln5+Pl/3s1kg+R1/p2ppl2p/2ps1Bp2/P8/2P3P1P/N2gP4/5KS2/L+r3G1N+b b GS3Pn3p 57", + "l3k3l/1r1sg1B2/3p2+R1p/2p1pN2P/9/pPP1PP3/3PK1P2/2G1sg3/LNS2+n1NL b 5Pbgsp 69", + "ln1g4l/2s2+R3/2kp2G2/p1r2pp1p/4S4/Pp1Gp1P2/BP1P4P/2GKs1+bP1/LNN5L w SN4Pp 88", + "ln1+P1GBnl/s8/p1p1p1kpp/3P2p2/5p3/Pp3PPP1/1P1SP3P/2R6/1N1GKG1NL b BGLr2sp 67", + "ln1s3nl/1+S4+B2/p1p1k2pp/3p2P2/7P1/P1Pnpp1R1/1P1P+lP2P/2G1G4/L1SKP2N+b b R2GPsp 73", + "lnB2k1+Pl/4g1g2/p1pp2ppp/5r3/3s2P1P/NpPnPP3/P5Sb1/3S5/LNG1KG2L w rs4p 74", + // head -10 mate7.sfen + "ln1g3+Rl/2sk1s+P2/2ppppb1p/p1b3p2/8P/P4P3/2PPP1P2/1+r2GS3/LN+p2KGNL w GN2Ps 36", + "ln1g2B+Rl/2s6/pPppppk2/6p1p/9/4P1P1P/P1PPSP3/3+psK3/L+r3G1NL b SNb2gn2p 39", + "ln+P3s+Pl/2+R1Gsk2/p3pp1g1/4r1ppp/1NS6/6P2/PP1+bPPS1P/3+p1K3/LG3G1NL w Nb3p 72", + "lnsgk2+Pl/6+N2/p1pp2p1p/4p2R1/9/2P3P2/P2PPPN1P/4s1g1K/L4+r2L w 2B2SN4P2g 56", + "l+P1g2+S1l/2sk5/p1ppppngp/6p2/9/6P2/P1+bPPP2P/2+r2S3/+rN2GK1NL w SNbgl4p 56", + "l2R2snl/4gkg2/p+P1ppp2p/2p3pp1/9/1nPPP4/P1G1GPP1P/3K1Ss2/+r3Bb1NL w N2Psl 68", + "l6nl/3k2+B2/p1n1g2pp/2G1ppp2/2P2N1P1/3P2P1P/Ps1GP4/1+rSK2R2/LN6L b G3Pb2s2p 77", + "+N5snl/4+N1gp1/1b1p1pkP1/1s1l2pLp/4p+b3/P1P6/1P1PPPP1P/2+rSK2L1/2+r1S1GN1 w 2P2gp 84", + // TODO: "ln3kgRl/2s1g2p1/2ppppn1p/p5p2/6b2/P3P4/1+rPP1PP1P/1P4S2/LNSK1G1NL w GPbsp 50", + "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn ghi_problems() { + let test_cases = vec![ + "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:1 + "3Bp2n1/5+R2+B/p2p1GSp1/8p/Pn5l1/1n2SNP2/2pPPS1Pk/1P1SK1G2/L1G1G4 b RL3Pl3p 131", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate7.sfen:71 + "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn other_problems() { + let test_cases = vec![ + "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn 無駄合駒() { + let test_cases = vec![ + "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(5))) { + Ok(ret) => { + assert!(ret.len() % 2 == 1, "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } + + #[test] + fn 不詰() { + let test_cases = vec![ + "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position + ]; + for (i, &sfen) in test_cases.iter().enumerate() { + let pos = + PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); + match solve::(pos, Some(Duration::from_secs(1))) { + Ok(ret) => { + assert!(ret.is_empty(), "failed to solve #{i}"); + } + Err(e) => { + panic!("canceled #{i}: {e}"); + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index f632faf..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,140 +0,0 @@ -pub mod implementations; - -#[cfg(test)] -mod tests { - use crate::implementations::{HashMapTable, YasaiPosition}; - use shogi_core::PartialPosition; - use shogi_usi_parser::FromUsi; - use solver::solve; - use std::time::Duration; - - #[test] - fn solve_mates() { - // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ - let test_cases = vec![ - // head -10 mate3.sfen - "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", - "l3kgsnl/9/p1pS+Bp3/7pp/6PP1/9/PPPPPPn1P/1B1GG2+r1/LNS1K3L w RG3Psnp 54", - "l3k2nl/4g1gb1/1+S1pspp+P1/p1p6/3n4p/2PPR1P2/P2bPP2P/5GS2/LN1K4L w R2Pgsn2p 50", - "lns+R4l/1p1p5/p1pkppB1p/6p2/1R7/6P1P/P1PPnPS2/2+b1G1g2/L3K1sNL b 2GS3Pnp 51", - "1+P1gkg2l/2s3s+P1/3ppp2p/P1p2npp1/l2N1+b3/3KP1P2/N2P1PS1P/2+p1G2R1/L1+r3sNL w Pbgp 58", - "lnsG5/4g4/prpp1p1pp/1p4p2/4+B3k/2P1P4/P+b1PSP1LP/4K2SL/2G2G1r1 b SP3nl3p 71", - "l5+R1l/4kS3/p4pnpp/2Pppb3/6p1P/P2s5/NP2+nPPR1/2+bS2GK1/L6NL b 3GSP4p 93", - "lR5nl/5k1b1/2gp3p1/2s1p1P2/p4N2p/P3PpR2/1PPP1P2P/2G1K2s1/LN6L b GSN2Pbgs2p 83", - "l1+R5l/2pS5/p2pp+P1pp/2k3p2/2N4P1/PP2R1P1P/2+pPP1N2/2GSG1bs1/LN1K4L b 2GSNPbp 73", - "lnsg4l/1r1b5/p1pp1+N1+R1/4p3p/9/P3SSk2/NpPPPPg1P/2GK5/L1S4NL b 2Pbg4p 91", - // head -10 mate5.sfen - "l2gkg2l/2s3s2/p1nppp1pp/2p3p2/P4P1P1/4n3P/1PPPG1N2/1BKS2+s2/LN3+r3 w RBgl3p 72", - "lnsgs2+Pl/3kg4/p1pppN2p/6pp1/9/7R1/P1PP1Sg1P/1S3+b3/LN5KL w Nrbg6p 58", - "lnG4nl/5k3/p1p+R1g1p1/1p1p3sp/5N3/2P1p1p2/PP1GP3P/1SG2+p1+b1/LN1K4L w Srbs4p 60", - "ln4knl/4+N2b1/4ppsG1/p1P5p/2G3pp1/3P1P2P/P2+pP1P2/2+srSK3/L+r3G1NL w G4Pbs 78", - "ln5+Pl/3s1kg+R1/p2ppl2p/2ps1Bp2/P8/2P3P1P/N2gP4/5KS2/L+r3G1N+b b GS3Pn3p 57", - "l3k3l/1r1sg1B2/3p2+R1p/2p1pN2P/9/pPP1PP3/3PK1P2/2G1sg3/LNS2+n1NL b 5Pbgsp 69", - "ln1g4l/2s2+R3/2kp2G2/p1r2pp1p/4S4/Pp1Gp1P2/BP1P4P/2GKs1+bP1/LNN5L w SN4Pp 88", - "ln1+P1GBnl/s8/p1p1p1kpp/3P2p2/5p3/Pp3PPP1/1P1SP3P/2R6/1N1GKG1NL b BGLr2sp 67", - "ln1s3nl/1+S4+B2/p1p1k2pp/3p2P2/7P1/P1Pnpp1R1/1P1P+lP2P/2G1G4/L1SKP2N+b b R2GPsp 73", - "lnB2k1+Pl/4g1g2/p1pp2ppp/5r3/3s2P1P/NpPnPP3/P5Sb1/3S5/LNG1KG2L w rs4p 74", - // head -10 mate7.sfen - "ln1g3+Rl/2sk1s+P2/2ppppb1p/p1b3p2/8P/P4P3/2PPP1P2/1+r2GS3/LN+p2KGNL w GN2Ps 36", - "ln1g2B+Rl/2s6/pPppppk2/6p1p/9/4P1P1P/P1PPSP3/3+psK3/L+r3G1NL b SNb2gn2p 39", - "ln+P3s+Pl/2+R1Gsk2/p3pp1g1/4r1ppp/1NS6/6P2/PP1+bPPS1P/3+p1K3/LG3G1NL w Nb3p 72", - "lnsgk2+Pl/6+N2/p1pp2p1p/4p2R1/9/2P3P2/P2PPPN1P/4s1g1K/L4+r2L w 2B2SN4P2g 56", - "l+P1g2+S1l/2sk5/p1ppppngp/6p2/9/6P2/P1+bPPP2P/2+r2S3/+rN2GK1NL w SNbgl4p 56", - "l2R2snl/4gkg2/p+P1ppp2p/2p3pp1/9/1nPPP4/P1G1GPP1P/3K1Ss2/+r3Bb1NL w N2Psl 68", - "l6nl/3k2+B2/p1n1g2pp/2G1ppp2/2P2N1P1/3P2P1P/Ps1GP4/1+rSK2R2/LN6L b G3Pb2s2p 77", - "+N5snl/4+N1gp1/1b1p1pkP1/1s1l2pLp/4p+b3/P1P6/1P1PPPP1P/2+rSK2L1/2+r1S1GN1 w 2P2gp 84", - // TODO: "ln3kgRl/2s1g2p1/2ppppn1p/p5p2/6b2/P3P4/1+rPP1PP1P/1P4S2/LNSK1G1NL w GPbsp 50", - "3g4l/+R1sg2S2/p1npk1s+Rp/2pb2p2/4g2N1/1p7/P1PP1PP1P/1P1S5/LNK2G1+lL b N3Pb2p 71", - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn ghi_problems() { - let test_cases = vec![ - "ln1gkg1nl/6+P2/2sppps1p/2p3p2/p8/P1P1P3P/2NP1PP2/3s1KSR1/L1+b2G1NL w R2Pbgp 42", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:1 - "3Bp2n1/5+R2+B/p2p1GSp1/8p/Pn5l1/1n2SNP2/2pPPS1Pk/1P1SK1G2/L1G1G4 b RL3Pl3p 131", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate7.sfen:71 - "7+P1/5R1s1/6ks1/9/5L1p1/9/9/9/9 b R2b4g2s4n3l16p 1", // https://www.shogi.or.jp/tsume_shogi/everyday/20211183_1.html - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn other_problems() { - let test_cases = vec![ - "ln1g3k1/5G2l/1+LspSp2p/2p1S2p1/2r3p2/p3P4/1P+BP1P+b1P/2GS5/L2K1G3 b NPr2n5p 79", // https://yaneuraou.yaneu.com/2020/12/25/christmas-present/ mate3.sfen:569 - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn 無駄合駒() { - let test_cases = vec![ - "7nl/5B1k1/6Ppp/5+R3/9/9/9/9/9 b Srb4g3s3n3l15p 1", // issues/5, - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(5))) { - Ok(ret) => { - assert!(ret.len() % 2 == 1, "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } - - #[test] - fn 不詰() { - let test_cases = vec![ - "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1", // initial position - ]; - for (i, &sfen) in test_cases.iter().enumerate() { - let pos = - PartialPosition::from_usi(&format!("sfen {sfen}")).expect("failed to parse sfen"); - match solve::(pos, Some(Duration::from_secs(1))) { - Ok(ret) => { - assert!(ret.is_empty(), "failed to solve #{i}"); - } - Err(e) => { - panic!("canceled #{i}: {e}"); - } - } - } - } -} diff --git a/src/main.rs b/src/main.rs index d279b77..7387be7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,12 @@ use shogi_converter::Record; use shogi_core::{Color, Hand, Move, PartialPosition, PieceKind, Square, ToUsi}; use shogi_official_kifu::display_single_move_kansuji; use shogi_usi_parser::FromUsi; +use solver::implementations::{HashMapTable, YasaiPosition}; use solver::solve; use std::fs::File; use std::io::{BufRead, Read}; use std::time::{Duration, Instant}; use thiserror::Error; -use tsumeshogi_solver::implementations::{HashMapTable, YasaiPosition}; #[derive(Error, Debug)] enum ParseError { From 7da1045c8f4b362bbd7d3cf37a5013a024c7d03b Mon Sep 17 00:00:00 2001 From: sugyan Date: Fri, 15 Jul 2022 10:00:12 +0900 Subject: [PATCH 11/12] Update README --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 48 +++++++++++------------------------------------- benches/bench.rs | 2 +- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6172a6a..4220c4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ [[package]] name = "tsumeshogi-solver" -version = "0.5.2" +version = "0.6.0" dependencies = [ "clap", "csa", diff --git a/Cargo.toml b/Cargo.toml index 45cbcc4..ea6ffaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tsumeshogi-solver" -version = "0.5.2" +version = "0.6.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index e88826e..8b9f713 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ # tsumeshogi-solver ``` -$ cat 3.csa -V2.2 +% ./tsumeshogi-solver -v '9/9/3pp4/+r2k1p3/2L1+p4/2+R6/B8/B8/9 b 4g4s4n3l14p 1' +9/9/3pp4/+r2k1p3/2L1+p4/2+R6/B8/B8/9 b 4g4s4n3l14p 1: P1 * * * * * * * * * P2 * * * * * * * * * P3 * * * -FU-FU * * * * @@ -17,39 +17,14 @@ P9 * * * * * * * * * P-00AL + -$ ./tsumeshogi-solver --format csa -v 3.csa - 9 8 7 6 5 4 3 2 1 -+---+---+---+---+---+---+---+---+---+ -| | | | | | | | | | a -+---+---+---+---+---+---+---+---+---+ -| | | | | | | | | | b -+---+---+---+---+---+---+---+---+---+ -| | | | p| p| | | | | c -+---+---+---+---+---+---+---+---+---+ -| +r| | | k| | p| | | | d -+---+---+---+---+---+---+---+---+---+ -| | | L| | +p| | | | | e -+---+---+---+---+---+---+---+---+---+ -| | | +R| | | | | | | f -+---+---+---+---+---+---+---+---+---+ -| B| | | | | | | | | g -+---+---+---+---+---+---+---+---+---+ -| B| | | | | | | | | h -+---+---+---+---+---+---+---+---+---+ -| | | | | | | | | | i -+---+---+---+---+---+---+---+---+---+ -Side to move: Black -Hand (Black): -Hand (White): g4 s4 n4 l3 p14 -Ply: 1 - -Ok(["+7572NY", "-0086KE", "+7673RY"]) +Ok("7e7b+ N*8f 7f7c") +elapsed: 35.561833ms ``` ### Run ``` -Tsumeshogi Solver 0.5.2 +Tsumeshogi Solver 0.6.0 USAGE: tsumeshogi-solver [OPTIONS] ... @@ -58,13 +33,12 @@ ARGS: ... Input files or SFEN strings OPTIONS: - --backend Backend implementation [default: yasai] [possible values: shogi, - yasai] - -f, --format Input format [default: sfen] [possible values: sfen, csa, kif] - -h, --help Print help information - -t, --timeout Time limit to solve (seconds) - -v, --verbose Verbose mode - -V, --version Print version information + -h, --help Print help information + -i, --input-format Input format [default: sfen] [possible values: sfen, csa, kif] + -o, --output-format Output format [default: usi] [possible values: usi, csa, kifu] + -t, --timeout Time limit to solve (seconds) + -v, --verbose Verbose mode + -V, --version Print version information ``` diff --git a/benches/bench.rs b/benches/bench.rs index deec595..7d76d67 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -4,7 +4,7 @@ use dfpn::search::Search; use dfpn::DefaultSearcher; use shogi_core::PartialPosition; use shogi_usi_parser::FromUsi; -use tsumeshogi_solver::implementations::{HashMapTable, VecTable, YasaiPosition}; +use solver::implementations::{HashMapTable, VecTable, YasaiPosition}; fn test_cases() -> Vec { vec![ From 4d9b174acec47e4e96b40289dceb63775fbfa3ff Mon Sep 17 00:00:00 2001 From: sugyan Date: Fri, 15 Jul 2022 10:12:45 +0900 Subject: [PATCH 12/12] Fix Cargo.toml --- Cargo.lock | 1 - Cargo.toml | 6 +++++- solver/Cargo.toml | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4220c4e..2120de3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,7 +436,6 @@ version = "0.6.0" dependencies = [ "clap", "csa", - "dfpn", "shogi-converter", "shogi_core", "shogi_official_kifu", diff --git a/Cargo.toml b/Cargo.toml index ea6ffaf..6a25cb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dfpn = { path = "./dfpn" } shogi-converter = { path = "./shogi-converter" } solver = { path = "./solver" } csa = "1.0.2" @@ -18,3 +17,8 @@ shogi_official_kifu = "0.1.1" [profile.release] lto = true + +[workspace] +members = [ + "solver", +] diff --git a/solver/Cargo.toml b/solver/Cargo.toml index 5c6350f..fea0fa7 100644 --- a/solver/Cargo.toml +++ b/solver/Cargo.toml @@ -9,5 +9,7 @@ edition = "2021" dfpn = { path = "../dfpn" } dfpn-extended = { path = "../dfpn-extended" } shogi_core = "0.1.4" -shogi_usi_parser = "0.1.0" yasai = { git = "https://github.com/sugyan/yasai", tag = "0.5.0", features = ["simd"] } + +[dev-dependencies] +shogi_usi_parser = "0.1.0"