use super::*;
#[derive(Default)]
pub struct MoveDownAndSwap;
impl Tactic for MoveDownAndSwap {
fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult {
let trace_target = builder.trace_target().clone().with_topic("solver:move-down-and-swap");
if builder.requires_copies() || builder.arity() < 2 {
log::trace!(
target: &trace_target,
"cannot apply tactic when there are required copies ({}) or fewer than 2 operands \
({})",
builder.requires_copies(),
builder.arity()
);
return Err(TacticError::PreconditionFailed);
}
if builder.is_expected(0) {
log::trace!(target: &trace_target, "abandoning tactic: operand at index 0 is already in position");
return Err(TacticError::NotApplicable);
}
let actual0 = builder.unwrap_current(0);
if let Some(target_pos) = builder.get_expected_position(&actual0) {
log::trace!(
target: &trace_target,
"{actual0:?} expects to be at index {target_pos}, but is on top of the stack"
);
log::trace!(
target: &trace_target,
"looking for operands after index {target_pos} which need to come before \
{actual0:?} on the stack"
);
let target_offset = builder
.stack()
.iter()
.rev()
.skip(1 + target_pos as usize)
.copied()
.enumerate()
.fold(0, |acc, (offset, operand)| {
builder
.get_expected_position(&operand)
.and_then(|operand_expected_at| {
if target_pos >= operand_expected_at {
Some(offset + 1)
} else {
None
}
})
.unwrap_or(acc)
}) as u8;
log::trace!(
target: &trace_target,
"moving {actual0:?} to {}, shifting {:?} to the top of stack",
target_pos + target_offset,
builder.unwrap_current(1)
);
builder.movdn(target_pos + target_offset);
} else {
let expected0 = builder.unwrap_expected(0);
let expected0_at = builder.unwrap_current_position(&expected0);
log::trace!(
target: &trace_target,
"{actual0:?} is not an expected operand, but is occupying index {expected0_at}, \
where we expect {expected0:?}, evicting.."
);
builder.evict();
}
if builder.is_expected(0) {
log::trace!(
target: &trace_target,
"item on top of the stack is now in position, so we cannot proceed further, \
returning possible solution"
);
return Ok(());
}
let actual0 = builder.unwrap_current(0);
if let Some(target_pos) = builder.get_expected_position(&actual0) {
let target_expected_pos =
builder.get_expected_position(&builder.unwrap_current(target_pos));
match target_expected_pos {
Some(0) => {
log::trace!(
target: &trace_target,
"{actual0:?} is expected at {target_pos}, the occupant of which is \
expected on top of the stack, swapping.."
);
builder.swap(target_pos);
}
Some(pos) if pos == target_pos - 1 => {
log::trace!(
target: &trace_target,
"moving {actual0:?} to {target_pos}, the occupant of which is expected at \
{pos}"
);
builder.movdn(target_pos);
}
Some(_) | None => {
log::trace!(target: &trace_target, "unable to apply tactic, operands do not match expected pattern");
return Err(TacticError::NotApplicable);
}
}
} else {
let expected0 = builder.unwrap_expected(0);
let expected0_at = builder.unwrap_expected_position(&expected0);
log::trace!(
target: &trace_target,
"{actual0:?} is not an expected operand, but is occupying index {expected0_at}, \
where we expect {expected0:?}, evicting.."
);
builder.evict();
}
Ok(())
}
}