Skip to content

Commit

Permalink
Implement custom actions for the macro solver
Browse files Browse the repository at this point in the history
  • Loading branch information
KonaeAkira committed Jan 1, 2025
1 parent e22201d commit 6aa0382
Show file tree
Hide file tree
Showing 14 changed files with 371 additions and 251 deletions.
2 changes: 1 addition & 1 deletion raphael-cli/src/commands/solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ pub fn execute(args: &SolveArgs) {
let state_quality = final_state.quality;
let final_quality = state_quality.saturating_add(initial_quality);
let steps = actions.len();
let duration: i16 = actions.iter().map(|action| action.time_cost()).sum();
let duration: u8 = actions.iter().map(|action| action.time_cost()).sum();

if args.output_variables.is_empty() {
println!("Item ID: {}", recipe.item_id);
Expand Down
2 changes: 1 addition & 1 deletion simulator/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ impl Combo {
}

impl Action {
pub const fn time_cost(self) -> i16 {
pub const fn time_cost(self) -> u8 {
match self {
Action::BasicSynthesis => 3,
Action::BasicTouch => 3,
Expand Down
2 changes: 1 addition & 1 deletion solvers/examples/macro_solver_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn main() {
.unwrap()
.quality;
let steps = actions.len();
let duration: i16 = actions.iter().map(|action| action.time_cost()).sum();
let duration: u8 = actions.iter().map(|action| action.time_cost()).sum();

log::info!(
"Solution - quality: {}, steps: {}, duration: {}",
Expand Down
213 changes: 170 additions & 43 deletions solvers/src/actions.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,170 @@
use simulator::{action_mask, Action, ActionMask};

pub const PROGRESS_ACTIONS: ActionMask = action_mask!(
Action::BasicSynthesis,
Action::Veneration,
Action::MuscleMemory,
Action::CarefulSynthesis,
Action::Groundwork,
Action::DelicateSynthesis,
Action::IntensiveSynthesis,
Action::HeartAndSoul,
Action::PrudentSynthesis
);

pub const QUALITY_ACTIONS: ActionMask = action_mask!(
Action::BasicTouch,
Action::Observe,
Action::StandardTouch,
Action::GreatStrides,
Action::Innovation,
Action::ByregotsBlessing,
Action::PreciseTouch,
Action::PrudentTouch,
Action::Reflect,
Action::PreparatoryTouch,
Action::DelicateSynthesis,
Action::AdvancedTouch,
Action::TrainedFinesse,
Action::RefinedTouch,
Action::TrainedEye,
Action::HeartAndSoul,
Action::QuickInnovation
);

pub const DURABILITY_ACTIONS: ActionMask = action_mask!(
Action::MasterMend,
Action::TricksOfTheTrade,
Action::WasteNot,
Action::WasteNot2,
Action::Manipulation,
Action::ImmaculateMend,
Action::TrainedPerfection
);
use simulator::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverAction {
TricksOfTheTradeCombo, // Heart and Soul + Tricks of the Trade
IntensiveSynthesisCombo, // Heart and Soul + Intensive Synthesis
PreciseTouchCombo, // Heart and Soul + Precise Touch
Single(Action),
}

impl SolverAction {
#[inline(always)]
pub fn actions(self) -> &'static [Action] {
match self {
SolverAction::TricksOfTheTradeCombo => {
&[Action::HeartAndSoul, Action::TricksOfTheTrade]
}
SolverAction::IntensiveSynthesisCombo => {
&[Action::HeartAndSoul, Action::IntensiveSynthesis]
}
SolverAction::PreciseTouchCombo => &[Action::HeartAndSoul, Action::PreciseTouch],
SolverAction::Single(action) => match action {
Action::BasicSynthesis => &[Action::BasicSynthesis],
Action::BasicTouch => &[Action::BasicTouch],
Action::MasterMend => &[Action::MasterMend],
Action::Observe => &[Action::Observe],
Action::TricksOfTheTrade => &[Action::TricksOfTheTrade],
Action::WasteNot => &[Action::WasteNot],
Action::Veneration => &[Action::Veneration],
Action::StandardTouch => &[Action::StandardTouch],
Action::GreatStrides => &[Action::GreatStrides],
Action::Innovation => &[Action::Innovation],
Action::WasteNot2 => &[Action::WasteNot2],
Action::ByregotsBlessing => &[Action::ByregotsBlessing],
Action::PreciseTouch => &[Action::PreciseTouch],
Action::MuscleMemory => &[Action::MuscleMemory],
Action::CarefulSynthesis => &[Action::CarefulSynthesis],
Action::Manipulation => &[Action::Manipulation],
Action::PrudentTouch => &[Action::PrudentTouch],
Action::AdvancedTouch => &[Action::AdvancedTouch],
Action::Reflect => &[Action::Reflect],
Action::PreparatoryTouch => &[Action::PreparatoryTouch],
Action::Groundwork => &[Action::Groundwork],
Action::DelicateSynthesis => &[Action::DelicateSynthesis],
Action::IntensiveSynthesis => &[Action::IntensiveSynthesis],
Action::TrainedEye => &[Action::TrainedEye],
Action::HeartAndSoul => &[Action::HeartAndSoul],
Action::PrudentSynthesis => &[Action::PrudentSynthesis],
Action::TrainedFinesse => &[Action::TrainedFinesse],
Action::RefinedTouch => &[Action::RefinedTouch],
Action::QuickInnovation => &[Action::QuickInnovation],
Action::ImmaculateMend => &[Action::ImmaculateMend],
Action::TrainedPerfection => &[Action::TrainedPerfection],
},
}
}

#[inline(always)]
pub fn steps(self) -> u8 {
self.actions().len() as u8
}

#[inline(always)]
pub fn duration(self) -> u8 {
self.actions().iter().map(|action| action.time_cost()).sum()
}
}

pub const FULL_SEARCH_ACTIONS: &[SolverAction] = &[
SolverAction::TricksOfTheTradeCombo,
SolverAction::IntensiveSynthesisCombo,
SolverAction::PreciseTouchCombo,
// progress
SolverAction::Single(Action::BasicSynthesis),
SolverAction::Single(Action::Veneration),
SolverAction::Single(Action::MuscleMemory),
SolverAction::Single(Action::CarefulSynthesis),
SolverAction::Single(Action::Groundwork),
SolverAction::Single(Action::IntensiveSynthesis),
SolverAction::Single(Action::PrudentSynthesis),
// quality
SolverAction::Single(Action::BasicTouch),
SolverAction::Single(Action::StandardTouch),
SolverAction::Single(Action::GreatStrides),
SolverAction::Single(Action::Innovation),
SolverAction::Single(Action::ByregotsBlessing),
SolverAction::Single(Action::PreciseTouch),
SolverAction::Single(Action::PrudentTouch),
SolverAction::Single(Action::Reflect),
SolverAction::Single(Action::PreparatoryTouch),
SolverAction::Single(Action::AdvancedTouch),
SolverAction::Single(Action::TrainedFinesse),
SolverAction::Single(Action::RefinedTouch),
SolverAction::Single(Action::TrainedEye),
SolverAction::Single(Action::QuickInnovation),
// durability
SolverAction::Single(Action::MasterMend),
SolverAction::Single(Action::WasteNot),
SolverAction::Single(Action::WasteNot2),
SolverAction::Single(Action::Manipulation),
SolverAction::Single(Action::ImmaculateMend),
SolverAction::Single(Action::TrainedPerfection),
// misc
SolverAction::Single(Action::DelicateSynthesis),
SolverAction::Single(Action::Observe),
SolverAction::Single(Action::TricksOfTheTrade),
];

pub const PROGRESS_ONLY_SEARCH_ACTIONS: &[SolverAction] = &[
SolverAction::IntensiveSynthesisCombo,
SolverAction::TricksOfTheTradeCombo,
// progress
SolverAction::Single(Action::BasicSynthesis),
SolverAction::Single(Action::Veneration),
SolverAction::Single(Action::MuscleMemory),
SolverAction::Single(Action::CarefulSynthesis),
SolverAction::Single(Action::Groundwork),
SolverAction::Single(Action::IntensiveSynthesis),
SolverAction::Single(Action::PrudentSynthesis),
// durability
SolverAction::Single(Action::MasterMend),
SolverAction::Single(Action::WasteNot),
SolverAction::Single(Action::WasteNot2),
SolverAction::Single(Action::Manipulation),
SolverAction::Single(Action::ImmaculateMend),
SolverAction::Single(Action::TrainedPerfection),
// misc
SolverAction::Single(Action::TricksOfTheTrade),
];

pub const QUALITY_ONLY_SEARCH_ACTIONS: &[SolverAction] = &[
SolverAction::TricksOfTheTradeCombo,
SolverAction::PreciseTouchCombo,
// quality
SolverAction::Single(Action::BasicTouch),
SolverAction::Single(Action::StandardTouch),
SolverAction::Single(Action::GreatStrides),
SolverAction::Single(Action::Innovation),
SolverAction::Single(Action::ByregotsBlessing),
SolverAction::Single(Action::PreciseTouch),
SolverAction::Single(Action::PrudentTouch),
SolverAction::Single(Action::Reflect),
SolverAction::Single(Action::PreparatoryTouch),
SolverAction::Single(Action::AdvancedTouch),
SolverAction::Single(Action::TrainedFinesse),
SolverAction::Single(Action::RefinedTouch),
SolverAction::Single(Action::TrainedEye),
SolverAction::Single(Action::QuickInnovation),
// durability
SolverAction::Single(Action::MasterMend),
SolverAction::Single(Action::WasteNot),
SolverAction::Single(Action::WasteNot2),
SolverAction::Single(Action::Manipulation),
SolverAction::Single(Action::ImmaculateMend),
SolverAction::Single(Action::TrainedPerfection),
// misc
SolverAction::Single(Action::Observe),
SolverAction::Single(Action::TricksOfTheTrade),
];

pub fn use_solver_action(
settings: &Settings,
mut state: SimulationState,
action: SolverAction,
) -> Result<SimulationState, &'static str> {
for action in action.actions().iter() {
state = state.use_action(*action, Condition::Normal, settings)?;
}
Ok(state)
}
16 changes: 3 additions & 13 deletions solvers/src/finish_solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ use simulator::*;

use rustc_hash::FxHashMap as HashMap;

use super::actions::{DURABILITY_ACTIONS, PROGRESS_ACTIONS};

const SEARCH_ACTIONS: ActionMask = PROGRESS_ACTIONS
.union(DURABILITY_ACTIONS)
.remove(Action::TricksOfTheTrade)
.remove(Action::DelicateSynthesis);
use crate::actions::{use_solver_action, FULL_SEARCH_ACTIONS};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct ReducedState {
Expand Down Expand Up @@ -75,14 +70,9 @@ impl FinishSolver {
Some(max_progress) => *max_progress,
None => {
let mut max_progress = 0;
for action in SEARCH_ACTIONS
.intersection(self.settings.allowed_actions)
.actions_iter()
{
for action in FULL_SEARCH_ACTIONS.iter() {
if let Ok(new_state) =
state
.to_state()
.use_action(action, Condition::Normal, &self.settings)
use_solver_action(&self.settings, state.to_state(), *action)
{
if !new_state.is_final(&self.settings) {
let child_progress =
Expand Down
51 changes: 28 additions & 23 deletions solvers/src/macro_solver/fast_lower_bound.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
use radix_heap::RadixHeapMap;
use simulator::{Action, ActionMask, Combo, Condition, Settings, SimulationState};
use simulator::{Action, ActionMask, Combo, Settings, SimulationState};

use crate::{
actions::{DURABILITY_ACTIONS, QUALITY_ACTIONS},
actions::{use_solver_action, SolverAction, QUALITY_ONLY_SEARCH_ACTIONS},
finish_solver::FinishSolver,
macro_solver::pareto_front::QualityParetoFront,
utils::NamedTimer,
QualityUpperBoundSolver,
};

const SEARCH_ACTIONS: ActionMask = QUALITY_ACTIONS
.union(DURABILITY_ACTIONS)
.remove(Action::StandardTouch) // non-combo version
.remove(Action::AdvancedTouch) // non-combo version
.remove(Action::DelicateSynthesis);

pub fn fast_lower_bound(
state: SimulationState,
settings: &Settings,
finish_solver: &mut FinishSolver,
upper_bound_solver: &mut QualityUpperBoundSolver,
) -> Option<u16> {
let _timer = NamedTimer::new("Fast lower bound");
let allowed_actions = settings.allowed_actions.intersection(SEARCH_ACTIONS);

let mut search_queue: RadixHeapMap<u16, SimulationState> = RadixHeapMap::default();
let mut pareto_set = QualityParetoFront::default();
Expand All @@ -35,17 +28,17 @@ pub fn fast_lower_bound(
if score <= quality_lower_bound {
break;
}
for action in allowed_actions.actions_iter() {
if !should_use_action(action, &state, allowed_actions) {
for action in QUALITY_ONLY_SEARCH_ACTIONS.iter() {
if !should_use_action(*action, &state, settings.allowed_actions) {
continue;
}
if let Ok(state) = state.use_action(action, Condition::Normal, settings) {
if let Ok(state) = use_solver_action(settings, state, *action) {
if !state.is_final(settings) {
if !finish_solver.can_finish(&state) {
continue;
}
quality_lower_bound = std::cmp::max(quality_lower_bound, state.quality);
if action == Action::ByregotsBlessing {
if *action == SolverAction::Single(Action::ByregotsBlessing) {
continue;
}
let quality_upper_bound = upper_bound_solver.quality_upper_bound(state)?;
Expand All @@ -65,19 +58,27 @@ pub fn fast_lower_bound(
Some(std::cmp::min(settings.max_quality, quality_lower_bound))
}

fn should_use_action(action: Action, state: &SimulationState, allowed_actions: ActionMask) -> bool {
fn should_use_action(
action: SolverAction,
state: &SimulationState,
allowed_actions: ActionMask,
) -> bool {
// Force the use of the next combo action if it is available
match state.combo {
Combo::None => (),
Combo::BasicTouch => {
let combo_available = allowed_actions.has(Action::StandardTouch)
|| allowed_actions.has(Action::RefinedTouch);
return !combo_available
|| matches!(action, Action::StandardTouch | Action::RefinedTouch);
|| matches!(
action,
SolverAction::Single(Action::StandardTouch | Action::RefinedTouch)
);
}
Combo::StandardTouch => {
let combo_available = allowed_actions.has(Action::AdvancedTouch);
return !combo_available || matches!(action, Action::AdvancedTouch);
return !combo_available
|| matches!(action, SolverAction::Single(Action::AdvancedTouch));
}
Combo::SynthesisBegin => {
let combo_available = allowed_actions.has(Action::Reflect)
Expand All @@ -86,19 +87,23 @@ fn should_use_action(action: Action, state: &SimulationState, allowed_actions: A
return !combo_available
|| matches!(
action,
Action::Reflect | Action::MuscleMemory | Action::TrainedEye
SolverAction::Single(
Action::Reflect | Action::MuscleMemory | Action::TrainedEye
)
);
}
}

// Misc
match action {
Action::Innovation => state.effects.innovation() == 0,
Action::Veneration => state.effects.veneration() == 0,
Action::Manipulation => state.effects.manipulation() == 0,
Action::WasteNot | Action::WasteNot2 => state.effects.waste_not() == 0,
Action::GreatStrides => state.effects.great_strides() == 0,
Action::TrainedPerfection => state.effects.waste_not() == 0,
SolverAction::Single(Action::Innovation) => state.effects.innovation() == 0,
SolverAction::Single(Action::Veneration) => state.effects.veneration() == 0,
SolverAction::Single(Action::Manipulation) => state.effects.manipulation() == 0,
SolverAction::Single(Action::WasteNot | Action::WasteNot2) => {
state.effects.waste_not() == 0
}
SolverAction::Single(Action::GreatStrides) => state.effects.great_strides() == 0,
SolverAction::Single(Action::TrainedPerfection) => state.effects.waste_not() == 0,
_ => true,
}
}
Loading

0 comments on commit 6aa0382

Please sign in to comment.