use crate::{
patterns::{is_3x3x3_solved, is_slot_solved, SlotMask},
triggers::{SlotTriggerInfo, TriggerInfo},
};
use cubing_core::experimental_twizzle_link::{
experimental_twizzle_link, ExperimentalTwizzleLinkParameters,
};
use rand::seq::SliceRandom;
use rand::thread_rng;
use cubing::{
alg::{Alg, AlgBuilder, AlgNode, Pause},
kpuzzle::KPattern,
};
struct SearchStatus {
num_solutions: usize,
depth_limit: usize,
}
struct SearchFrame {
pattern: KPattern,
solved_slots: SlotMask, total_depth: usize,
slot_depth: usize,
}
struct SearchFrameRecursionInfo<'a> {
auf: &'a TriggerInfo,
trigger: &'a TriggerInfo,
solves_slot: bool,
parent: Option<&'a SearchFrameRecursionInfo<'a>>,
}
#[derive(Clone)]
pub struct Search {
pub scramble: Alg,
pub triggers_by_slot: Vec<SlotTriggerInfo>,
pub auf_triggers: Vec<TriggerInfo>,
pub debug: bool,
pub randomize: bool,
pub prefer_immediate_slots: bool,
pub start_depth_limit: usize,
pub max_depth_limit: usize,
pub depth_limit_per_slot: usize,
pub max_num_solutions: usize,
}
impl Search {
pub fn search(&self, pattern: &KPattern) {
for depth_limit in self.start_depth_limit..(self.max_depth_limit + 1) {
println!("Search depth: {}", depth_limit);
let search_status = &mut SearchStatus {
depth_limit,
num_solutions: 0,
};
let search_frame = &SearchFrame {
pattern: pattern.clone(),
solved_slots: SlotMask::from_pattern(pattern),
total_depth: 0,
slot_depth: 0,
};
self.search_recursive(search_status, search_frame, None)
}
}
fn search_recursive(
&self,
search_status: &mut SearchStatus,
search_frame: &SearchFrame,
recursion_info: Option<&SearchFrameRecursionInfo>,
) {
if self.debug {
};
if search_frame.solved_slots.is_f2l_solved() {
for auf in &self.auf_triggers {
let with_auf = search_frame
.pattern
.apply_transformation(&auf.transformation);
if is_3x3x3_solved(&with_auf) {
let (short_solution, long_solution) =
self.build_solutions(recursion_info, &auf.short_alg);
println!();
println!("Full Solution!");
println!("Short: {}", short_solution);
println!("Long: {}", long_solution);
println!(
"{}",
experimental_twizzle_link(ExperimentalTwizzleLinkParameters {
setup: Some(&self.scramble),
alg: Some(&long_solution),
..Default::default()
})
);
search_status.num_solutions += 1;
if search_status.num_solutions == self.max_num_solutions {
return; }
}
}
return; }
if search_frame.total_depth == search_status.depth_limit
|| search_frame.slot_depth == self.depth_limit_per_slot
{
return;
}
let mut next_frames_preferred = Vec::<(SearchFrame, SearchFrameRecursionInfo)>::new();
let mut next_frames_non_preferred = Vec::<(SearchFrame, SearchFrameRecursionInfo)>::new();
for slot_trigger_info in &self.triggers_by_slot {
if search_frame
.solved_slots
.is_slot_solved(&slot_trigger_info.f2l_slot)
{
continue;
}
for auf in &self.auf_triggers {
let next_pattern = search_frame
.pattern
.apply_transformation(&auf.transformation);
for trigger in &slot_trigger_info.triggers {
let next_pattern = next_pattern.apply_transformation(&trigger.transformation);
let (next_searches, remaining_depth_for_slot, solves_slot, solved_slots) =
if is_slot_solved(&next_pattern, &slot_trigger_info.f2l_slot) {
(
if self.prefer_immediate_slots {
&mut next_frames_preferred
} else {
&mut next_frames_non_preferred
},
0,
true,
search_frame
.solved_slots
.set(&slot_trigger_info.f2l_slot, true),
)
} else {
(
&mut next_frames_non_preferred,
search_frame.slot_depth + 1,
false,
search_frame.solved_slots.clone(),
)
};
next_searches.push((
SearchFrame {
pattern: next_pattern,
solved_slots,
total_depth: search_frame.total_depth + 1,
slot_depth: remaining_depth_for_slot,
},
SearchFrameRecursionInfo {
auf,
trigger,
solves_slot,
parent: recursion_info,
},
))
}
}
}
if self.randomize {
next_frames_preferred.shuffle(&mut thread_rng());
next_frames_non_preferred.shuffle(&mut thread_rng());
}
for next_frames in [next_frames_preferred, next_frames_non_preferred] {
for next_frame in next_frames {
let (next_frame, recursion_info) = next_frame;
if self.debug {
for _ in 0..next_frame.total_depth {
print!(" ")
}
println!(
"↳ {} {}",
recursion_info.auf.short_alg, recursion_info.trigger.short_alg
);
}
self.search_recursive(search_status, &next_frame, Some(&recursion_info));
}
}
}
fn build_solutions(
&self,
recursion_info: Option<&SearchFrameRecursionInfo>,
suffix: &Alg,
) -> (Alg, Alg) {
let mut short_alg_builder = AlgBuilder::default();
let mut long_alg_builder = AlgBuilder::default();
self.build_solutions_recursive(
&mut short_alg_builder,
&mut long_alg_builder,
recursion_info,
);
short_alg_builder.push(suffix);
long_alg_builder.push(suffix);
(short_alg_builder.to_alg(), long_alg_builder.to_alg())
}
#[allow(clippy::only_used_in_recursion)] fn build_solutions_recursive(
&self,
short_alg_builder: &mut AlgBuilder,
long_alg_builder: &mut AlgBuilder,
recursion_info: Option<&SearchFrameRecursionInfo>,
) {
if let Some(child_info) = recursion_info {
self.build_solutions_recursive(short_alg_builder, long_alg_builder, child_info.parent);
short_alg_builder.push(&child_info.auf.short_alg);
short_alg_builder.push(&child_info.trigger.short_alg);
long_alg_builder.push(&child_info.auf.long_alg);
long_alg_builder.push(&child_info.trigger.long_alg);
if child_info.solves_slot {
let pause: AlgNode = Pause {}.into();
short_alg_builder.push(&pause);
long_alg_builder.push(&pause);
}
}
}
}