diff --git a/.editorconfig b/.editorconfig index 0d8e909..b84c7ed 100644 --- a/.editorconfig +++ b/.editorconfig @@ -157,6 +157,7 @@ dotnet_diagnostic.CA1825.severity = warning dotnet_diagnostic.CA1841.severity = suggestion dotnet_diagnostic.CA1845.severity = suggestion dotnet_diagnostic.MA0011.severity = silent +dotnet_diagnostic.MA0018.severity = silent dotnet_diagnostic.MA0076.severity = silent dotnet_diagnostic.MA0046.severity = silent dotnet_diagnostic.MA0002.severity = silent diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 70c6299..34a3ca5 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -102,9 +102,12 @@ private static async Task RunOther() MaxStepCount = 30, }; - var sim = new SimulatorNoRandom(new(input)); - (_, var state) = sim.Execute(new(input), ActionType.MuscleMemory); - (_, state) = sim.Execute(state, ActionType.PrudentTouch); + var state = new SimulationState(input); + state.ExecuteMultiple(new[] + { + ActionType.MuscleMemory, + ActionType.PrudentTouch + }); //(_, state) = sim.Execute(state, ActionType.Manipulation); //(_, state) = sim.Execute(state, ActionType.Veneration); //(_, state) = sim.Execute(state, ActionType.WasteNot); diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 0114ec7..ac4465a 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -904,7 +904,8 @@ private void DrawIngredientHQEntry(int idx) private void DrawActionHotbars() { - var sim = new Sim(State); + var state = State; + var sim = new Simulator(ref state); var imageSize = ImGui.GetFrameHeight() * 2; var spacing = ImGui.GetStyle().ItemSpacing.Y; @@ -1130,10 +1131,7 @@ private void DrawMacro() ImGui.PopClipRect(); } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - { - var sim = new Sim(lastState); - ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(sim, true)}"); - } + ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(new Simulator(ref lastState), true)}"); lastState = state; } } @@ -1483,10 +1481,12 @@ private void SaveMacro() private void RecalculateState() { InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality)); - var sim = new Sim(InitialState); var lastState = InitialState; foreach (var step in Macro) - lastState = ((step.Response, step.State) = sim.Execute(lastState, step.Action)).State; + { + (step.Response, _, _) = lastState.ExecuteOn(step.Action); + step.State = lastState; + } } private void AddStep(ActionType action, int index = -1, bool isSolver = false) @@ -1500,19 +1500,20 @@ private void AddStep(ActionType action, int index = -1, bool isSolver = false) if (index == -1) { - var sim = new Sim(State); - var resp = sim.Execute(State, action); - Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState }); + var (resp, _, _, newState) = State.Execute(action); + Macro.Add(new() { Action = action, Response = resp, State = newState }); } else { var state = index == 0 ? InitialState : Macro[index - 1].State; - var sim = new Sim(state); - var resp = sim.Execute(state, action); - Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState }); - state = resp.NewState; + var (resp, _, _) = state.ExecuteOn(action); + Macro.Insert(index, new() { Action = action, Response = resp, State = state }); for (var i = index + 1; i < Macro.Count; i++) - state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; + { + var step = Macro[i]; + (step.Response, _, _) = state.ExecuteOn(step.Action); + step.State = state; + } } } @@ -1527,9 +1528,12 @@ private void RemoveStep(int index) Macro.RemoveAt(index); var state = index == 0 ? InitialState : Macro[index - 1].State; - var sim = new Sim(state); for (var i = index; i < Macro.Count; i++) - state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; + { + var step = Macro[i]; + (step.Response, _, _) = state.ExecuteOn(step.Action); + step.State = state; + } } public void Dispose() diff --git a/Craftimizer/Windows/MacroList.cs b/Craftimizer/Windows/MacroList.cs index 3601af6..bd6e8ad 100644 --- a/Craftimizer/Windows/MacroList.cs +++ b/Craftimizer/Windows/MacroList.cs @@ -329,8 +329,7 @@ private void OnMacroListChanged() return state; state = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo)); - var sim = new Sim(state); - (_, state, _) = sim.ExecuteMultiple(state, macro.Actions); + state.ExecuteMultipleOn(macro.Actions); return MacroStateCache[macro] = state; } diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index 5abd350..e74a3b0 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -824,7 +824,6 @@ private void CalculateBestMacrosTask(CancellationToken token) var state = new SimulationState(input); var config = Service.Configuration.SimulatorSolverConfig; var mctsConfig = new MCTSConfig(config); - var simulator = new SimulatorNoRandom(state); List macros = new(Service.Configuration.Macros); token.ThrowIfCancellationRequested(); @@ -835,9 +834,9 @@ private void CalculateBestMacrosTask(CancellationToken token) var bestSaved = macros .Select(macro => { - var (resp, outState, failedIdx) = simulator.ExecuteMultiple(state, macro.Actions); + var (resp, completionState, _, failedIdx, outState) = state.ExecuteMultiple(macro.Actions); outState.ActionCount = macro.Actions.Count; - var score = SimulationNode.CalculateScoreForState(outState, simulator.CompletionState, mctsConfig) ?? 0; + var score = SimulationNode.CalculateScoreForState(outState, completionState, mctsConfig) ?? 0; if (resp != ActionResponse.SimulationComplete) { if (failedIdx != -1) diff --git a/Simulator/Actions/ActionType.cs b/Simulator/Actions/ActionType.cs index 1c6e589..d7dd285 100644 --- a/Simulator/Actions/ActionType.cs +++ b/Simulator/Actions/ActionType.cs @@ -63,12 +63,6 @@ static ActionUtils() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static BaseAction Base(this ActionType me) => Actions[(int)me]; - public static IEnumerable AvailableActions(Simulator simulation) => - simulation.IsComplete - ? Enumerable.Empty() - : Enum.GetValues() - .Where(a => a.Base().CanUse(simulation)); - public static int Level(this ActionType me) => me.Base().Level; diff --git a/Simulator/Actions/AdvancedTouch.cs b/Simulator/Actions/AdvancedTouch.cs index ec1f0af..deccc01 100644 --- a/Simulator/Actions/AdvancedTouch.cs +++ b/Simulator/Actions/AdvancedTouch.cs @@ -8,6 +8,6 @@ internal sealed class AdvancedTouch : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => s.ActionStates.TouchComboIdx == 2 ? 18 : 46; - public override int Efficiency(Simulator s) => 150; + public override int CPCost(Simulator s) => s.ActionStates.TouchComboIdx == 2 ? 18 : 46; + public override int Efficiency(Simulator s) => 150; } diff --git a/Simulator/Actions/BaseAction.cs b/Simulator/Actions/BaseAction.cs index 8e006be..3b01231 100644 --- a/Simulator/Actions/BaseAction.cs +++ b/Simulator/Actions/BaseAction.cs @@ -21,14 +21,14 @@ public abstract class BaseAction public virtual bool IncreasesStepCount => true; // Instanced properties - public abstract int CPCost(Simulator s); - public virtual int Efficiency(Simulator s) => 0; - public virtual float SuccessRate(Simulator s) => 1f; + public abstract int CPCost(Simulator s) where S : ISimulator; + public virtual int Efficiency(Simulator s) where S : ISimulator => 0; + public virtual float SuccessRate(Simulator s) where S : ISimulator => 1f; - public virtual bool CanUse(Simulator s) => + public virtual bool CanUse(Simulator s) where S : ISimulator => s.Input.Stats.Level >= Level && s.CP >= CPCost(s); - public virtual void Use(Simulator s) + public virtual void Use(Simulator s) where S : ISimulator { if (s.RollSuccess(SuccessRate(s))) UseSuccess(s); @@ -52,7 +52,7 @@ public virtual void Use(Simulator s) s.ActiveEffects.DecrementDuration(); } - public virtual void UseSuccess(Simulator s) + public virtual void UseSuccess(Simulator s) where S : ISimulator { if (Efficiency(s) != 0f) { @@ -63,7 +63,7 @@ public virtual void UseSuccess(Simulator s) } } - public virtual string GetTooltip(Simulator s, bool addUsability) + public virtual string GetTooltip(Simulator s, bool addUsability) where S : ISimulator { var builder = new StringBuilder(); if (addUsability && !CanUse(s)) diff --git a/Simulator/Actions/BaseBuffAction.cs b/Simulator/Actions/BaseBuffAction.cs index dbabd50..4fac2ec 100644 --- a/Simulator/Actions/BaseBuffAction.cs +++ b/Simulator/Actions/BaseBuffAction.cs @@ -11,16 +11,16 @@ internal abstract class BaseBuffAction : BaseAction public sealed override int DurabilityCost => 0; - public override void UseSuccess(Simulator s) => + public override void UseSuccess(Simulator s) => s.AddEffect(Effect, Duration); - public override string GetTooltip(Simulator s, bool addUsability) + public override string GetTooltip(Simulator s, bool addUsability) { var builder = new StringBuilder(base.GetTooltip(s, addUsability)); builder.AppendLine($"{Duration} Steps"); return builder.ToString(); } - protected string GetBaseTooltip(Simulator s, bool addUsability) => + protected string GetBaseTooltip(Simulator s, bool addUsability) where S : ISimulator => base.GetTooltip(s, addUsability); } diff --git a/Simulator/Actions/BaseComboAction.cs b/Simulator/Actions/BaseComboAction.cs index 80d11f7..d220273 100644 --- a/Simulator/Actions/BaseComboAction.cs +++ b/Simulator/Actions/BaseComboAction.cs @@ -7,7 +7,7 @@ public abstract class BaseComboAction : BaseAction public sealed override ActionCategory Category => ActionCategory.Combo; - protected bool BaseCanUse(Simulator s) => + protected bool BaseCanUse(Simulator s) where S : ISimulator => base.CanUse(s); private static bool VerifyDurability2(int durabilityA, int durability, Effects effects) @@ -26,9 +26,6 @@ private static bool VerifyDurability2(int durabilityA, int durability, Effects e public static bool VerifyDurability2(SimulationState s, int durabilityA) => VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects); - public static bool VerifyDurability2(Simulator s, int durabilityA) => - VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects); - public static bool VerifyDurability3(int durabilityA, int durabilityB, int durability, Effects effects) { var wasteNots = Math.Max(effects.GetDuration(EffectType.WasteNot), effects.GetDuration(EffectType.WasteNot2)); @@ -54,9 +51,6 @@ public static bool VerifyDurability3(int durabilityA, int durabilityB, int durab return true; } - public static bool VerifyDurability3(Simulator s, int durabilityA, int durabilityB) => - VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects); - public static bool VerifyDurability3(SimulationState s, int durabilityA, int durabilityB) => VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects); } diff --git a/Simulator/Actions/BaseComboActionImpl.cs b/Simulator/Actions/BaseComboActionImpl.cs index 623fd53..b4d1f74 100644 --- a/Simulator/Actions/BaseComboActionImpl.cs +++ b/Simulator/Actions/BaseComboActionImpl.cs @@ -11,17 +11,17 @@ namespace Craftimizer.Simulator.Actions; public override bool IncreasesProgress => ActionA.IncreasesProgress || ActionB.IncreasesProgress; public override bool IncreasesQuality => ActionA.IncreasesQuality || ActionB.IncreasesQuality; - public override int CPCost(Simulator s) => ActionA.CPCost(s) + ActionB.CPCost(s); + public override int CPCost(Simulator s) => ActionA.CPCost(s) + ActionB.CPCost(s); - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => BaseCanUse(s) && VerifyDurability2(s, ActionA.DurabilityCost); - public override void Use(Simulator s) + public override void Use(Simulator s) { ActionA.Use(s); ActionB.Use(s); } - public override string GetTooltip(Simulator s, bool addUsability) => + public override string GetTooltip(Simulator s, bool addUsability) => $"{ActionA.GetTooltip(s, addUsability)}\n\n{ActionB.GetTooltip(s, addUsability)}"; } diff --git a/Simulator/Actions/BasicSynthesis.cs b/Simulator/Actions/BasicSynthesis.cs index 2ad8d82..80c393b 100644 --- a/Simulator/Actions/BasicSynthesis.cs +++ b/Simulator/Actions/BasicSynthesis.cs @@ -8,7 +8,7 @@ internal sealed class BasicSynthesis : BaseAction public override bool IncreasesProgress => true; - public override int CPCost(Simulator s) => 0; + public override int CPCost(Simulator s) => 0; // Basic Synthesis Mastery Trait - public override int Efficiency(Simulator s) => s.Input.Stats.Level >= 31 ? 120 : 100; + public override int Efficiency(Simulator s) => s.Input.Stats.Level >= 31 ? 120 : 100; } diff --git a/Simulator/Actions/BasicTouch.cs b/Simulator/Actions/BasicTouch.cs index e3a2434..c18a73a 100644 --- a/Simulator/Actions/BasicTouch.cs +++ b/Simulator/Actions/BasicTouch.cs @@ -8,6 +8,6 @@ internal sealed class BasicTouch : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 18; - public override int Efficiency(Simulator s) => 100; + public override int CPCost(Simulator s) => 18; + public override int Efficiency(Simulator s) => 100; } diff --git a/Simulator/Actions/ByregotsBlessing.cs b/Simulator/Actions/ByregotsBlessing.cs index 257db8e..a288d3a 100644 --- a/Simulator/Actions/ByregotsBlessing.cs +++ b/Simulator/Actions/ByregotsBlessing.cs @@ -8,12 +8,12 @@ internal sealed class ByregotsBlessing : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 24; - public override int Efficiency(Simulator s) => 100 + (20 * s.GetEffectStrength(EffectType.InnerQuiet)); + public override int CPCost(Simulator s) => 24; + public override int Efficiency(Simulator s) => 100 + (20 * s.GetEffectStrength(EffectType.InnerQuiet)); - public override bool CanUse(Simulator s) => s.HasEffect(EffectType.InnerQuiet) && base.CanUse(s); + public override bool CanUse(Simulator s) => s.HasEffect(EffectType.InnerQuiet) && base.CanUse(s); - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.RemoveEffect(EffectType.InnerQuiet); diff --git a/Simulator/Actions/CarefulObservation.cs b/Simulator/Actions/CarefulObservation.cs index ebee287..c277d67 100644 --- a/Simulator/Actions/CarefulObservation.cs +++ b/Simulator/Actions/CarefulObservation.cs @@ -10,12 +10,12 @@ internal sealed class CarefulObservation : BaseAction public override int DurabilityCost => 0; public override bool IncreasesStepCount => false; - public override int CPCost(Simulator s) => 0; + public override int CPCost(Simulator s) => 0; - public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && s.ActionStates.CarefulObservationCount < 3; + public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && s.ActionStates.CarefulObservationCount < 3; - public override void UseSuccess(Simulator s) => s.StepCondition(); + public override void UseSuccess(Simulator s) => s.StepCondition(); - public override string GetTooltip(Simulator s, bool addUsability) => + public override string GetTooltip(Simulator s, bool addUsability) => $"{base.GetTooltip(s, addUsability)}Specialist Only"; } diff --git a/Simulator/Actions/CarefulSynthesis.cs b/Simulator/Actions/CarefulSynthesis.cs index 4449583..389900a 100644 --- a/Simulator/Actions/CarefulSynthesis.cs +++ b/Simulator/Actions/CarefulSynthesis.cs @@ -8,7 +8,7 @@ internal sealed class CarefulSynthesis : BaseAction public override bool IncreasesProgress => true; - public override int CPCost(Simulator s) => 7; + public override int CPCost(Simulator s) => 7; // Careful Synthesis Mastery Trait - public override int Efficiency(Simulator s) => s.Input.Stats.Level >= 82 ? 180 : 150; + public override int Efficiency(Simulator s) => s.Input.Stats.Level >= 82 ? 180 : 150; } diff --git a/Simulator/Actions/DelicateSynthesis.cs b/Simulator/Actions/DelicateSynthesis.cs index 688352f..886dcc9 100644 --- a/Simulator/Actions/DelicateSynthesis.cs +++ b/Simulator/Actions/DelicateSynthesis.cs @@ -9,6 +9,6 @@ internal sealed class DelicateSynthesis : BaseAction public override bool IncreasesProgress => true; public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 32; - public override int Efficiency(Simulator s) => 100; + public override int CPCost(Simulator s) => 32; + public override int Efficiency(Simulator s) => 100; } diff --git a/Simulator/Actions/FinalAppraisal.cs b/Simulator/Actions/FinalAppraisal.cs index d9ab313..98b005e 100644 --- a/Simulator/Actions/FinalAppraisal.cs +++ b/Simulator/Actions/FinalAppraisal.cs @@ -11,5 +11,5 @@ internal sealed class FinalAppraisal : BaseBuffAction public override EffectType Effect => EffectType.FinalAppraisal; public override byte Duration => 5; - public override int CPCost(Simulator s) => 1; + public override int CPCost(Simulator s) => 1; } diff --git a/Simulator/Actions/FocusedSynthesis.cs b/Simulator/Actions/FocusedSynthesis.cs index 407077e..21e6857 100644 --- a/Simulator/Actions/FocusedSynthesis.cs +++ b/Simulator/Actions/FocusedSynthesis.cs @@ -8,7 +8,7 @@ internal sealed class FocusedSynthesis : BaseAction public override bool IncreasesProgress => true; - public override int CPCost(Simulator s) => 5; - public override int Efficiency(Simulator s) => 200; - public override float SuccessRate(Simulator s) => s.ActionStates.Observed ? 1.00f : 0.50f; + public override int CPCost(Simulator s) => 5; + public override int Efficiency(Simulator s) => 200; + public override float SuccessRate(Simulator s) => s.ActionStates.Observed ? 1.00f : 0.50f; } diff --git a/Simulator/Actions/FocusedTouch.cs b/Simulator/Actions/FocusedTouch.cs index 1afde89..a1db964 100644 --- a/Simulator/Actions/FocusedTouch.cs +++ b/Simulator/Actions/FocusedTouch.cs @@ -8,7 +8,7 @@ internal sealed class FocusedTouch : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 18; - public override int Efficiency(Simulator s) => 150; - public override float SuccessRate(Simulator s) => s.ActionStates.Observed ? 1.00f : 0.50f; + public override int CPCost(Simulator s) => 18; + public override int Efficiency(Simulator s) => 150; + public override float SuccessRate(Simulator s) => s.ActionStates.Observed ? 1.00f : 0.50f; } diff --git a/Simulator/Actions/GreatStrides.cs b/Simulator/Actions/GreatStrides.cs index 2dd964c..d72a28d 100644 --- a/Simulator/Actions/GreatStrides.cs +++ b/Simulator/Actions/GreatStrides.cs @@ -9,5 +9,5 @@ internal sealed class GreatStrides : BaseBuffAction public override EffectType Effect => EffectType.GreatStrides; public override byte Duration => 3; - public override int CPCost(Simulator s) => 32; + public override int CPCost(Simulator s) => 32; } diff --git a/Simulator/Actions/Groundwork.cs b/Simulator/Actions/Groundwork.cs index 118b3ec..550e117 100644 --- a/Simulator/Actions/Groundwork.cs +++ b/Simulator/Actions/Groundwork.cs @@ -9,8 +9,8 @@ internal sealed class Groundwork : BaseAction public override bool IncreasesProgress => true; public override int DurabilityCost => 20; - public override int CPCost(Simulator s) => 18; - public override int Efficiency(Simulator s) + public override int CPCost(Simulator s) => 18; + public override int Efficiency(Simulator s) { // Groundwork Mastery Trait var ret = s.Input.Stats.Level >= 86 ? 360 : 300; diff --git a/Simulator/Actions/HastyTouch.cs b/Simulator/Actions/HastyTouch.cs index cfa179d..b260643 100644 --- a/Simulator/Actions/HastyTouch.cs +++ b/Simulator/Actions/HastyTouch.cs @@ -8,7 +8,7 @@ internal sealed class HastyTouch : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 0; - public override int Efficiency(Simulator s) => 100; - public override float SuccessRate(Simulator s) => 0.60f; + public override int CPCost(Simulator s) => 0; + public override int Efficiency(Simulator s) => 100; + public override float SuccessRate(Simulator s) => 0.60f; } diff --git a/Simulator/Actions/HeartAndSoul.cs b/Simulator/Actions/HeartAndSoul.cs index 16e7b1a..333d531 100644 --- a/Simulator/Actions/HeartAndSoul.cs +++ b/Simulator/Actions/HeartAndSoul.cs @@ -11,10 +11,10 @@ internal sealed class HeartAndSoul : BaseBuffAction public override EffectType Effect => EffectType.HeartAndSoul; - public override int CPCost(Simulator s) => 0; + public override int CPCost(Simulator s) => 0; - public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && !s.ActionStates.UsedHeartAndSoul; + public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && !s.ActionStates.UsedHeartAndSoul; - public override string GetTooltip(Simulator s, bool addUsability) => + public override string GetTooltip(Simulator s, bool addUsability) => $"{GetBaseTooltip(s, addUsability)}Specialist Only"; } diff --git a/Simulator/Actions/Innovation.cs b/Simulator/Actions/Innovation.cs index 1f60160..8a5406a 100644 --- a/Simulator/Actions/Innovation.cs +++ b/Simulator/Actions/Innovation.cs @@ -9,5 +9,5 @@ internal sealed class Innovation : BaseBuffAction public override EffectType Effect => EffectType.Innovation; public override byte Duration => 4; - public override int CPCost(Simulator s) => 18; + public override int CPCost(Simulator s) => 18; } diff --git a/Simulator/Actions/IntensiveSynthesis.cs b/Simulator/Actions/IntensiveSynthesis.cs index 54db7b0..6d856ab 100644 --- a/Simulator/Actions/IntensiveSynthesis.cs +++ b/Simulator/Actions/IntensiveSynthesis.cs @@ -8,14 +8,14 @@ internal sealed class IntensiveSynthesis : BaseAction public override bool IncreasesProgress => true; - public override int CPCost(Simulator s) => 6; - public override int Efficiency(Simulator s) => 400; + public override int CPCost(Simulator s) => 6; + public override int Efficiency(Simulator s) => 400; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => (s.Condition == Condition.Good || s.Condition == Condition.Excellent || s.HasEffect(EffectType.HeartAndSoul)) && base.CanUse(s); - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); if (s.Condition != Condition.Good && s.Condition != Condition.Excellent) diff --git a/Simulator/Actions/Manipulation.cs b/Simulator/Actions/Manipulation.cs index 804ee65..89a8606 100644 --- a/Simulator/Actions/Manipulation.cs +++ b/Simulator/Actions/Manipulation.cs @@ -9,10 +9,10 @@ internal sealed class Manipulation : BaseBuffAction public override EffectType Effect => EffectType.Manipulation; public override byte Duration => 8; - public override int CPCost(Simulator s) => 96; - public override bool CanUse(Simulator s) => s.Input.Stats.CanUseManipulation && base.CanUse(s); + public override int CPCost(Simulator s) => 96; + public override bool CanUse(Simulator s) => s.Input.Stats.CanUseManipulation && base.CanUse(s); - public override void Use(Simulator s) + public override void Use(Simulator s) { UseSuccess(s); diff --git a/Simulator/Actions/MastersMend.cs b/Simulator/Actions/MastersMend.cs index f398be4..a64c885 100644 --- a/Simulator/Actions/MastersMend.cs +++ b/Simulator/Actions/MastersMend.cs @@ -8,8 +8,8 @@ internal sealed class MastersMend : BaseAction public override int DurabilityCost => 0; - public override int CPCost(Simulator s) => 88; + public override int CPCost(Simulator s) => 88; - public override void UseSuccess(Simulator s) => + public override void UseSuccess(Simulator s) => s.RestoreDurability(30); } diff --git a/Simulator/Actions/MuscleMemory.cs b/Simulator/Actions/MuscleMemory.cs index f6eb09d..f6e8382 100644 --- a/Simulator/Actions/MuscleMemory.cs +++ b/Simulator/Actions/MuscleMemory.cs @@ -8,12 +8,12 @@ internal sealed class MuscleMemory : BaseAction public override bool IncreasesProgress => true; - public override int CPCost(Simulator s) => 6; - public override int Efficiency(Simulator s) => 300; + public override int CPCost(Simulator s) => 6; + public override int Efficiency(Simulator s) => 300; - public override bool CanUse(Simulator s) => s.IsFirstStep && base.CanUse(s); + public override bool CanUse(Simulator s) => s.IsFirstStep && base.CanUse(s); - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.AddEffect(EffectType.MuscleMemory, 5); diff --git a/Simulator/Actions/Observe.cs b/Simulator/Actions/Observe.cs index e0e60d6..5c6c940 100644 --- a/Simulator/Actions/Observe.cs +++ b/Simulator/Actions/Observe.cs @@ -8,5 +8,5 @@ internal sealed class Observe : BaseAction public override int DurabilityCost => 0; - public override int CPCost(Simulator s) => 7; + public override int CPCost(Simulator s) => 7; } diff --git a/Simulator/Actions/PreciseTouch.cs b/Simulator/Actions/PreciseTouch.cs index 1be9c2d..5e7a86a 100644 --- a/Simulator/Actions/PreciseTouch.cs +++ b/Simulator/Actions/PreciseTouch.cs @@ -8,14 +8,14 @@ internal sealed class PreciseTouch : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 18; - public override int Efficiency(Simulator s) => 150; + public override int CPCost(Simulator s) => 18; + public override int Efficiency(Simulator s) => 150; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => (s.Condition == Condition.Good || s.Condition == Condition.Excellent || s.HasEffect(EffectType.HeartAndSoul)) && base.CanUse(s); - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.StrengthenEffect(EffectType.InnerQuiet); diff --git a/Simulator/Actions/PreparatoryTouch.cs b/Simulator/Actions/PreparatoryTouch.cs index f5f0950..ff8c92e 100644 --- a/Simulator/Actions/PreparatoryTouch.cs +++ b/Simulator/Actions/PreparatoryTouch.cs @@ -9,10 +9,10 @@ internal sealed class PreparatoryTouch : BaseAction public override bool IncreasesQuality => true; public override int DurabilityCost => 20; - public override int CPCost(Simulator s) => 40; - public override int Efficiency(Simulator s) => 200; + public override int CPCost(Simulator s) => 40; + public override int Efficiency(Simulator s) => 200; - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.StrengthenEffect(EffectType.InnerQuiet); diff --git a/Simulator/Actions/PrudentSynthesis.cs b/Simulator/Actions/PrudentSynthesis.cs index 1ac3a22..7ba100c 100644 --- a/Simulator/Actions/PrudentSynthesis.cs +++ b/Simulator/Actions/PrudentSynthesis.cs @@ -9,10 +9,10 @@ internal sealed class PrudentSynthesis : BaseAction public override bool IncreasesProgress => true; public override int DurabilityCost => base.DurabilityCost / 2; - public override int CPCost(Simulator s) => 18; - public override int Efficiency(Simulator s) => 180; + public override int CPCost(Simulator s) => 18; + public override int Efficiency(Simulator s) => 180; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => !(s.HasEffect(EffectType.WasteNot) || s.HasEffect(EffectType.WasteNot2)) && base.CanUse(s); } diff --git a/Simulator/Actions/PrudentTouch.cs b/Simulator/Actions/PrudentTouch.cs index f01f814..e0b52f1 100644 --- a/Simulator/Actions/PrudentTouch.cs +++ b/Simulator/Actions/PrudentTouch.cs @@ -9,10 +9,10 @@ internal sealed class PrudentTouch : BaseAction public override bool IncreasesQuality => true; public override int DurabilityCost => base.DurabilityCost / 2; - public override int CPCost(Simulator s) => 25; - public override int Efficiency(Simulator s) => 100; + public override int CPCost(Simulator s) => 25; + public override int Efficiency(Simulator s) => 100; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => !(s.HasEffect(EffectType.WasteNot) || s.HasEffect(EffectType.WasteNot2)) && base.CanUse(s); } diff --git a/Simulator/Actions/RapidSynthesis.cs b/Simulator/Actions/RapidSynthesis.cs index d24056e..8837ce1 100644 --- a/Simulator/Actions/RapidSynthesis.cs +++ b/Simulator/Actions/RapidSynthesis.cs @@ -8,8 +8,8 @@ internal sealed class RapidSynthesis : BaseAction public override bool IncreasesProgress => true; - public override int CPCost(Simulator s) => 0; + public override int CPCost(Simulator s) => 0; // Rapid Synthesis Mastery Trait - public override int Efficiency(Simulator s) => s.Input.Stats.Level >= 63 ? 500 : 250; - public override float SuccessRate(Simulator s) => 0.50f; + public override int Efficiency(Simulator s) => s.Input.Stats.Level >= 63 ? 500 : 250; + public override float SuccessRate(Simulator s) => 0.50f; } diff --git a/Simulator/Actions/Reflect.cs b/Simulator/Actions/Reflect.cs index 9a7dbdc..e2c012f 100644 --- a/Simulator/Actions/Reflect.cs +++ b/Simulator/Actions/Reflect.cs @@ -8,12 +8,12 @@ internal sealed class Reflect : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 6; - public override int Efficiency(Simulator s) => 100; + public override int CPCost(Simulator s) => 6; + public override int Efficiency(Simulator s) => 100; - public override bool CanUse(Simulator s) => s.IsFirstStep && base.CanUse(s); + public override bool CanUse(Simulator s) => s.IsFirstStep && base.CanUse(s); - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.StrengthenEffect(EffectType.InnerQuiet); diff --git a/Simulator/Actions/StandardTouch.cs b/Simulator/Actions/StandardTouch.cs index 59b0d1e..bbf596c 100644 --- a/Simulator/Actions/StandardTouch.cs +++ b/Simulator/Actions/StandardTouch.cs @@ -8,6 +8,6 @@ internal sealed class StandardTouch : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => s.ActionStates.TouchComboIdx == 1 ? 18 : 32; - public override int Efficiency(Simulator s) => 125; + public override int CPCost(Simulator s) => s.ActionStates.TouchComboIdx == 1 ? 18 : 32; + public override int Efficiency(Simulator s) => 125; } diff --git a/Simulator/Actions/TrainedEye.cs b/Simulator/Actions/TrainedEye.cs index 494fb0c..6145342 100644 --- a/Simulator/Actions/TrainedEye.cs +++ b/Simulator/Actions/TrainedEye.cs @@ -8,17 +8,17 @@ internal sealed class TrainedEye : BaseAction public override bool IncreasesQuality => true; - public override int CPCost(Simulator s) => 250; + public override int CPCost(Simulator s) => 250; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => s.IsFirstStep && !s.Input.Recipe.IsExpert && s.Input.Stats.Level >= (s.Input.Recipe.ClassJobLevel + 10) && base.CanUse(s); - public override void UseSuccess(Simulator s) => + public override void UseSuccess(Simulator s) => s.IncreaseQualityRaw(s.Input.Recipe.MaxQuality - s.Quality); - public override string GetTooltip(Simulator s, bool addUsability) => + public override string GetTooltip(Simulator s, bool addUsability) => $"{base.GetTooltip(s, addUsability)}+{s.Input.Recipe.MaxQuality - s.Quality} Quality"; } diff --git a/Simulator/Actions/TrainedFinesse.cs b/Simulator/Actions/TrainedFinesse.cs index 28026c2..589025a 100644 --- a/Simulator/Actions/TrainedFinesse.cs +++ b/Simulator/Actions/TrainedFinesse.cs @@ -9,10 +9,10 @@ internal sealed class TrainedFinesse : BaseAction public override bool IncreasesQuality => true; public override int DurabilityCost => 0; - public override int CPCost(Simulator s) => 32; - public override int Efficiency(Simulator s) => 100; + public override int CPCost(Simulator s) => 32; + public override int Efficiency(Simulator s) => 100; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => s.GetEffectStrength(EffectType.InnerQuiet) == 10 && base.CanUse(s); } diff --git a/Simulator/Actions/TricksOfTheTrade.cs b/Simulator/Actions/TricksOfTheTrade.cs index ae9f5d2..c3b0241 100644 --- a/Simulator/Actions/TricksOfTheTrade.cs +++ b/Simulator/Actions/TricksOfTheTrade.cs @@ -8,13 +8,13 @@ internal sealed class TricksOfTheTrade : BaseAction public override int DurabilityCost => 0; - public override int CPCost(Simulator s) => 0; + public override int CPCost(Simulator s) => 0; - public override bool CanUse(Simulator s) => + public override bool CanUse(Simulator s) => (s.Condition == Condition.Good || s.Condition == Condition.Excellent || s.HasEffect(EffectType.HeartAndSoul)) && base.CanUse(s); - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { s.RestoreCP(20); if (s.Condition != Condition.Good && s.Condition != Condition.Excellent) diff --git a/Simulator/Actions/Veneration.cs b/Simulator/Actions/Veneration.cs index b270587..8d81b06 100644 --- a/Simulator/Actions/Veneration.cs +++ b/Simulator/Actions/Veneration.cs @@ -9,5 +9,5 @@ internal sealed class Veneration : BaseBuffAction public override EffectType Effect => EffectType.Veneration; public override byte Duration => 4; - public override int CPCost(Simulator s) => 18; + public override int CPCost(Simulator s) => 18; } diff --git a/Simulator/Actions/WasteNot.cs b/Simulator/Actions/WasteNot.cs index 37bd64c..c63cd47 100644 --- a/Simulator/Actions/WasteNot.cs +++ b/Simulator/Actions/WasteNot.cs @@ -9,9 +9,9 @@ internal sealed class WasteNot : BaseBuffAction public override EffectType Effect => EffectType.WasteNot; public override byte Duration => 4; - public override int CPCost(Simulator s) => 56; + public override int CPCost(Simulator s) => 56; - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.RemoveEffect(EffectType.WasteNot2); diff --git a/Simulator/Actions/WasteNot2.cs b/Simulator/Actions/WasteNot2.cs index d939f96..45512cf 100644 --- a/Simulator/Actions/WasteNot2.cs +++ b/Simulator/Actions/WasteNot2.cs @@ -9,9 +9,9 @@ internal sealed class WasteNot2 : BaseBuffAction public override EffectType Effect => EffectType.WasteNot2; public override byte Duration => 8; - public override int CPCost(Simulator s) => 98; + public override int CPCost(Simulator s) => 98; - public override void UseSuccess(Simulator s) + public override void UseSuccess(Simulator s) { base.UseSuccess(s); s.RemoveEffect(EffectType.WasteNot); diff --git a/Simulator/ISimulator.cs b/Simulator/ISimulator.cs new file mode 100644 index 0000000..fceabfd --- /dev/null +++ b/Simulator/ISimulator.cs @@ -0,0 +1,20 @@ +namespace Craftimizer.Simulator; + +public interface ISimulator +{ + static CompletionState GetCompletionStateBase(Simulator s) where S : ISimulator + { + if (s.Progress >= s.Input.Recipe.MaxProgress) + return CompletionState.ProgressComplete; + if (s.Durability <= 0) + return CompletionState.NoMoreDurability; + return CompletionState.Incomplete; + } + + virtual static CompletionState GetCompletionState(Simulator s) where S : ISimulator => + GetCompletionStateBase(s); + + abstract static Condition GetNextRandomCondition(Simulator s) where S : ISimulator; + + abstract static bool RollSuccessRaw(Simulator s, float successRate) where S : ISimulator; +} diff --git a/Simulator/SimulationInput.cs b/Simulator/SimulationInput.cs index fe56059..bd1f508 100644 --- a/Simulator/SimulationInput.cs +++ b/Simulator/SimulationInput.cs @@ -5,6 +5,7 @@ public sealed class SimulationInput public CharacterStats Stats { get; } public RecipeInfo Recipe { get; } public Random Random { get; } + public object? SolverData { get; set; } public int StartingQuality { get; } public int BaseProgressGain { get; } diff --git a/Simulator/SimulationState.cs b/Simulator/SimulationState.cs index e743ade..94f92b1 100644 --- a/Simulator/SimulationState.cs +++ b/Simulator/SimulationState.cs @@ -1,3 +1,5 @@ +using Craftimizer.Simulator.Actions; +using System; using System.Runtime.InteropServices; namespace Craftimizer.Simulator; @@ -44,4 +46,39 @@ public SimulationState(SimulationInput input) ActionCount = 0; ActionStates = new(); } + + public (ActionResponse Response, CompletionState State, bool IsComplete) ExecuteOn(ActionType action) where S : ISimulator + { + var sim = new Simulator(ref this); + return (sim.Execute(action), sim.CompletionState, sim.IsComplete); + } + + public readonly (ActionResponse Response, CompletionState State, bool IsComplete, SimulationState NewState) Execute(ActionType action) where S : ISimulator + { + var self = this; + var (resp, state, complete) = self.ExecuteOn(action); + return (resp, state, complete, self); + } + + public (ActionResponse Response, CompletionState State, bool IsComplete, int FailedActionIdx) ExecuteMultipleOn(IEnumerable actions) where S : ISimulator + { + var i = 0; + var complete = false; + var state = CompletionState.Incomplete; + foreach (var action in actions) + { + (var resp, state, complete) = ExecuteOn(action); + if (resp != ActionResponse.UsedAction) + return (resp, state, complete, i); + i++; + } + return (ActionResponse.UsedAction, state, complete, -1); + } + + public readonly (ActionResponse Response, CompletionState State, bool IsComplete, int FailedActionIdx, SimulationState NewState) ExecuteMultiple(IEnumerable actions) where S : ISimulator + { + var self = this; + var (resp, state, complete, idx) = self.ExecuteMultipleOn(actions); + return (resp, state, complete, idx, self); + } } diff --git a/Simulator/Simulator.cs b/Simulator/Simulator.cs index 492b4db..a739a04 100644 --- a/Simulator/Simulator.cs +++ b/Simulator/Simulator.cs @@ -4,9 +4,9 @@ namespace Craftimizer.Simulator; -public class Simulator +public readonly ref struct Simulator where S : ISimulator { - protected SimulationState State; + public readonly ref SimulationState State; public SimulationInput Input => State.Input; public ref int ActionCount => ref State.ActionCount; @@ -21,37 +21,15 @@ public class Simulator public bool IsFirstStep => State.StepCount == 0; - public virtual CompletionState CompletionState { - get - { - if (Progress >= Input.Recipe.MaxProgress) - return CompletionState.ProgressComplete; - if (Durability <= 0) - return CompletionState.NoMoreDurability; - return CompletionState.Incomplete; - } - } + public CompletionState CompletionState => S.GetCompletionState(this); public bool IsComplete => CompletionState != CompletionState.Incomplete; - public IEnumerable AvailableActions => ActionUtils.AvailableActions(this); - - public Simulator(SimulationState state) - { - State = state; - } - - public void SetState(SimulationState state) - { - State = state; - } - - public (ActionResponse Response, SimulationState NewState) Execute(SimulationState state, ActionType action) + public Simulator(ref SimulationState state) { - State = state; - return (Execute(action), State); + State = ref state; } - private ActionResponse Execute(ActionType action) + public ActionResponse Execute(ActionType action) { if (IsComplete) return ActionResponse.SimulationComplete; @@ -75,19 +53,8 @@ private ActionResponse Execute(ActionType action) return ActionResponse.UsedAction; } - public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(SimulationState state, IEnumerable actions) - { - State = state; - var i = 0; - foreach(var action in actions) - { - var resp = Execute(action); - if (resp != ActionResponse.UsedAction) - return (resp, State, i); - i++; - } - return (ActionResponse.UsedAction, State, -1); - } + public static implicit operator SimulationState(Simulator s) => + s.State; [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -123,11 +90,8 @@ public void RemoveEffect(EffectType effect) => public bool HasEffect(EffectType effect) => ActiveEffects.HasEffect(effect); - public virtual bool RollSuccessRaw(float successRate) => - successRate >= Input.Random.NextSingle(); - public bool RollSuccess(float successRate) => - RollSuccessRaw(CalculateSuccessRate(successRate)); + S.RollSuccessRaw(this, CalculateSuccessRate(successRate)); public void IncreaseStepCount() { @@ -135,31 +99,6 @@ public void IncreaseStepCount() StepCondition(); } - private static float GetConditionChance(SimulationInput input, Condition condition) => - condition switch - { - Condition.Good => input.Recipe.IsExpert ? 0.12f : (input.Stats.Level >= 63 ? 0.15f : 0.18f), - Condition.Excellent => 0.04f, - Condition.Centered => 0.15f, - Condition.Sturdy => 0.15f, - Condition.Pliant => 0.10f, - Condition.Malleable => 0.13f, - Condition.Primed => 0.15f, - Condition.GoodOmen => 0.12f, // https://github.com/ffxiv-teamcraft/simulator/issues/77 - _ => 0.00f - }; - - public virtual Condition GetNextRandomCondition() - { - var conditionChance = Input.Random.NextSingle(); - - foreach (var condition in Input.AvailableConditions) - if ((conditionChance -= GetConditionChance(Input, condition)) < 0) - return condition; - - return Condition.Normal; - } - public void StepCondition() { Condition = Condition switch @@ -168,7 +107,7 @@ public void StepCondition() Condition.Good => Condition.Normal, Condition.Excellent => Condition.Poor, Condition.GoodOmen => Condition.Good, - _ => GetNextRandomCondition() + _ => S.GetNextRandomCondition(this) }; } diff --git a/Simulator/SimulatorNoRandom.cs b/Simulator/SimulatorNoRandom.cs index 1f4c827..2803ea1 100644 --- a/Simulator/SimulatorNoRandom.cs +++ b/Simulator/SimulatorNoRandom.cs @@ -1,11 +1,12 @@ namespace Craftimizer.Simulator; -public class SimulatorNoRandom : Simulator +public sealed class SimulatorNoRandom : ISimulator { - public SimulatorNoRandom(SimulationState state) : base(state) - { - } + private SimulatorNoRandom() { } - public sealed override bool RollSuccessRaw(float successRate) => successRate == 1; - public sealed override Condition GetNextRandomCondition() => Condition.Normal; + public static Condition GetNextRandomCondition(Simulator s) where S : ISimulator => + Condition.Normal; + + public static bool RollSuccessRaw(Simulator s, float successRate) where S : ISimulator => + successRate == 1; } diff --git a/Simulator/SimulatorRandom.cs b/Simulator/SimulatorRandom.cs new file mode 100644 index 0000000..cbec935 --- /dev/null +++ b/Simulator/SimulatorRandom.cs @@ -0,0 +1,34 @@ +namespace Craftimizer.Simulator; + +public class SimulatorRandom : ISimulator +{ + private SimulatorRandom() { } + + public static Condition GetNextRandomCondition(Simulator s) where S : ISimulator + { + static float GetConditionChance(SimulationInput input, Condition condition) => + condition switch + { + Condition.Good => input.Recipe.IsExpert ? 0.12f : (input.Stats.Level >= 63 ? 0.15f : 0.18f), + Condition.Excellent => 0.04f, + Condition.Centered => 0.15f, + Condition.Sturdy => 0.15f, + Condition.Pliant => 0.10f, + Condition.Malleable => 0.13f, + Condition.Primed => 0.15f, + Condition.GoodOmen => 0.12f, // https://github.com/ffxiv-teamcraft/simulator/issues/77 + _ => 0.00f + }; + + var conditionChance = s.Input.Random.NextSingle(); + + foreach (var condition in s.Input.AvailableConditions) + if ((conditionChance -= GetConditionChance(s.Input, condition)) < 0) + return condition; + + return Condition.Normal; + } + + public static bool RollSuccessRaw(Simulator s, float successRate) where S : ISimulator => + successRate >= s.Input.Random.NextSingle(); +} diff --git a/Solver/ArenaBuffer.cs b/Solver/ArenaBuffer.cs index 6405a85..4227f4e 100644 --- a/Solver/ArenaBuffer.cs +++ b/Solver/ArenaBuffer.cs @@ -5,7 +5,7 @@ namespace Craftimizer.Solver; // Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs -public struct ArenaBuffer where T : struct +internal struct ArenaBuffer where T : struct { // Technically 25, but it's very unlikely to actually get to there. // The benchmark reaches 20 at most, but here we have a little leeway just in case. diff --git a/Solver/ArenaNode.cs b/Solver/ArenaNode.cs index 5d65e9f..2c59769 100644 --- a/Solver/ArenaNode.cs +++ b/Solver/ArenaNode.cs @@ -2,7 +2,7 @@ namespace Craftimizer.Solver; -public sealed class ArenaNode where T : struct +internal sealed class ArenaNode where T : struct { public T State; public ArenaBuffer Children; diff --git a/Solver/MCTS.cs b/Solver/MCTS.cs index 8e40a6b..9ccab00 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -9,7 +9,7 @@ namespace Craftimizer.Solver; // https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/simulator.rs -public sealed class MCTS +internal sealed class MCTS { private readonly MCTSConfig config; private readonly Node rootNode; @@ -20,28 +20,29 @@ public sealed class MCTS public MCTS(MCTSConfig config, SimulationState state) { this.config = config; - var sim = new Simulator(state, config.MaxStepCount); + var sim = new Simulator(ref state); rootNode = new(new( state, null, sim.CompletionState, - sim.AvailableActionsHeuristic(config.StrictActions) + Simulator.AvailableActionsHeuristic(sim, config.StrictActions) )); rootScores = new(); } - private static SimulationNode Execute(Simulator simulator, SimulationState state, ActionType action, bool strict) + private static SimulationNode Execute(SimulationState state, ActionType action, bool strict) { - (_, var newState) = simulator.Execute(state, action); + var sim = new Simulator(ref state); + sim.Execute(action); return new( - newState, + state, action, - simulator.CompletionState, - simulator.AvailableActionsHeuristic(strict) + sim.CompletionState, + Simulator.AvailableActionsHeuristic(sim, strict) ); } - private static Node ExecuteActions(Simulator simulator, Node startNode, ReadOnlySpan actions, bool strict) + private static Node ExecuteActions(Node startNode, ReadOnlySpan actions, bool strict) { foreach (var action in actions) { @@ -53,7 +54,7 @@ private static Node ExecuteActions(Simulator simulator, Node startNode, ReadOnly return startNode; state.AvailableActions.RemoveAction(action); - startNode = startNode.Add(Execute(simulator, state.State, action, strict)); + startNode = startNode.Add(Execute(state.State, action, strict)); } return startNode; @@ -174,7 +175,7 @@ private Node Select() } } - private (Node ExpandedNode, float Score) ExpandAndRollout(Random random, Simulator simulator, Node initialNode) + private (Node ExpandedNode, float Score) ExpandAndRollout(Random random, Node initialNode) { ref var initialState = ref initialNode.State; // expand once @@ -182,7 +183,7 @@ private Node Select() return (initialNode, initialState.CalculateScore(config) ?? 0); var poppedAction = initialState.AvailableActions.PopRandom(random); - var expandedNode = initialNode.Add(Execute(simulator, initialState.State, poppedAction, true)); + var expandedNode = initialNode.Add(Execute(initialState.State, poppedAction, true)); // playout to a terminal state var currentState = expandedNode.State.State; @@ -197,11 +198,10 @@ private Node Select() { var nextAction = currentActions.SelectRandom(random); actions[actionCount++] = nextAction; - (_, currentState) = simulator.Execute(currentState, nextAction); - currentCompletionState = simulator.CompletionState; - if (currentCompletionState != CompletionState.Incomplete) + (_, currentCompletionState, var isComplete) = currentState.ExecuteOn(nextAction); + if (isComplete) break; - currentActions = simulator.AvailableActionsHeuristic(true); + currentActions = Simulator.AvailableActionsHeuristic(new Simulator(ref currentState), true); } // store the result if a max score was reached @@ -210,7 +210,7 @@ private Node Select() { if (score >= config.ScoreStorageThreshold && score >= MaxScore) { - var terminalNode = ExecuteActions(simulator, expandedNode, actions[..actionCount], true); + var terminalNode = ExecuteActions(expandedNode, actions[..actionCount], true); return (terminalNode, score); } } @@ -280,7 +280,6 @@ static bool NodesIncomplete(Node node, Stack path) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Search(int iterations, CancellationToken token) { - Simulator simulator = new(rootNode.State.State, config.MaxStepCount); var random = rootNode.State.State.Input.Random; var n = 0; for (var i = 0; i < iterations || MaxScore == 0; i++) @@ -288,7 +287,7 @@ public void Search(int iterations, CancellationToken token) token.ThrowIfCancellationRequested(); var selectedNode = Select(); - var (endNode, score) = ExpandAndRollout(random, simulator, selectedNode); + var (endNode, score) = ExpandAndRollout(random, selectedNode); if (MaxScore == 0) { if (endNode == selectedNode) diff --git a/Solver/RootScores.cs b/Solver/RootScores.cs index 43b8420..3480ab4 100644 --- a/Solver/RootScores.cs +++ b/Solver/RootScores.cs @@ -3,7 +3,7 @@ namespace Craftimizer.Solver; [StructLayout(LayoutKind.Auto)] -public sealed class RootScores +internal sealed class RootScores { public float ScoreSum; public float MaxScore; diff --git a/Solver/Simulator.cs b/Solver/Simulator.cs index 8edf3e4..24c1f39 100644 --- a/Solver/Simulator.cs +++ b/Solver/Simulator.cs @@ -5,103 +5,104 @@ namespace Craftimizer.Solver; -internal sealed class Simulator : SimulatorNoRandom +internal sealed class Simulator : ISimulator { - private readonly int maxStepCount; + private Simulator() { } - public override CompletionState CompletionState + public static CompletionState GetCompletionState(Simulator s) where S : ISimulator { - get + var b = ISimulator.GetCompletionStateBase(s); + if (s.Input.SolverData is SolverConfig { MaxStepCount: var stepCount }) { - var b = base.CompletionState; - if (b == CompletionState.Incomplete && (ActionCount + 1) >= maxStepCount) + if (b == CompletionState.Incomplete && (s.ActionCount + 1) >= stepCount) return CompletionState.MaxActionCountReached; - return b; } + return b; } - public Simulator(SimulationState state, int maxStepCount) : base(state) - { - this.maxStepCount = maxStepCount; - } + public static Condition GetNextRandomCondition(Simulator s) where S : ISimulator => + Condition.Normal; + + public static bool RollSuccessRaw(Simulator s, float successRate) where S : ISimulator => + successRate == 1; // https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/craft_state.rs#L146 [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] // It's just a bunch of if statements, I would assume this is actually quite simple to follow #pragma warning disable MA0051 // Method is too long - private bool CanUseAction(ActionType action, bool strict) + private static bool CanUseAction(Simulator s, ActionType action, bool strict) #pragma warning restore MA0051 // Method is too long { var baseAction = action.Base(); - if (CalculateSuccessRate(baseAction.SuccessRate(this)) != 1) + if (s.CalculateSuccessRate(baseAction.SuccessRate(s)) != 1) return false; // don't allow quality moves at max quality - if (Quality >= Input.Recipe.MaxQuality && baseAction.IncreasesQuality) + if (s.Quality >= s.Input.Recipe.MaxQuality && baseAction.IncreasesQuality) return false; if (action == ActionType.Observe && - ActionStates.Observed) + s.ActionStates.Observed) return false; if (strict) { // always use Trained Eye if it's available if (action == ActionType.TrainedEye) - return baseAction.CanUse(this); + return baseAction.CanUse(s); // only allow Focused moves after Observe - if (ActionStates.Observed && + if (s.ActionStates.Observed && action != ActionType.FocusedSynthesis && action != ActionType.FocusedTouch) return false; // don't allow quality moves under Muscle Memory for difficult crafts - if (Input.Recipe.ClassJobLevel == 90 && - HasEffect(EffectType.MuscleMemory) && + if (s.Input.Recipe.ClassJobLevel == 90 && + s.HasEffect(EffectType.MuscleMemory) && baseAction.IncreasesQuality) return false; // use First Turn actions if it's available and the craft is difficult - if (IsFirstStep && - Input.Recipe.ClassJobLevel == 90 && + if (s.IsFirstStep && + s.Input.Recipe.ClassJobLevel == 90 && baseAction.Category != ActionCategory.FirstTurn && - CP > 10) + s.CP > 10) return false; // don't allow combo actions if the combo is already in progress - if (ActionStates.TouchComboIdx != 0 && + if (s.ActionStates.TouchComboIdx != 0 && (action == ActionType.StandardTouchCombo || action == ActionType.AdvancedTouchCombo)) return false; // don't allow pure quality moves under Veneration - if (HasEffect(EffectType.Veneration) && + if (s.HasEffect(EffectType.Veneration) && !baseAction.IncreasesProgress && baseAction.IncreasesQuality) return false; // don't allow pure quality moves when it won't be able to finish the craft if (baseAction.IncreasesQuality && - CalculateDurabilityCost(baseAction.DurabilityCost) > Durability) + s.CalculateDurabilityCost(baseAction.DurabilityCost) > s.Durability) return false; if (baseAction.IncreasesProgress) { - var progressIncrease = CalculateProgressGain(baseAction.Efficiency(this)); - var wouldFinish = Progress + progressIncrease >= Input.Recipe.MaxProgress; + var progressIncrease = s.CalculateProgressGain(baseAction.Efficiency(s)); + var wouldFinish = s.Progress + progressIncrease >= s.Input.Recipe.MaxProgress; if (wouldFinish) { // don't allow finishing the craft if there is significant quality remaining - if (Quality < Input.Recipe.MaxQuality / 5) + if (s.Quality < s.Input.Recipe.MaxQuality / 5) return false; } else { // don't allow pure progress moves under Innovation, if it wouldn't finish the craft - if (HasEffect(EffectType.Innovation) && + if (s.HasEffect(EffectType.Innovation) && !baseAction.IncreasesQuality && baseAction.IncreasesProgress) return false; @@ -109,48 +110,47 @@ private bool CanUseAction(ActionType action, bool strict) } if (action == ActionType.ByregotsBlessing && - GetEffectStrength(EffectType.InnerQuiet) <= 1) + s.GetEffectStrength(EffectType.InnerQuiet) <= 1) return false; if ((action == ActionType.WasteNot || action == ActionType.WasteNot2) && - (HasEffect(EffectType.WasteNot) || HasEffect(EffectType.WasteNot2))) + (s.HasEffect(EffectType.WasteNot) || s.HasEffect(EffectType.WasteNot2))) return false; if (action == ActionType.Observe && - CP < 12) + s.CP < 12) return false; if (action == ActionType.MastersMend && - Input.Recipe.MaxDurability - Durability < 25) + s.Input.Recipe.MaxDurability - s.Durability < 25) return false; if (action == ActionType.Manipulation && - HasEffect(EffectType.Manipulation)) + s.HasEffect(EffectType.Manipulation)) return false; if (action == ActionType.GreatStrides && - HasEffect(EffectType.GreatStrides)) + s.HasEffect(EffectType.GreatStrides)) return false; if ((action == ActionType.Veneration || action == ActionType.Innovation) && - (GetEffectDuration(EffectType.Veneration) > 1 || GetEffectDuration(EffectType.Innovation) > 1)) + (s.GetEffectDuration(EffectType.Veneration) > 1 || s.GetEffectDuration(EffectType.Innovation) > 1)) return false; } - return baseAction.CanUse(this); + return baseAction.CanUse(s); } // https://github.com/alostsock/crafty/blob/cffbd0cad8bab3cef9f52a3e3d5da4f5e3781842/crafty/src/craft_state.rs#L137 - public ActionSet AvailableActionsHeuristic(bool strict) + public static ActionSet AvailableActionsHeuristic(Simulator s, bool strict) { - if (IsComplete) + if (s.IsComplete) return new(); var ret = new ActionSet(); foreach (var action in ActionSet.AcceptedActions) - if (CanUseAction(action, strict)) + if (CanUseAction(s, action, strict)) ret.AddAction(action); return ret; } - } diff --git a/Solver/Solver.cs b/Solver/Solver.cs index ca6343e..a8cb407 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -37,6 +37,8 @@ public sealed class Solver : IDisposable public Solver(SolverConfig config, SimulationState state) { + state.Input.SolverData = config; + Config = config; State = state; @@ -116,7 +118,6 @@ private async Task SearchStepwiseFurcated() var bestSims = new List<(float Score, SolverSolution Result)>(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); var activeStates = new List() { new(Array.Empty(), state) }; @@ -185,8 +186,8 @@ private async Task SearchStepwiseFurcated() var chosenAction = solutionActions[0]; var newActions = new List(activeActions) { chosenAction }; - var newState = sim.Execute(activeState, chosenAction).NewState; - if (sim.IsComplete) + var (_, _, complete, newState) = activeState.Execute(chosenAction); + if (complete) bestSims.Add((maxScore, new(newActions, newState))); else newStates.Add(new(newActions, newState)); @@ -240,12 +241,12 @@ private async Task SearchStepwiseForked() { var actions = new List(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); + var complete = false; while (true) { Token.ThrowIfCancellationRequested(); - if (sim.IsComplete) + if (complete) break; using var semaphore = new SemaphoreSlim(0, Config.MaxThreadCount); @@ -292,7 +293,7 @@ private async Task SearchStepwiseForked() var chosenAction = solution.Actions[0]; InvokeNewAction(chosenAction); - (_, state) = sim.Execute(state, chosenAction); + (_, _, complete) = state.ExecuteOn(chosenAction); actions.Add(chosenAction); } @@ -303,12 +304,12 @@ private Task SearchStepwise() { var actions = new List(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); + var complete = false; while (true) { Token.ThrowIfCancellationRequested(); - if (sim.IsComplete) + if (complete) break; var solver = new MCTS(MCTSConfig, state); @@ -331,7 +332,7 @@ private Task SearchStepwise() var chosenAction = solution.Actions[0]; InvokeNewAction(chosenAction); - (_, state) = sim.Execute(state, chosenAction); + (_, _, complete) = state.ExecuteOn(chosenAction); actions.Add(chosenAction); } diff --git a/Test/Simulator/Simulator.cs b/Test/Simulator/Simulator.cs index 20756fc..241ce9f 100644 --- a/Test/Simulator/Simulator.cs +++ b/Test/Simulator/Simulator.cs @@ -68,8 +68,7 @@ private static SimulationState AssertCraft(SimulationInput input, IEnumerable(actions); Assert.AreEqual(progress, state.Progress); Assert.AreEqual(quality, state.Quality); Assert.AreEqual(durability, state.Durability); @@ -170,7 +169,7 @@ public void TrainedFinesseProcs() }, 0, 4064, 15, 332); Assert.AreEqual(10, state.ActiveEffects.InnerQuiet); - Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom(state))); + Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new Simulator(ref state))); } [TestMethod]