Added rust solver to the repository

This commit is contained in:
Lukas Wölfer
2025-08-08 19:16:08 +02:00
parent a9ca38e812
commit 5fdf1602eb
91 changed files with 7047 additions and 0 deletions

View 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"}

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 128 KiB

View 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, &[]);
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,5 @@
mod loop_move_avoider;
mod unique;
pub(crate) use unique::*;
mod base;
pub(crate) use base::*;

View File

@@ -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();
}
}
}

View 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);
}
}
}

View File

@@ -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) {
}
}

View File

@@ -0,0 +1,5 @@
mod action_board;
pub mod adapter;
mod base;
mod stack_frame;
pub(crate) use base::*;

View File

@@ -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;
}
}

View 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;

View 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 {});
}

View 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(());
}