Added rust solver to the repository
This commit is contained in:
15
solver-rs/lib/action_optimization/Cargo.toml
Normal file
15
solver-rs/lib/action_optimization/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "action_optimization"
|
||||
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]
|
||||
serde = {version="1.0.105",features=["derive"]}
|
||||
serde_json = "1.0"
|
||||
petgraph = "0.5.1"
|
||||
|
||||
board = {path = "../board"}
|
||||
actions = {path = "../actions"}
|
||||
98
solver-rs/lib/action_optimization/src/drawing.rs
Normal file
98
solver-rs/lib/action_optimization/src/drawing.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use super::graph_entity::{to_graph, ActionGraph, RelationType};
|
||||
use std::{
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn dot_actions(actions: &[actions::All]) -> String {
|
||||
return dot_actiongraph(&to_graph(actions));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn dot_actiongraph(graph: &ActionGraph) -> String {
|
||||
let edge_attr = |relation_type: &RelationType| {
|
||||
let edge_style = match relation_type {
|
||||
RelationType::Move => "bold",
|
||||
RelationType::Unblock
|
||||
| RelationType::Clear
|
||||
| RelationType::Socket
|
||||
| RelationType::Goal => "solid",
|
||||
};
|
||||
let edge_color = match relation_type {
|
||||
RelationType::Move => "black",
|
||||
RelationType::Unblock => "green",
|
||||
RelationType::Clear => "grey",
|
||||
RelationType::Socket => "red",
|
||||
RelationType::Goal => "blue",
|
||||
};
|
||||
return format!("style=\"{}\" color=\"{}\"", edge_style, edge_color);
|
||||
};
|
||||
let node_attr = |action: &actions::All| {
|
||||
let node_color = match action {
|
||||
actions::All::Bunkerize(_) | actions::All::Move(_) => "white",
|
||||
actions::All::DragonKill(_) => "silver",
|
||||
actions::All::Goal(_) => "blue",
|
||||
actions::All::HuaKill(_) => "gold",
|
||||
};
|
||||
return format!(
|
||||
r#"style="filled" fillcolor="{}" label="{}" shape="rect""#,
|
||||
node_color,
|
||||
action.to_string().replace(r#"""#, r#"\""#)
|
||||
);
|
||||
};
|
||||
let dot_rep = petgraph::dot::Dot::with_attr_getters(
|
||||
&graph,
|
||||
&[
|
||||
petgraph::dot::Config::EdgeNoLabel,
|
||||
petgraph::dot::Config::NodeNoLabel,
|
||||
],
|
||||
&|_mygraph, myedge| return edge_attr(myedge.weight()),
|
||||
&|_mygraph, (_index, action)| {
|
||||
return node_attr(action);
|
||||
},
|
||||
)
|
||||
.to_string();
|
||||
return dot_rep;
|
||||
}
|
||||
|
||||
pub fn draw_graph(graph: &ActionGraph, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! # Errors
|
||||
//! File write error
|
||||
let input = dot_actiongraph(graph);
|
||||
let mut child = Command::new("dot")
|
||||
.args(&["-Tsvg", "-o", path.to_string_lossy().as_ref()])
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
std::io::Write::write_all(
|
||||
child
|
||||
.stdin
|
||||
.as_mut()
|
||||
.ok_or("Child process stdin has not been captured!")?,
|
||||
input.as_bytes(),
|
||||
)?;
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
println!(
|
||||
"Dot failed\n{}\n{}",
|
||||
std::str::from_utf8(&output.stdout).unwrap(),
|
||||
std::str::from_utf8(&output.stderr).unwrap()
|
||||
);
|
||||
// No idea how to return a custom error here
|
||||
}
|
||||
return Result::Ok(());
|
||||
}
|
||||
|
||||
pub fn draw_actions(
|
||||
actions: &[actions::All],
|
||||
path: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! # Errors
|
||||
//! File write error
|
||||
let graph = to_graph(actions);
|
||||
return draw_graph(&graph, path);
|
||||
}
|
||||
87
solver-rs/lib/action_optimization/src/graph_entity.rs
Normal file
87
solver-rs/lib/action_optimization/src/graph_entity.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use super::relation::{
|
||||
get_clear_parents, get_destination_parent, get_goal_parent, get_move_parents,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum RelationType {
|
||||
Move,
|
||||
Unblock,
|
||||
Clear,
|
||||
Socket,
|
||||
Goal,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RelationType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let stringed = match self {
|
||||
Self::Move => "Move",
|
||||
Self::Unblock => "Unblock",
|
||||
Self::Clear => "Clear",
|
||||
Self::Socket => "Socket",
|
||||
Self::Goal => "Goal",
|
||||
};
|
||||
return write!(f, "{}", stringed);
|
||||
}
|
||||
}
|
||||
pub type ActionGraph = petgraph::stable_graph::StableDiGraph<actions::All, RelationType>;
|
||||
|
||||
pub fn to_graph(actions: &[actions::All]) -> ActionGraph {
|
||||
let mut x = ActionGraph::new();
|
||||
|
||||
macro_rules! relations {
|
||||
($actions:expr, $index: expr, $( $x:expr, $y: expr ),*) => {{
|
||||
[
|
||||
$(
|
||||
($x)($actions, $index).into_iter().map(|b| return (b, $y)).collect::<Vec<(usize, RelationType)>>(),
|
||||
)*
|
||||
]
|
||||
}};
|
||||
}
|
||||
// can you ActionGraph::from_elements here
|
||||
for (index, action) in actions.iter().enumerate() {
|
||||
let current_node = x.add_node(action.clone());
|
||||
|
||||
let relations = relations!(
|
||||
actions,
|
||||
index,
|
||||
get_move_parents,
|
||||
RelationType::Move,
|
||||
get_clear_parents,
|
||||
RelationType::Clear,
|
||||
get_goal_parent,
|
||||
RelationType::Goal
|
||||
);
|
||||
for (parent, relation) in relations.iter().flatten() {
|
||||
x.add_edge(
|
||||
petgraph::stable_graph::NodeIndex::new(*parent),
|
||||
current_node,
|
||||
*relation,
|
||||
);
|
||||
}
|
||||
if let Option::Some((parent, relation)) = get_destination_parent(actions, index) {
|
||||
x.add_edge(
|
||||
petgraph::stable_graph::NodeIndex::new(parent),
|
||||
current_node,
|
||||
relation,
|
||||
);
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
pub fn from_graph(graph: &ActionGraph) -> Vec<actions::All> {
|
||||
match petgraph::algo::toposort(graph, Option::None) {
|
||||
Ok(topo_actions) => {
|
||||
let topo_actions = topo_actions
|
||||
.into_iter()
|
||||
.map(|index| return graph.node_weight(index).unwrap().clone())
|
||||
.collect::<Vec<actions::All>>();
|
||||
return topo_actions;
|
||||
}
|
||||
Err(c) => panic!(
|
||||
"Could not toposort the graph, {:#?}, Graph: {:?}",
|
||||
c,
|
||||
super::draw_graph(graph, std::path::Path::new("cycle_graph.svg"))
|
||||
),
|
||||
}
|
||||
}
|
||||
52
solver-rs/lib/action_optimization/src/lib.rs
Normal file
52
solver-rs/lib/action_optimization/src/lib.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#![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
|
||||
)]
|
||||
// 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::option_expect_used,
|
||||
clippy::as_conversions,
|
||||
clippy::result_unwrap_used,
|
||||
// clippy::wildcard_enum_match_arm
|
||||
)]
|
||||
#![allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#![allow(dead_code)]
|
||||
mod drawing;
|
||||
mod graph_entity;
|
||||
mod optimize;
|
||||
pub use optimize::optimize;
|
||||
mod relation;
|
||||
mod util;
|
||||
// mod graph_check;
|
||||
pub use drawing::*;
|
||||
pub mod test_actions;
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
526
solver-rs/lib/action_optimization/src/optimize.rs
Normal file
526
solver-rs/lib/action_optimization/src/optimize.rs
Normal file
@@ -0,0 +1,526 @@
|
||||
use super::{
|
||||
graph_entity::{from_graph, to_graph, ActionGraph, RelationType},
|
||||
util::{get_all_cards, get_all_sources},
|
||||
};
|
||||
|
||||
use actions::{Bunkerize, DragonKill, Goal, Move};
|
||||
use board::PositionNoGoal;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use petgraph::visit::{EdgeRef, IntoNodeReferences};
|
||||
|
||||
pub fn merge_actions(
|
||||
descendant_action: &actions::All,
|
||||
parent_action: &actions::All,
|
||||
) -> Result<actions::All, String> {
|
||||
debug_assert_eq!(
|
||||
get_all_cards(descendant_action),
|
||||
get_all_cards(parent_action)
|
||||
);
|
||||
match descendant_action {
|
||||
actions::All::Bunkerize(action) => {
|
||||
let parent_source = get_all_sources(parent_action.clone());
|
||||
if parent_source.len() != 1 {
|
||||
return Result::Err("Only operates on parents with one source".to_string());
|
||||
}
|
||||
let parent_source = &parent_source[0];
|
||||
if action.to_bunker {
|
||||
match parent_source {
|
||||
PositionNoGoal::Field(parent_field) => {
|
||||
return Result::Ok(actions::All::Bunkerize(Bunkerize {
|
||||
field_position: *parent_field,
|
||||
..action.clone()
|
||||
}));
|
||||
}
|
||||
PositionNoGoal::Bunker { .. } => {
|
||||
return Result::Err("Cannot merge non field move to bunkerize".to_string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match parent_source {
|
||||
PositionNoGoal::Field(parent_field) => {
|
||||
return Result::Ok(actions::All::Move(Move::new(
|
||||
*parent_field,
|
||||
action.field_position,
|
||||
&[action.card.add_hua()],
|
||||
)));
|
||||
}
|
||||
PositionNoGoal::Bunker { .. } => panic!(
|
||||
"How can you have two debunkerize actions after following each other?"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
actions::All::DragonKill(_) => return Result::Err("Not implemented".to_string()),
|
||||
actions::All::Goal(action) => {
|
||||
let parent_source = get_all_sources(parent_action.clone());
|
||||
if parent_source.len() != 1 {
|
||||
return Result::Err("Only operates on parents with one source".to_string());
|
||||
}
|
||||
let parent_source = parent_source.into_iter().next().unwrap();
|
||||
return Result::Ok(actions::All::Goal(Goal {
|
||||
source: parent_source,
|
||||
..action.clone()
|
||||
}));
|
||||
}
|
||||
actions::All::HuaKill(_) => {
|
||||
panic!("How do you have a move parent for a hua kill?");
|
||||
}
|
||||
actions::All::Move(action) => {
|
||||
let parent_source = get_all_sources(parent_action.clone());
|
||||
if parent_source.len() != 1 {
|
||||
return Result::Err("Only operates on parents with one source".to_string());
|
||||
}
|
||||
let parent_source = parent_source.into_iter().next().unwrap();
|
||||
match parent_source {
|
||||
PositionNoGoal::Field(parent_field) => {
|
||||
let mut result_action = action.clone();
|
||||
result_action.source = parent_field;
|
||||
return Result::Ok(actions::All::Move(result_action));
|
||||
}
|
||||
PositionNoGoal::Bunker { slot_index } => {
|
||||
assert!(action.stack_len() == 1);
|
||||
return Result::Ok(actions::All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index: slot_index,
|
||||
card: action.cards()[0].remove_hua(),
|
||||
field_position: action.destination,
|
||||
to_bunker: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parents(
|
||||
graph: &ActionGraph,
|
||||
index: petgraph::stable_graph::NodeIndex,
|
||||
) -> Vec<petgraph::stable_graph::NodeIndex> {
|
||||
let parent = graph
|
||||
.edges_directed(index, petgraph::Direction::Incoming)
|
||||
.filter_map(|x| {
|
||||
if x.weight() == &RelationType::Move {
|
||||
return Option::Some(x.source());
|
||||
} else {
|
||||
return Option::None;
|
||||
}
|
||||
});
|
||||
return parent.collect();
|
||||
}
|
||||
|
||||
fn socket_for(
|
||||
graph: &ActionGraph,
|
||||
index: petgraph::stable_graph::NodeIndex,
|
||||
) -> Vec<petgraph::stable_graph::NodeIndex> {
|
||||
return graph
|
||||
.edges_directed(index, petgraph::Direction::Outgoing)
|
||||
.filter_map(|x| {
|
||||
if x.weight() == &RelationType::Socket {
|
||||
return Option::Some(x.target());
|
||||
} else {
|
||||
return Option::None;
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// # Relations when merging nodes
|
||||
/// - To parent:
|
||||
/// - Clearing -> To merged node, needs to be cleared no matter destination
|
||||
/// - Unblocking -> remove
|
||||
/// - Socketing -> remove
|
||||
/// - From parent:
|
||||
/// - Clearing -> From merged node, clears no matter of destination
|
||||
/// - Unblocking -> From merged node, when to child both nodes can be removed
|
||||
/// - Socketing -> Abort merging, probably needs to be still there
|
||||
/// - To child:
|
||||
/// - Clearing -> Shouldn't happen when there is no traffic between parent
|
||||
/// - Unblocking -> Still required, keep
|
||||
/// - Socketing -> Still required, keep
|
||||
/// - From child:
|
||||
/// - Clearing -> Should cancel the socket to parent, remove
|
||||
/// - Unblocking -> Join with incoming unblocking of parent, otherwise cell was always empty
|
||||
/// - Socketing -> keep, destination stays the same, as such still sockets the target of the relation
|
||||
fn merge_edges(
|
||||
graph: &mut ActionGraph,
|
||||
parent: petgraph::stable_graph::NodeIndex,
|
||||
child: petgraph::stable_graph::NodeIndex,
|
||||
) {
|
||||
let mut addable_edges = Vec::new();
|
||||
let mut removable_edges = Vec::new();
|
||||
let mut unblock_parent = Option::None;
|
||||
let mut socket_parent = Option::None;
|
||||
for parent_dependent in graph.edges_directed(parent, petgraph::Direction::Incoming) {
|
||||
match parent_dependent.weight() {
|
||||
RelationType::Socket => socket_parent = Option::Some(parent_dependent.source()),
|
||||
RelationType::Unblock => unblock_parent = Option::Some(parent_dependent.source()),
|
||||
RelationType::Move | RelationType::Clear => {
|
||||
addable_edges.push((parent_dependent.source(), child, *parent_dependent.weight()));
|
||||
}
|
||||
RelationType::Goal => panic!("Merging actions does not work on goal actions"),
|
||||
}
|
||||
}
|
||||
for parent_dependent in graph.edges_directed(parent, petgraph::Direction::Outgoing) {
|
||||
match parent_dependent.weight() {
|
||||
RelationType::Move => {
|
||||
debug_assert_eq!(parent_dependent.target(), child);
|
||||
}
|
||||
RelationType::Unblock | RelationType::Clear => {
|
||||
if parent_dependent.target() != child {
|
||||
addable_edges.push((
|
||||
child,
|
||||
parent_dependent.target(),
|
||||
*parent_dependent.weight(),
|
||||
));
|
||||
}
|
||||
}
|
||||
RelationType::Socket => {
|
||||
panic!("Cannot merge a parent which provides a socket for an action")
|
||||
}
|
||||
RelationType::Goal => panic!("Merging actions does not work on goal actions"),
|
||||
}
|
||||
}
|
||||
for child_dependent in graph.edges_directed(child, petgraph::Direction::Incoming) {
|
||||
match child_dependent.weight() {
|
||||
RelationType::Move => {
|
||||
if get_all_cards(&graph[child]).len() == 1 {
|
||||
debug_assert_eq!(child_dependent.source(), parent);
|
||||
}
|
||||
}
|
||||
RelationType::Clear => {
|
||||
if get_all_cards(&graph[child]).len() == 1 {
|
||||
panic!("What is being cleared between the parent and the child when no other interaction happened in between?\n{:?} {}\n{:?} {}",
|
||||
parent, graph.node_weight(parent).unwrap(), child, graph.node_weight(child).unwrap());
|
||||
}
|
||||
}
|
||||
RelationType::Unblock | RelationType::Socket | RelationType::Goal => {}
|
||||
}
|
||||
}
|
||||
for child_dependent in graph.edges_directed(child, petgraph::Direction::Outgoing) {
|
||||
match child_dependent.weight() {
|
||||
RelationType::Goal | RelationType::Move | RelationType::Socket => {}
|
||||
RelationType::Clear => removable_edges.push(child_dependent.id()),
|
||||
RelationType::Unblock => {
|
||||
debug_assert!(
|
||||
!(unblock_parent.is_some() && socket_parent.is_some()),
|
||||
"Both unblock {:?} and socket {:?} parent for {:?}",
|
||||
unblock_parent.unwrap(),
|
||||
socket_parent.unwrap(),
|
||||
child
|
||||
);
|
||||
if let Option::Some(parent_unblocker) = unblock_parent {
|
||||
addable_edges.push((
|
||||
parent_unblocker,
|
||||
child_dependent.target(),
|
||||
*child_dependent.weight(),
|
||||
));
|
||||
removable_edges.push(child_dependent.id());
|
||||
}
|
||||
if let Option::Some(parent_socket) = socket_parent {
|
||||
addable_edges.push((
|
||||
parent_socket,
|
||||
child_dependent.target(),
|
||||
RelationType::Socket,
|
||||
));
|
||||
removable_edges.push(child_dependent.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (source, target, weight) in addable_edges {
|
||||
graph.add_edge(source, target, weight);
|
||||
}
|
||||
for edge in removable_edges {
|
||||
graph.remove_edge(edge);
|
||||
}
|
||||
graph.remove_node(parent);
|
||||
}
|
||||
|
||||
pub fn try_merge(
|
||||
graph: &mut ActionGraph,
|
||||
parent: petgraph::stable_graph::NodeIndex,
|
||||
child: petgraph::stable_graph::NodeIndex,
|
||||
) -> bool {
|
||||
if let Result::Ok(new_action) = merge_actions(
|
||||
graph.node_weight(child).unwrap(),
|
||||
graph.node_weight(parent).unwrap(),
|
||||
) {
|
||||
*graph.node_weight_mut(child).unwrap() = new_action;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
merge_edges(graph, parent, child);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Remove an action from the graph which has no impact on the board
|
||||
pub fn delete_null_node(graph: &mut ActionGraph, null_node: petgraph::stable_graph::NodeIndex) {
|
||||
let join_edge = |graph: &mut ActionGraph, reltype: RelationType| {
|
||||
let incoming_edge = graph
|
||||
.edges_directed(null_node, petgraph::Direction::Incoming)
|
||||
.find_map(|x| {
|
||||
if x.weight() == &reltype {
|
||||
return Option::Some(x.source());
|
||||
} else {
|
||||
return Option::None;
|
||||
}
|
||||
});
|
||||
let outgoing_edge = graph
|
||||
.edges_directed(null_node, petgraph::Direction::Outgoing)
|
||||
.find_map(|x| {
|
||||
if x.weight() == &reltype {
|
||||
return Option::Some(x.target());
|
||||
} else {
|
||||
return Option::None;
|
||||
}
|
||||
});
|
||||
if let Option::Some(incoming_edge) = incoming_edge {
|
||||
if let Option::Some(outgoing_edge) = outgoing_edge {
|
||||
graph.add_edge(incoming_edge, outgoing_edge, reltype);
|
||||
}
|
||||
}
|
||||
};
|
||||
join_edge(graph, RelationType::Move);
|
||||
join_edge(graph, RelationType::Unblock);
|
||||
for weird_edge in graph
|
||||
.edges_directed(null_node, petgraph::Direction::Incoming)
|
||||
.chain(graph.edges_directed(null_node, petgraph::Direction::Outgoing))
|
||||
.filter(|x| {
|
||||
return x.weight() != &RelationType::Move && x.weight() != &RelationType::Unblock;
|
||||
})
|
||||
{
|
||||
eprintln!(
|
||||
"Weird edge while deleting null node\n{}\n{:?} {}\n{:?} {}\n{:?} {}",
|
||||
weird_edge.weight(),
|
||||
null_node,
|
||||
graph.node_weight(null_node).unwrap(),
|
||||
weird_edge.source(),
|
||||
graph.node_weight(weird_edge.source()).unwrap(),
|
||||
weird_edge.target(),
|
||||
graph.node_weight(weird_edge.target()).unwrap(),
|
||||
)
|
||||
}
|
||||
graph.remove_node(null_node);
|
||||
}
|
||||
|
||||
fn try_replace_bunker_slot(
|
||||
graph: &mut ActionGraph,
|
||||
index: petgraph::stable_graph::NodeIndex,
|
||||
parent_slot: u8,
|
||||
child_slot: u8,
|
||||
) {
|
||||
let swap_slot = |slot| {
|
||||
if slot == child_slot {
|
||||
return parent_slot;
|
||||
} else if slot == parent_slot {
|
||||
return child_slot;
|
||||
} else {
|
||||
return slot;
|
||||
}
|
||||
};
|
||||
match graph.node_weight_mut(index).unwrap() {
|
||||
actions::All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index, ..
|
||||
}) => {
|
||||
*bunker_slot_index = swap_slot(*bunker_slot_index);
|
||||
}
|
||||
actions::All::DragonKill(DragonKill {
|
||||
source,
|
||||
destination_slot_index,
|
||||
..
|
||||
}) => {
|
||||
let slot_index = source.iter_mut().filter_map(|x| {
|
||||
if let board::PositionNoGoal::Bunker { slot_index } = x {
|
||||
return Option::Some(slot_index);
|
||||
} else {
|
||||
return Option::None;
|
||||
}
|
||||
});
|
||||
for current_slot in slot_index {
|
||||
*current_slot = swap_slot(*current_slot);
|
||||
}
|
||||
*destination_slot_index = swap_slot(*destination_slot_index);
|
||||
}
|
||||
actions::All::Goal(Goal { source, .. }) => {
|
||||
if let PositionNoGoal::Bunker { slot_index } = source {
|
||||
*slot_index = swap_slot(*slot_index);
|
||||
}
|
||||
}
|
||||
actions::All::HuaKill(_) | actions::All::Move(_) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flip_bunker_slots(
|
||||
graph: &mut ActionGraph,
|
||||
index: petgraph::stable_graph::NodeIndex,
|
||||
parent_slot: u8,
|
||||
child_slot: u8,
|
||||
) {
|
||||
let unblock_move_graph = petgraph::visit::EdgeFiltered::from_fn(
|
||||
&*graph,
|
||||
&|x: petgraph::stable_graph::EdgeReference<RelationType>| match x.weight() {
|
||||
RelationType::Move | RelationType::Unblock => return true,
|
||||
RelationType::Clear | RelationType::Socket | RelationType::Goal => return false,
|
||||
},
|
||||
);
|
||||
let mut visitor = petgraph::visit::Dfs::new(&unblock_move_graph, index);
|
||||
|
||||
while let Option::Some(index) = visitor.next(&*graph) {
|
||||
try_replace_bunker_slot(graph, index, parent_slot, child_slot);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_bunker_loop(
|
||||
graph: &ActionGraph,
|
||||
parent: petgraph::stable_graph::NodeIndex,
|
||||
child: petgraph::stable_graph::NodeIndex,
|
||||
) -> bool {
|
||||
if let actions::All::Bunkerize(Bunkerize {
|
||||
to_bunker: parent_to_bunker,
|
||||
..
|
||||
}) = graph.node_weight(parent).unwrap()
|
||||
{
|
||||
if let actions::All::Bunkerize(Bunkerize { to_bunker, .. }) =
|
||||
graph.node_weight(child).unwrap()
|
||||
{
|
||||
if !parent_to_bunker && *to_bunker {
|
||||
// if *parent_slot == *bunker_slot_index {
|
||||
// return Option::Some((*parent_slot, *bunker_slot_index));
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn is_field_loop(
|
||||
graph: &ActionGraph,
|
||||
parent: petgraph::stable_graph::NodeIndex,
|
||||
child: petgraph::stable_graph::NodeIndex,
|
||||
) -> bool {
|
||||
if let actions::All::Move(move_action) = graph.node_weight(parent).unwrap() {
|
||||
if let actions::All::Move(child_move_action) = graph.node_weight(child).unwrap() {
|
||||
debug_assert_eq!(move_action.cards(), child_move_action.cards());
|
||||
debug_assert_eq!(move_action.destination, child_move_action.source);
|
||||
debug_assert_eq!(move_action.stack_len(), 1);
|
||||
return move_action.source == child_move_action.destination;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn merge_step(mut graph: ActionGraph) -> ActionGraph {
|
||||
let mut used_nodes = HashSet::new();
|
||||
let mut mergeable = Vec::new();
|
||||
let mut loop_deletion = Vec::new();
|
||||
let mut bunker_loop_deletion = Vec::new();
|
||||
for (index, _action) in graph.node_references() {
|
||||
if used_nodes.contains(&index) {
|
||||
continue;
|
||||
}
|
||||
let parents = get_parents(&graph, index);
|
||||
if parents.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
let parent = parents.into_iter().next().unwrap();
|
||||
if used_nodes.contains(&parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if get_all_cards(graph.node_weight(parent).unwrap()).len() > 1 {
|
||||
continue;
|
||||
}
|
||||
if get_all_cards(graph.node_weight(index).unwrap()).len() > 1 {
|
||||
continue;
|
||||
}
|
||||
if socket_for(&graph, parent)
|
||||
.into_iter()
|
||||
.any(|x| return x != index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let filtered_graph = petgraph::visit::EdgeFiltered::from_fn(&graph, |x| {
|
||||
return !(x.source() == parent && x.target() == index);
|
||||
});
|
||||
if petgraph::algo::has_path_connecting(&filtered_graph, parent, index, Option::None) {
|
||||
continue;
|
||||
}
|
||||
if is_bunker_loop(&graph, parent, index) {
|
||||
bunker_loop_deletion.push((parent, index));
|
||||
} else if is_field_loop(&graph, parent, index) {
|
||||
loop_deletion.push((parent, index));
|
||||
} else {
|
||||
mergeable.push((parent, index));
|
||||
}
|
||||
used_nodes.insert(parent);
|
||||
used_nodes.insert(index);
|
||||
}
|
||||
for (parent, child) in mergeable {
|
||||
try_merge(&mut graph, parent, child);
|
||||
}
|
||||
for (parent, child) in loop_deletion {
|
||||
merge_edges(&mut graph, parent, child);
|
||||
delete_null_node(&mut graph, child);
|
||||
}
|
||||
for (parent, child) in bunker_loop_deletion {
|
||||
let parent_slot = if let actions::All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index,
|
||||
to_bunker,
|
||||
..
|
||||
}) = &graph[parent]
|
||||
{
|
||||
assert!(!*to_bunker);
|
||||
*bunker_slot_index
|
||||
} else {
|
||||
panic!("Should be bunkerize action")
|
||||
};
|
||||
let child_slot = if let actions::All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index,
|
||||
to_bunker,
|
||||
..
|
||||
}) = &graph[child]
|
||||
{
|
||||
assert!(*to_bunker);
|
||||
*bunker_slot_index
|
||||
} else {
|
||||
panic!("Should be bunkerize action")
|
||||
};
|
||||
flip_bunker_slots(&mut graph, parent, parent_slot, child_slot);
|
||||
merge_edges(&mut graph, parent, child);
|
||||
delete_null_node(&mut graph, child);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
fn fix_dragonkill_destination(actions: &[actions::All]) -> Vec<actions::All> {
|
||||
let graph = to_graph(actions);
|
||||
let result = graph
|
||||
.node_indices()
|
||||
.map(|node| return graph.node_weight(node).unwrap().clone())
|
||||
.collect();
|
||||
return result;
|
||||
}
|
||||
|
||||
fn fix_goal_ordering(actions: &[actions::All]) -> Vec<actions::All> {
|
||||
return actions.to_vec();
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn optimize(actions: &[actions::All]) -> Vec<actions::All> {
|
||||
let mut graph = to_graph(actions);
|
||||
let mut last_length = graph.node_count();
|
||||
loop {
|
||||
graph = merge_step(graph);
|
||||
if graph.node_count() == last_length {
|
||||
break;
|
||||
}
|
||||
last_length = graph.node_count();
|
||||
}
|
||||
|
||||
let optimized_sequence = from_graph(&graph);
|
||||
return fix_goal_ordering(&fix_dragonkill_destination(&optimized_sequence));
|
||||
}
|
||||
149
solver-rs/lib/action_optimization/src/relation.rs
Normal file
149
solver-rs/lib/action_optimization/src/relation.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use super::{
|
||||
graph_entity::RelationType,
|
||||
util::{
|
||||
get_all_bottom_sources, get_all_destinations, get_all_sources, get_all_top_sources,
|
||||
get_destination, get_top_destination, search_parent_tree, top_card,
|
||||
},
|
||||
};
|
||||
use actions::{Goal, Move};
|
||||
use board::PositionNoGoal;
|
||||
pub fn get_move_parents(actions: &[actions::All], current_action: usize) -> Vec<usize> {
|
||||
let result = get_all_sources(actions[current_action].clone())
|
||||
.into_iter()
|
||||
.filter_map(|cur_source_pos| {
|
||||
let is_move_parent = |other_action: &actions::All| {
|
||||
let destinations =
|
||||
get_all_destinations(other_action.clone())
|
||||
.into_iter()
|
||||
.any(|cur_dest_pos| {
|
||||
return cur_dest_pos == cur_source_pos;
|
||||
});
|
||||
return destinations;
|
||||
};
|
||||
|
||||
let source_action = search_parent_tree(actions, current_action, is_move_parent);
|
||||
return source_action.map(|(index, _)| return index);
|
||||
})
|
||||
.collect();
|
||||
return result;
|
||||
}
|
||||
fn get_unblocking_parent(actions: &[actions::All], current_action: usize) -> Option<usize> {
|
||||
let destination = get_destination(&actions[current_action])?;
|
||||
let is_unblocking = |other_action: &actions::All| {
|
||||
return get_all_sources(other_action.clone())
|
||||
.into_iter()
|
||||
.any(|source| return source == destination);
|
||||
};
|
||||
return search_parent_tree(actions, current_action, is_unblocking)
|
||||
.filter(|&(_, found_action)| {
|
||||
if let actions::All::Move(Move { ref source, .. }) = found_action {
|
||||
return board::Position::Field(*source) == destination;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(|(index, _)| return index);
|
||||
}
|
||||
|
||||
fn get_socket_parent(actions: &[actions::All], current_action: usize) -> Option<usize> {
|
||||
let top_action = get_destination(&actions[current_action]);
|
||||
|
||||
if let Option::Some(board::Position::Field(top_action)) = top_action {
|
||||
let is_socket = |action: &actions::All| {
|
||||
let socket_destination = get_top_destination(action.clone());
|
||||
if let Option::Some(board::Position::Field(destination)) = socket_destination {
|
||||
return top_card(&destination) == top_action;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
let added_socket =
|
||||
search_parent_tree(actions, current_action, is_socket).map(|(index, _)| {
|
||||
return index;
|
||||
});
|
||||
let unblocking_parent = get_unblocking_parent(actions, current_action);
|
||||
if added_socket < unblocking_parent {
|
||||
return Option::None;
|
||||
} else {
|
||||
return added_socket;
|
||||
}
|
||||
}
|
||||
return Option::None;
|
||||
}
|
||||
|
||||
pub fn get_destination_parent(
|
||||
actions: &[actions::All],
|
||||
current_action: usize,
|
||||
) -> Option<(usize, RelationType)> {
|
||||
let socket_parent = get_socket_parent(actions, current_action);
|
||||
let unblock_parent = get_unblocking_parent(actions, current_action);
|
||||
if socket_parent.is_none() && unblock_parent.is_none() {
|
||||
return Option::None;
|
||||
} else if socket_parent > unblock_parent {
|
||||
return Option::Some((socket_parent.unwrap(), RelationType::Socket));
|
||||
} else {
|
||||
return Option::Some((unblock_parent.unwrap(), RelationType::Unblock));
|
||||
}
|
||||
}
|
||||
|
||||
/// Actions which moved cards on top of other cards away
|
||||
pub fn get_clear_parents(actions: &[actions::All], current_action: usize) -> Vec<usize> {
|
||||
let filter_fields = |x: PositionNoGoal| {
|
||||
if let PositionNoGoal::Field(f) = x {
|
||||
return Some(f);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let source_positions = get_all_top_sources(&actions[current_action]);
|
||||
|
||||
let parents: Vec<usize> = source_positions
|
||||
.into_iter()
|
||||
.filter_map(|current_source_pos| {
|
||||
let current_source_pos = filter_fields(current_source_pos)?;
|
||||
let latest_moves = get_move_parents(actions, current_action);
|
||||
let latest_move = if let actions::All::DragonKill(_) = actions[current_action] {
|
||||
latest_moves
|
||||
.into_iter()
|
||||
.find(|index| {
|
||||
return get_destination(&actions[*index])
|
||||
== Option::Some(board::Position::Field(current_source_pos));
|
||||
})
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
latest_moves.into_iter().max().unwrap_or(0)
|
||||
};
|
||||
let is_clearing = move |other_action: &actions::All| {
|
||||
let sources = get_all_bottom_sources(other_action);
|
||||
let clear_parent = sources
|
||||
.into_iter()
|
||||
.filter_map(filter_fields)
|
||||
.any(|cur_dest_pos| return top_card(¤t_source_pos) == cur_dest_pos);
|
||||
|
||||
return clear_parent;
|
||||
};
|
||||
return search_parent_tree(actions, current_action, is_clearing)
|
||||
.map(|(index, _)| return index)
|
||||
.filter(|index| return *index >= latest_move);
|
||||
})
|
||||
.collect();
|
||||
return parents;
|
||||
}
|
||||
|
||||
pub fn get_goal_parent(actions: &[actions::All], current_action: usize) -> Option<usize> {
|
||||
if let actions::All::Goal(Goal { card, .. }) = &actions[current_action] {
|
||||
let is_successive_goal = move |other_action: &actions::All| {
|
||||
if let actions::All::Goal(Goal {
|
||||
card: other_card, ..
|
||||
}) = other_action
|
||||
{
|
||||
return other_card.value + 1 == card.value && other_card.suit == card.suit;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if card.value > 1 {
|
||||
let parent_goal = search_parent_tree(actions, current_action, is_successive_goal)
|
||||
.map(|(index, _)| return index);
|
||||
return parent_goal;
|
||||
}
|
||||
}
|
||||
return Option::None;
|
||||
}
|
||||
20
solver-rs/lib/action_optimization/src/test_actions.rs
Normal file
20
solver-rs/lib/action_optimization/src/test_actions.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// This is incredibly shit, as other crates call this macro with _their_ CARGO_MANIFEST_DIR. Ideally we would move
|
||||
// the boards into the board crate, and use the path of the board crate. But it seems to be really hard to get this done with
|
||||
// macros, and const variables can't be used by macros, so we're using this hack for now.
|
||||
#[macro_export]
|
||||
macro_rules! TEST_ACTION_ROOT {
|
||||
() => {
|
||||
concat!(env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../aux/actions/")
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! load_test_action {
|
||||
( $relpath:expr ) => {
|
||||
{
|
||||
return serde_json::from_str::<Vec<actions::All>>(include_str!(concat!($crate::TEST_ACTION_ROOT!(),
|
||||
$relpath)));
|
||||
}
|
||||
};
|
||||
}
|
||||
66
solver-rs/lib/action_optimization/src/tests.rs
Normal file
66
solver-rs/lib/action_optimization/src/tests.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::{draw_graph, graph_entity::to_graph};
|
||||
use std::str::FromStr;
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn optimize_bunker_loop() {
|
||||
use actions::{All, Bunkerize, Goal};
|
||||
use board::FieldPosition;
|
||||
let numbercard = board::NumberCard {
|
||||
suit: board::NumberCardColor::Red,
|
||||
value: 1,
|
||||
};
|
||||
let zhong_card = board::CardType::Number(numbercard.clone());
|
||||
let actions = vec![
|
||||
All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index: 0,
|
||||
card: zhong_card.remove_hua(),
|
||||
to_bunker: false,
|
||||
field_position: FieldPosition::new(2, 0),
|
||||
}),
|
||||
All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index: 2,
|
||||
card: zhong_card.remove_hua(),
|
||||
to_bunker: true,
|
||||
field_position: FieldPosition::new(2, 0),
|
||||
}),
|
||||
All::Goal(Goal {
|
||||
card: numbercard,
|
||||
goal_slot_index: 0,
|
||||
source: board::PositionNoGoal::Bunker { slot_index: 2 },
|
||||
}),
|
||||
];
|
||||
let graph = to_graph(&actions);
|
||||
draw_graph(&graph, std::path::Path::new("unopt_bunker.svg")).unwrap();
|
||||
let graph = crate::optimize::merge_step(graph);
|
||||
draw_graph(&graph, std::path::Path::new("opt_bunker.svg")).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn all_boards_correct() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for i in 1..19 {
|
||||
let action_string =
|
||||
std::fs::read_to_string(std::format!("{}/{:02}.json", crate::TEST_ACTION_ROOT!(), i))?;
|
||||
|
||||
let actions: Vec<actions::All> = serde_json::from_str(&action_string)?;
|
||||
|
||||
let board_string = std::fs::read_to_string(std::format!(
|
||||
"{}/normal/{:02}.json",
|
||||
board::TEST_BOARD_ROOT!(),
|
||||
i
|
||||
))?;
|
||||
let src_board = board::Board::from_str(&board_string)?;
|
||||
let mut board = src_board.clone();
|
||||
for action in actions.iter() {
|
||||
action.apply(&mut board);
|
||||
}
|
||||
assert!(board.solved());
|
||||
let actions = crate::optimize(&actions);
|
||||
let mut board = src_board;
|
||||
for (index, action) in actions.into_iter().enumerate() {
|
||||
println!("{}", index);
|
||||
action.apply(&mut board);
|
||||
}
|
||||
assert!(board.solved());
|
||||
}
|
||||
return Result::Ok(());
|
||||
}
|
||||
173
solver-rs/lib/action_optimization/src/util.rs
Normal file
173
solver-rs/lib/action_optimization/src/util.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use actions::{Bunkerize, DragonKill, Goal, HuaKill, Move};
|
||||
use board::{CardType, FieldPosition};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
fn node_name(index: usize) -> String {
|
||||
return format!("action_{:04}", index);
|
||||
}
|
||||
|
||||
/// Position on top of this position (increments `position.row_index` by one)
|
||||
pub fn top_card(position: &FieldPosition) -> FieldPosition {
|
||||
return FieldPosition::new(position.column(), position.row() + 1);
|
||||
}
|
||||
|
||||
pub fn column_range(position: &FieldPosition, count: usize) -> Vec<FieldPosition> {
|
||||
return (0..count)
|
||||
.map(|i| {
|
||||
return FieldPosition::new(
|
||||
position.column(),
|
||||
position.row() + u8::try_from(i).unwrap(),
|
||||
);
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn get_all_sources(action: actions::All) -> Vec<board::PositionNoGoal> {
|
||||
match action {
|
||||
actions::All::Bunkerize(Bunkerize {
|
||||
bunker_slot_index,
|
||||
to_bunker,
|
||||
field_position,
|
||||
..
|
||||
}) => {
|
||||
if to_bunker {
|
||||
return vec![board::PositionNoGoal::Field(field_position)];
|
||||
} else {
|
||||
return vec![board::PositionNoGoal::Bunker {
|
||||
slot_index: bunker_slot_index,
|
||||
}];
|
||||
}
|
||||
}
|
||||
actions::All::DragonKill(DragonKill { source, .. }) => {
|
||||
return source.to_vec();
|
||||
}
|
||||
actions::All::Goal(Goal { source, .. }) => {
|
||||
return vec![source];
|
||||
}
|
||||
actions::All::HuaKill(HuaKill { field_position }) => {
|
||||
return vec![board::PositionNoGoal::Field(field_position)]
|
||||
}
|
||||
actions::All::Move(move_action) => {
|
||||
return column_range(&move_action.source, usize::from(move_action.stack_len()))
|
||||
.into_iter()
|
||||
.map(board::PositionNoGoal::Field)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_top_sources(action: &actions::All) -> Vec<board::PositionNoGoal> {
|
||||
if let actions::All::Move(move_action) = &action {
|
||||
return vec![board::PositionNoGoal::Field(FieldPosition::new(
|
||||
move_action.source.column(),
|
||||
move_action.source.row() + move_action.stack_len() - 1,
|
||||
))];
|
||||
} else {
|
||||
return get_all_sources(action.clone());
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_all_bottom_sources(action: &actions::All) -> Vec<board::PositionNoGoal> {
|
||||
if let actions::All::Move(Move { source, .. }) = &action {
|
||||
return vec![board::PositionNoGoal::Field(*source)];
|
||||
} else {
|
||||
return get_all_sources(action.clone());
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_all_cards(action: &actions::All) -> Vec<board::CardType> {
|
||||
match action {
|
||||
actions::All::Bunkerize(Bunkerize { card, .. }) => return vec![card.add_hua()], /* Does this actually work? */
|
||||
actions::All::DragonKill(DragonKill { card, .. }) => {
|
||||
return vec![
|
||||
CardType::Special(card.clone()),
|
||||
CardType::Special(card.clone()),
|
||||
CardType::Special(card.clone()),
|
||||
CardType::Special(card.clone()),
|
||||
]
|
||||
}
|
||||
actions::All::Goal(Goal { card, .. }) => return vec![CardType::Number(card.clone())],
|
||||
actions::All::HuaKill(_) => return vec![CardType::Hua],
|
||||
actions::All::Move(move_action) => return move_action.cards(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_destination(action: &actions::All) -> Option<board::Position> {
|
||||
match action {
|
||||
actions::All::Bunkerize(Bunkerize {
|
||||
field_position,
|
||||
to_bunker,
|
||||
bunker_slot_index,
|
||||
..
|
||||
}) => {
|
||||
if *to_bunker {
|
||||
return Option::Some(board::Position::Bunker {
|
||||
slot_index: *bunker_slot_index,
|
||||
});
|
||||
} else {
|
||||
return Option::Some(board::Position::Field(*field_position));
|
||||
}
|
||||
}
|
||||
actions::All::DragonKill(DragonKill {
|
||||
destination_slot_index,
|
||||
..
|
||||
}) => {
|
||||
return Option::Some(board::Position::Bunker {
|
||||
slot_index: *destination_slot_index,
|
||||
});
|
||||
}
|
||||
actions::All::Goal(Goal {
|
||||
goal_slot_index, ..
|
||||
}) => {
|
||||
return Option::Some(board::Position::Goal {
|
||||
slot_index: *goal_slot_index,
|
||||
});
|
||||
}
|
||||
actions::All::HuaKill(_) => return Option::None,
|
||||
actions::All::Move(Move { destination, .. }) => {
|
||||
return Option::Some(board::Position::Field(*destination));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the destination of a move, or the topmost card in its destination when moving multiple cards
|
||||
pub fn get_top_destination(action: actions::All) -> Option<board::Position> {
|
||||
if let actions::All::Move(move_action) = action {
|
||||
return Option::Some(board::Position::Field(FieldPosition::new(
|
||||
move_action.destination.column(),
|
||||
move_action.destination.row() + move_action.stack_len() - 1,
|
||||
)));
|
||||
} else {
|
||||
return get_destination(&action);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_all_destinations(action: actions::All) -> Vec<board::Position> {
|
||||
if let actions::All::Move(move_action) = action {
|
||||
return column_range(
|
||||
&move_action.destination,
|
||||
usize::from(move_action.stack_len()),
|
||||
)
|
||||
.into_iter()
|
||||
.map(board::Position::Field)
|
||||
.collect();
|
||||
} else {
|
||||
return get_destination(&action).into_iter().collect();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn search_parent_tree<F>(
|
||||
actions: &[actions::All],
|
||||
current_action: usize,
|
||||
predicate: F,
|
||||
) -> Option<(usize, &actions::All)>
|
||||
where
|
||||
F: Fn(&actions::All) -> bool,
|
||||
{
|
||||
return actions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take(current_action)
|
||||
.rev()
|
||||
.find(|&(_, action)| return predicate(action));
|
||||
}
|
||||
Reference in New Issue
Block a user