diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 8a2fa8e..dc352e6 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -10,7 +10,7 @@ internal static class Program { private static void Main() { - //TypeLayout.PrintLayout(true); + //TypeLayout.PrintLayout.Node>(true); //return; var input = new SimulationInput( @@ -28,17 +28,15 @@ internal static class Program QualityDivider = 115, ProgressModifier = 80, ProgressDivider = 130, - }, - 0 + } ); - var actions = new List(); var s = Stopwatch.StartNew(); if (true) - (actions, _) = Solver.Crafty.Solver.SearchStepwise(input, actions, a => Console.WriteLine(a)); + _ = Solver.Crafty.Solver.SearchStepwise(input, a => Console.WriteLine(a)); else { - (actions, _) = Solver.Crafty.Solver.SearchOneshot(input, actions); + (var actions, _) = Solver.Crafty.Solver.SearchOneshot(input); foreach (var action in actions) Console.Write($">{action.IntName()}"); Console.WriteLine(); diff --git a/Simulator/Actions/ActionType.cs b/Simulator/Actions/ActionType.cs index 1dd02f3..e9dd1df 100644 --- a/Simulator/Actions/ActionType.cs +++ b/Simulator/Actions/ActionType.cs @@ -1,6 +1,6 @@ namespace Craftimizer.Simulator.Actions; -public enum ActionType +public enum ActionType : byte { AdvancedTouch, BasicSynthesis, diff --git a/Simulator/SimulationInput.cs b/Simulator/SimulationInput.cs index 2f1f5a8..93810e2 100644 --- a/Simulator/SimulationInput.cs +++ b/Simulator/SimulationInput.cs @@ -9,11 +9,11 @@ public sealed class SimulationInput public int BaseProgressGain { get; } public int BaseQualityGain { get; } - public SimulationInput(CharacterStats stats, RecipeInfo recipe, int seed) + public SimulationInput(CharacterStats stats, RecipeInfo recipe, int? seed = null) { Stats = stats; Recipe = recipe; - Random = new Random(seed); + Random = seed == null ? new() : new(seed.Value); // https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88 { diff --git a/Solver/Crafty/Solver.cs b/Solver/Crafty/Solver.cs index acdd894..7aaec1a 100644 --- a/Solver/Crafty/Solver.cs +++ b/Solver/Crafty/Solver.cs @@ -3,6 +3,7 @@ using Craftimizer.Simulator.Actions; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Craftimizer.Solver.Crafty; @@ -12,9 +13,9 @@ public class Solver public Simulator Simulator; public Arena Tree; - //public Random Random => Simulator.Input.Random; + public Random Random => Simulator.Input.Random; - public const int Iterations = 30000; + public const int Iterations = 1000000; public const float ScoreStorageThreshold = 1f; public const float MaxScoreWeightingConstant = 0.1f; public const float ExplorationConstant = 4f; @@ -32,7 +33,7 @@ public class Solver }); } - public Solver(SimulationInput input) : this(new(input), false) + public Solver(SimulationInput input, bool strict) : this(new SimulationState(input), strict) { } @@ -48,7 +49,7 @@ public class Solver }; } - public (int Index, CompletionState State) ExecuteActions(int startIndex, List actions, bool strict = false) + public (int Index, CompletionState State) ExecuteActions(int startIndex, ReadOnlySpan actions, bool strict = false) { var currentIndex = startIndex; foreach (var action in actions) @@ -69,11 +70,11 @@ public class Solver } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RustMaxBy(List source, Func into) + private static int RustMaxBy(ReadOnlySpan source, Func into) { var max = 0; var maxV = into(source[0]); - for (var i = 1; i < source.Count; ++i) + for (var i = 1; i < source.Length; ++i) { var nextV = into(source[i]); if (maxV <= nextV) @@ -97,9 +98,9 @@ public class Solver (length + (Vector.Count - 1)) & ~(Vector.Count - 1); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int EvalBestChild(float parentVisits, List children) + private int EvalBestChild(float parentVisits, ReadOnlySpan children) { - var length = children.Count; + var length = children.Length; var C = ExplorationConstant * MathF.Log(parentVisits); var w = MaxScoreWeightingConstant; @@ -154,7 +155,7 @@ public class Solver } // select the node with the highest score - selectedIndex = EvalBestChild(selectedNode.State.Data.Scores.Visits, selectedNode.Children); + selectedIndex = EvalBestChild(selectedNode.State.Data.Scores.Visits, CollectionsMarshal.AsSpan(selectedNode.Children)); } } @@ -165,20 +166,23 @@ public class Solver if (initialNode.IsComplete) return (initialIndex, initialNode.CompletionState, initialNode.CalculateScore() ?? 0); - var randomAction = initialNode.Data.AvailableActions.ElementAt(0); + var randomIdx = Random.Next(initialNode.Data.AvailableActions.Count); + var randomAction = initialNode.Data.AvailableActions.ElementAt(randomIdx); initialNode.Data.AvailableActions.RemoveAction(randomAction); var expandedState = Execute(initialNode.State, randomAction, true); var expandedIndex = Tree.Insert(initialIndex, expandedState); // playout to a terminal state var currentState = Tree.Get(expandedIndex).State; - var actions = new List(); + byte actionCount = 0; + Span actions = stackalloc ActionType[MaxStepCount]; while (true) { if (currentState.IsComplete) break; - randomAction = currentState.Data.AvailableActions.ElementAt(0); - actions.Add(randomAction); + randomIdx = Random.Next(currentState.Data.AvailableActions.Count); + randomAction = currentState.Data.AvailableActions.ElementAt(randomIdx); + actions[actionCount++] = randomAction; currentState = Execute(currentState.State, randomAction, true); } @@ -188,7 +192,8 @@ public class Solver { if (score >= ScoreStorageThreshold && score >= Tree.Get(0).State.Data.Scores.MaxScore) { - (var terminalIndex, _) = ExecuteActions(expandedIndex, actions, true); + Console.WriteLine("DONE!"); + (var terminalIndex, _) = ExecuteActions(expandedIndex, actions[..actionCount], true); return (terminalIndex, currentState.CompletionState, score); } } @@ -227,7 +232,7 @@ public class Solver var node = Tree.Get(0); while (node.Children.Count != 0) { - var next_index = RustMaxBy(node.Children, n => Tree.Get(n).State.Data.Scores.MaxScore); + var next_index = RustMaxBy(CollectionsMarshal.AsSpan(node.Children), n => Tree.Get(n).State.Data.Scores.MaxScore); node = Tree.Get(next_index); if (node.State.Action != null) actions.Add(node.State.Action.Value); @@ -236,22 +241,11 @@ public class Solver return (actions, node.State); } - public static (SimulationState SimState, CompletionState State) Simulate(SimulationInput input, List actions) + public static (List Actions, SimulationState State) SearchStepwise(SimulationInput input, Action? actionCallback) { - var solver = new Solver(input); - var (index, result) = solver.ExecuteActions(0, actions); - return (solver.Tree.Get(index).State.State, result); - } - - public static (List Actions, SimulationState State) SearchStepwise(SimulationInput input, List actions, Action? actionCallback) - { - var (state, result) = Simulate(input, actions); - if (result != CompletionState.Incomplete) - { - return (actions, state); - } - + var state = new SimulationState(input); //Debugger.Break(); + var actions = new List(); var solver = new Solver(state, true); while (!solver.Simulator.IsComplete) { @@ -272,17 +266,16 @@ public class Solver solver = new Solver(state, true); } - Debugger.Break(); + //Debugger.Break(); return (actions, state); } - public static (List Actions, SimulationState State) SearchOneshot(SimulationInput input, List actions) + public static (List Actions, SimulationState State) SearchOneshot(SimulationInput input) { - var solver = new Solver(input); + var solver = new Solver(input, false); solver.Search(0); var (solution_actions, solution_node) = solver.Solution(); - actions.AddRange(solution_actions); - return (actions, solution_node.State); + return (solution_actions, solution_node.State); } }