Added rust solver to the repository
This commit is contained in:
12
solver-rs/lib/solving/Cargo.toml
Normal file
12
solver-rs/lib/solving/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "solving"
|
||||
version = "0.1.0"
|
||||
authors = ["Lukas Wölfer <lukas.woelfer@rwth-aachen.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
board = {path = "../board" }
|
||||
actions = {path = "../actions"}
|
||||
graphing = {package = "action_optimization", path = "../action_optimization"}
|
||||
2250
solver-rs/lib/solving/graph.svg
Normal file
2250
solver-rs/lib/solving/graph.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 128 KiB |
63
solver-rs/lib/solving/src/benchmark.rs
Normal file
63
solver-rs/lib/solving/src/benchmark.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use super::solve;
|
||||
|
||||
#[must_use]
|
||||
pub fn actions_correct(actions: &[actions::All], mut board: board::Board, verbose: bool) -> bool {
|
||||
for (index, action) in actions.iter().enumerate() {
|
||||
if verbose {
|
||||
println!("Action #{:3}: {}", index, action);
|
||||
}
|
||||
action.apply(&mut board);
|
||||
}
|
||||
return board.solved();
|
||||
}
|
||||
fn run_test(board: board::Board) -> (f32, usize) {
|
||||
use std::io::Write;
|
||||
std::io::stdout().flush().expect("Flushing did not work");
|
||||
assert_eq!(board.check(), Result::Ok(()));
|
||||
let start_time = std::time::Instant::now();
|
||||
let result = solve(&board);
|
||||
let result_time = start_time.elapsed().as_secs_f32();
|
||||
assert!(!result.is_err());
|
||||
let mut result_length: usize = 0;
|
||||
if let Result::Ok(actions) = result {
|
||||
assert!(actions_correct(&actions, board, false));
|
||||
result_length = actions.len();
|
||||
}
|
||||
return (result_time, result_length);
|
||||
}
|
||||
fn run_all_tests(
|
||||
dirname: &std::path::Path,
|
||||
exclude: &[&str],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for board_json in std::fs::read_dir(dirname)? {
|
||||
let board_json = board_json?;
|
||||
if exclude.contains(
|
||||
&board_json
|
||||
.path()
|
||||
.as_path()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let x = board::Board::from_file(&board_json.path())?;
|
||||
print!(
|
||||
"> {:<20} ",
|
||||
board_json.path().file_stem().unwrap().to_string_lossy(),
|
||||
);
|
||||
let (time, length) = run_test(x);
|
||||
println!("{:.02} {:3}", time, length);
|
||||
}
|
||||
return Result::Ok(());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn possible() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! # Errors
|
||||
let whole_board_dir: std::path::PathBuf = [board::TEST_BOARD_ROOT!(), "normal"].iter().collect();
|
||||
println!("{:?}", whole_board_dir);
|
||||
return run_all_tests(&whole_board_dir, &[]);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct ActionBoard {
|
||||
board: board::Board,
|
||||
stack: Vec<actions::All>,
|
||||
}
|
||||
|
||||
impl ActionBoard {
|
||||
pub(super) fn new(board: board::Board) -> Self {
|
||||
return Self {
|
||||
board,
|
||||
stack: Vec::new(),
|
||||
};
|
||||
}
|
||||
pub(super) fn push(&mut self, action: actions::All) {
|
||||
action.apply(&mut self.board);
|
||||
self.stack.push(action);
|
||||
debug_assert_eq!(self.board.check(), Result::Ok(()));
|
||||
}
|
||||
pub(super) fn pop(&mut self) -> Option<actions::All> {
|
||||
if let Option::Some(action) = self.stack.pop() {
|
||||
action.undo(&mut self.board);
|
||||
debug_assert_eq!(self.board.check(), Result::Ok(()));
|
||||
return Option::Some(action);
|
||||
}
|
||||
return Option::None;
|
||||
}
|
||||
pub(super) const fn board(&self) -> &board::Board {
|
||||
return &self.board;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use super::{ super::BoardState};
|
||||
use crate::board_state_iterator::BoardStateIterator;
|
||||
use super::loop_move_avoider::LoopMoveAvoider;
|
||||
pub(crate) trait BoardStateIteratorAdapter {
|
||||
fn advance(&mut self);
|
||||
fn get(&mut self) -> Option<BoardState>;
|
||||
fn next(&mut self) -> Option<BoardState> {
|
||||
self.advance();
|
||||
return self.get();
|
||||
}
|
||||
fn unique(self) -> super::Unique<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
return super::Unique::new(self);
|
||||
}
|
||||
|
||||
fn avoid_loops(self) -> LoopMoveAvoider<Self>
|
||||
where Self: Sized,
|
||||
{
|
||||
return LoopMoveAvoider::new(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BoardStateIteratorAdapter for BoardStateIterator {
|
||||
fn advance(&mut self) {
|
||||
self.advance();
|
||||
}
|
||||
fn get(&mut self) -> Option<BoardState> {
|
||||
return self.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use super::super::BoardState;
|
||||
use super::BoardStateIteratorAdapter;
|
||||
pub(crate) struct LoopMoveAvoider<T: BoardStateIteratorAdapter> {
|
||||
base_iterator: T,
|
||||
}
|
||||
impl<T: BoardStateIteratorAdapter> LoopMoveAvoider<T> {
|
||||
pub(crate) fn new(base_iterator: T) -> Self {
|
||||
return Self { base_iterator };
|
||||
}
|
||||
fn is_loop_move(state: &BoardState) -> bool {
|
||||
let last_action = state.action_stack().rev().next();
|
||||
if let Option::Some(actions::All::Move(last_move_action)) = last_action {
|
||||
let loop_move = state.action_stack().rev().skip(1).find(|x| {
|
||||
if let actions::All::Move(action) = x {
|
||||
return action.cards() == last_move_action.cards();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return loop_move.is_some();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: BoardStateIteratorAdapter> BoardStateIteratorAdapter for LoopMoveAvoider<T> {
|
||||
fn advance(&mut self) {
|
||||
while let Option::Some(mut state) = self.base_iterator.next() {
|
||||
if !Self::is_loop_move(&state) {
|
||||
return;
|
||||
}
|
||||
state.kill();
|
||||
}
|
||||
}
|
||||
fn get(&mut self) -> Option<BoardState> {
|
||||
return self.base_iterator.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod loop_move_avoider;
|
||||
mod unique;
|
||||
pub(crate) use unique::*;
|
||||
mod base;
|
||||
pub(crate) use base::*;
|
||||
@@ -0,0 +1,30 @@
|
||||
use super::super::BoardState;
|
||||
use super::BoardStateIteratorAdapter;
|
||||
|
||||
pub(crate) struct Unique<T> {
|
||||
base_iterator: T,
|
||||
known_boards: std::collections::HashSet<board::BoardEqHash>,
|
||||
}
|
||||
impl<T> Unique<T> {
|
||||
pub(crate) fn new(base_iterator: T) -> Self {
|
||||
return Self {
|
||||
base_iterator,
|
||||
known_boards: std::collections::HashSet::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl<T: BoardStateIteratorAdapter> BoardStateIteratorAdapter for Unique<T> {
|
||||
fn get(&mut self) -> Option<BoardState> {
|
||||
return self.base_iterator.get();
|
||||
}
|
||||
fn advance(&mut self) {
|
||||
while let Option::Some(mut nextboard) = self.base_iterator.next() {
|
||||
let eq_hash = nextboard.board().equivalence_hash();
|
||||
if !self.known_boards.contains(&eq_hash) {
|
||||
self.known_boards.insert(eq_hash);
|
||||
return;
|
||||
}
|
||||
nextboard.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
solver-rs/lib/solving/src/board_state_iterator/base.rs
Normal file
105
solver-rs/lib/solving/src/board_state_iterator/base.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use super::action_board::ActionBoard;
|
||||
use super::stack_frame::StackFrame;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BoardState<'a> {
|
||||
state_it: &'a mut BoardStateIterator,
|
||||
}
|
||||
impl<'a> BoardState<'a> {
|
||||
pub(crate) fn new(state_it: &'a mut BoardStateIterator) -> Self {
|
||||
return Self { state_it };
|
||||
}
|
||||
pub(crate) fn board(&'a self) -> &'a board::Board {
|
||||
return self.state_it.board.board();
|
||||
}
|
||||
pub(crate) fn action_stack(
|
||||
&'a self,
|
||||
) -> Box<dyn std::iter::DoubleEndedIterator<Item = actions::All> + 'a> {
|
||||
return self.state_it.action_stack();
|
||||
}
|
||||
pub(crate) fn kill(&'a mut self) {
|
||||
if let Option::Some(node) = self.state_it.stack.last_mut() {
|
||||
node.taint_child();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct BoardStateIterator {
|
||||
board: ActionBoard,
|
||||
stack: Vec<StackFrame>,
|
||||
}
|
||||
|
||||
impl BoardStateIterator {
|
||||
pub(crate) fn new(board: board::Board) -> Self {
|
||||
let mut result = Self {
|
||||
board: ActionBoard::new(board),
|
||||
stack: Vec::new(),
|
||||
};
|
||||
let actions = actions::possibilities::filter_actions(result.board.board());
|
||||
if !actions.is_empty() {
|
||||
result.push(actions);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn unwind(&mut self) {
|
||||
for i in (0..self.stack.len()).rev() {
|
||||
self.board.pop();
|
||||
if let Option::Some(action) = self.stack[i].next() {
|
||||
self.board.push(action.clone());
|
||||
return;
|
||||
}
|
||||
self.stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(&mut self) {
|
||||
if self.stack.is_empty() {
|
||||
return;
|
||||
}
|
||||
assert_ne!(self.stack.pop().unwrap().get(), Option::None);
|
||||
self.board.pop();
|
||||
self.stack.pop();
|
||||
self.unwind();
|
||||
}
|
||||
|
||||
fn push(&mut self, actions: Vec<actions::All>) {
|
||||
assert!(!actions.is_empty());
|
||||
self.board.push(actions.first().unwrap().clone());
|
||||
self.stack.push(StackFrame::new(actions));
|
||||
}
|
||||
|
||||
fn action_stack<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn std::iter::DoubleEndedIterator<Item = actions::All> + 'a> {
|
||||
return Box::new(self.stack.iter().map(|x| return x.get().unwrap().clone()));
|
||||
}
|
||||
|
||||
pub(crate) fn get(&mut self) -> Option<BoardState> {
|
||||
if self.stack.is_empty() {
|
||||
return Option::None;
|
||||
}
|
||||
return Option::Some(BoardState::new(self));
|
||||
}
|
||||
|
||||
pub(crate) fn next(&mut self) -> Option<BoardState> {
|
||||
self.advance();
|
||||
return self.get();
|
||||
}
|
||||
|
||||
pub(crate) fn advance(&mut self) {
|
||||
if let Option::Some(node) = self.stack.last() {
|
||||
if node.child_tainted() {
|
||||
return self.unwind();
|
||||
}
|
||||
}
|
||||
if let Option::Some(current_board) = self.get() {
|
||||
let actions = actions::possibilities::filter_actions(current_board.board());
|
||||
if actions.is_empty() {
|
||||
return self.unwind();
|
||||
}
|
||||
self.push(actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
enum SearcherNodeState<'a> {
|
||||
Unexplored,
|
||||
Exhausted,
|
||||
Exploring(Box<SearcherNode<'a>>),
|
||||
}
|
||||
|
||||
struct SearcherNode<'a> {
|
||||
children: Vec<SearcherNodeState<'a>>,
|
||||
parent: &'a SearcherNodeType<'a>,
|
||||
parent_id: usize,
|
||||
action: board::actions::All,
|
||||
}
|
||||
struct SearcherNodeRoot<'a> {
|
||||
children: Vec<SearcherNodeState<'a>>,
|
||||
}
|
||||
|
||||
enum SearcherNodeType<'a> {
|
||||
Root(SearcherNodeRoot<'a>),
|
||||
Normal(SearcherNode<'a>),
|
||||
}
|
||||
|
||||
fn toSearcherNodes<'a>(
|
||||
parent: &'a SearcherNodeType<'a>,
|
||||
actions: Vec<board::actions::All>,
|
||||
) -> Vec<SearcherNodeState<'a>> {
|
||||
return actions
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, action)| {
|
||||
return SearcherNodeState::Exploring(Box::new(SearcherNode {
|
||||
parent,
|
||||
children: vec![],
|
||||
parent_id: index,
|
||||
action,
|
||||
}));
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
struct Searcher<'a> {
|
||||
root: SearcherNodeRoot<'a>,
|
||||
board: board::Board,
|
||||
current_node: Option<&'a mut SearcherNodeState<'a>>,
|
||||
current_board: board::Board,
|
||||
}
|
||||
impl<'a> Searcher<'a> {
|
||||
pub(crate) fn new(board: board::Board) -> Self {
|
||||
let actions = super::filter_actions(&board, &board::possibilities::all_actions(&board));
|
||||
let mut root = SearcherNodeRoot { children: vec![] };
|
||||
root.children = toSearcherNodes(&SearcherNodeType::Root(root), actions);
|
||||
|
||||
let mut current_board = board.clone();
|
||||
let current_node = root.children.first_mut();
|
||||
if let Option::Some(SearcherNodeState::Exploring(action_node)) = current_node {
|
||||
action_node.action.apply(&mut current_board);
|
||||
}
|
||||
|
||||
return Self {
|
||||
root,
|
||||
board,
|
||||
current_node,
|
||||
current_board,
|
||||
};
|
||||
}
|
||||
pub(crate) fn advance(&'a mut self) {
|
||||
if let Option::Some(action_node) = self.current_node {
|
||||
if let SearcherNodeState::Exploring(expl_action_node) = action_node {
|
||||
let actions = super::filter_actions(
|
||||
&self.current_board,
|
||||
&board::possibilities::all_actions(&self.current_board),
|
||||
);
|
||||
expl_action_node.children =
|
||||
toSearcherNodes(&SearcherNodeType::Normal(**expl_action_node), actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn get(&'a self) -> Option<&'a board::Board> {
|
||||
self.current_node?;
|
||||
return Option::Some(&self.current_board);
|
||||
}
|
||||
pub(crate) fn next(&'a mut self) -> Option<&'a board::Board> {
|
||||
self.advance();
|
||||
return self.get();
|
||||
}
|
||||
pub(crate) fn kill_children(&'a mut self) {
|
||||
}
|
||||
}
|
||||
5
solver-rs/lib/solving/src/board_state_iterator/mod.rs
Normal file
5
solver-rs/lib/solving/src/board_state_iterator/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod action_board;
|
||||
pub mod adapter;
|
||||
mod base;
|
||||
mod stack_frame;
|
||||
pub(crate) use base::*;
|
||||
@@ -0,0 +1,36 @@
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct StackFrame {
|
||||
all_options: Vec<actions::All>,
|
||||
options_iter: usize,
|
||||
child_tainted: bool,
|
||||
}
|
||||
impl StackFrame {
|
||||
pub(super) fn new(actions: Vec<actions::All>) -> Self {
|
||||
return Self {
|
||||
all_options: actions,
|
||||
child_tainted: false,
|
||||
options_iter: 0,
|
||||
};
|
||||
}
|
||||
pub(super) fn get(&self) -> Option<&actions::All> {
|
||||
if self.options_iter >= self.all_options.len() {
|
||||
return Option::None;
|
||||
}
|
||||
return Option::Some(&self.all_options[self.options_iter]);
|
||||
}
|
||||
pub(super) fn advance(&mut self) {
|
||||
self.options_iter += 1;
|
||||
self.child_tainted = false;
|
||||
}
|
||||
pub(super) fn next(&mut self) -> Option<&actions::All> {
|
||||
self.advance();
|
||||
return self.get();
|
||||
}
|
||||
pub(super) fn taint_child(&mut self) {
|
||||
assert_eq!(self.child_tainted, false);
|
||||
self.child_tainted = true;
|
||||
}
|
||||
pub(super) fn child_tainted(&self) -> bool {
|
||||
return self.child_tainted;
|
||||
}
|
||||
}
|
||||
46
solver-rs/lib/solving/src/lib.rs
Normal file
46
solver-rs/lib/solving/src/lib.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::restriction,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
clippy::cargo
|
||||
)]
|
||||
#![allow(clippy::cargo)]
|
||||
// Style choices
|
||||
#![allow(
|
||||
clippy::missing_docs_in_private_items,
|
||||
clippy::needless_return,
|
||||
clippy::get_unwrap,
|
||||
clippy::indexing_slicing,
|
||||
clippy::explicit_iter_loop,
|
||||
clippy::redundant_pub_crate, // Just dont understand it, maybe fix instead?
|
||||
)]
|
||||
// Way too pedantic
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
// Useless
|
||||
#![allow(clippy::missing_inline_in_public_items, clippy::missing_const_for_fn)]
|
||||
// Useful for production
|
||||
#![allow(
|
||||
clippy::use_debug,
|
||||
clippy::print_stdout,
|
||||
clippy::dbg_macro,
|
||||
clippy::panic
|
||||
)]
|
||||
// Useful for improving code robustness
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::option_unwrap_used,
|
||||
clippy::result_unwrap_used,
|
||||
clippy::result_expect_used,
|
||||
// clippy::wildcard_enum_match_arm
|
||||
)]
|
||||
#![allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod board_state_iterator;
|
||||
mod solve;
|
||||
pub use solve::*;
|
||||
pub mod benchmark;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
30
solver-rs/lib/solving/src/solve.rs
Normal file
30
solver-rs/lib/solving/src/solve.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use super::board_state_iterator::adapter::BoardStateIteratorAdapter;
|
||||
use super::board_state_iterator::BoardStateIterator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return write!(f, "Board options exhausted, no solution found");
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub fn solve(solboard: &board::Board) -> Result<Vec<actions::All>, Error> {
|
||||
//! # Errors
|
||||
//! Returns error when no solution could be found
|
||||
if solboard.solved() {
|
||||
return Result::Ok(vec![]);
|
||||
}
|
||||
let mut stack = BoardStateIterator::new(solboard.clone())
|
||||
.unique()
|
||||
.avoid_loops();
|
||||
while let Option::Some(current_board) = stack.next() {
|
||||
if current_board.board().solved() {
|
||||
return Result::Ok(current_board.action_stack().collect());
|
||||
}
|
||||
}
|
||||
return Result::Err(Error {});
|
||||
}
|
||||
11
solver-rs/lib/solving/src/tests.rs
Normal file
11
solver-rs/lib/solving/src/tests.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::solve;
|
||||
|
||||
#[test]
|
||||
pub fn test_almost_solved() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! # Errors
|
||||
let x = board::load_test_board!("specific/scarce.json")?;
|
||||
assert_eq!(x.check(), Result::Ok(()));
|
||||
let result = solve(&x);
|
||||
assert!(result.is_ok());
|
||||
return Result::Ok(());
|
||||
}
|
||||
Reference in New Issue
Block a user