Changeset - d63ade4d8489
[Not reviewed]
Jason Maltzen - 3 years ago 2021-08-16 07:58:58
jason@hiddenachievement.com
Recipe generation no longer incorrectly skips some recipes when a catalyst is included. This should result in some less expensive receips, and more total recipes tested.
5 files changed with 239 insertions and 28 deletions:
0 comments (0 inline, 0 general)
Models/PaintRecipe.cs
Show inline comments
...
 
@@ -273,4 +273,25 @@ namespace DesertPaintCodex.Models
 
        }
 

	
 
        public uint Concentration
 
        {
 
            get
 
            {
 
                uint concentration = 0;
 
                foreach (ReagentQuantity ingredient in _recipe)
 
                {
 
                    string reagentName = ingredient.Name;
 
                    if (string.IsNullOrEmpty(reagentName))
 
                    {
 
                        continue;
 
                    }
 

	
 
                    Reagent reagent = ReagentService.GetReagent(reagentName);
 
                    if (reagent.IsCatalyst) continue;
 
                    concentration += ingredient.Quantity;
 
                }
 
                return concentration;
 
            }
 
        }
 

	
 
        public uint GetBulkCost(uint quantity)
 
        {
Models/RecipeSearchNode.cs
Show inline comments
...
 
@@ -28,4 +28,8 @@ namespace DesertPaintCodex.Models
 
        public uint MinReagents { get; set; }
 

	
 
        public bool ShouldLog { get; private set; }
 

	
 
        public Action<string>? WriteLog = null;
 

	
 
        private uint _maxReagents;
 
        public uint MaxReagents
...
 
@@ -72,4 +76,6 @@ namespace DesertPaintCodex.Models
 
                CurrentWeights[i] = other.CurrentWeights[i];
 
            }
 

	
 
            RefreshShouldLog();
 
        }
 

	
...
 
@@ -109,4 +115,6 @@ namespace DesertPaintCodex.Models
 
            CurrentWeights = new uint[MaxReagents];
 
            UsedQuantity = 0;
 

	
 
            RefreshShouldLog();
 
        }
 

	
...
 
@@ -133,4 +141,6 @@ namespace DesertPaintCodex.Models
 
            CurrentWeights = new uint[MaxReagents];
 
            UsedQuantity = 0;
 

	
 
            RefreshShouldLog();
 
        }
 

	
...
 
@@ -155,4 +165,12 @@ namespace DesertPaintCodex.Models
 
            CurrentWeights = new uint[MaxReagents];
 
            UsedQuantity = 0;
 

	
 
            RefreshShouldLog();
 
        }
 

	
 
        private void RefreshShouldLog()
 
        {
 
            ShouldLog = false;
 
            // (ReagentCount == 5) && (GetReagent(0).Name == "Iron") && (GetReagent(1).Name == "Red Sand") && (GetReagent(2).Name == "Sulfur") && (GetReagent(3).Name == "Carrot") && (GetReagent(4).Name == "Copper");
 
        }
 

	
...
 
@@ -172,4 +190,5 @@ namespace DesertPaintCodex.Models
 
            _reagents[_nextReagentPos - 1] = _invalidReagent;
 
            --_nextReagentPos;
 
            RefreshShouldLog();
 
        }
 

	
...
 
@@ -187,4 +206,5 @@ namespace DesertPaintCodex.Models
 
                ++CatalystCount;
 
            }
 
            RefreshShouldLog();
 
        }
 

	
...
 
@@ -236,5 +256,9 @@ namespace DesertPaintCodex.Models
 
            }
 
            UsedQuantity = 0;
 
            uint remainingReagents = ((uint)_nextReagentPos - CatalystCount);
 
            if (ShouldLog)
 
            {
 
                WriteLog?.Invoke($" == initializing {this} @ quantity {CurrentTargetQuantity} with {CatalystCount} catalysts ==");
 
            }
 
            uint remainingReagents = ((uint)_nextReagentPos); // Remaining reagents, including catalysts
 
            uint remainingWeight = CurrentTargetQuantity - CatalystCount;
 
            for (int i = 0; i < _nextReagentPos; ++i)
...
 
@@ -247,4 +271,9 @@ namespace DesertPaintCodex.Models
 
                    CurrentWeights[i] = 1;
 
                    ++UsedQuantity;
 
                    // This takes quantity but not weight (concentration)
 
                    if (ShouldLog)
 
                    {
 
                        WriteLog?.Invoke($"    + 1 {reagent.Name} (catalyst)");
 
                    }
 
                }
 
                else
...
 
@@ -257,4 +286,8 @@ namespace DesertPaintCodex.Models
 
                    uint weight = Math.Min(remainingWeight - (remainingReagents-1), reagentMaxWeight);
 
                    //Console.WriteLine("Init reagent {0} weight {1}", reagent.Name, weight);
 
                    if (ShouldLog)
 
                    {
 
                        WriteLog?.Invoke($"    + {weight} {reagent.Name} (remain: weight={remainingWeight} reagents={remainingReagents})");
 
                    }
 
                    remainingWeight -= weight;
 
                    CurrentWeights[i] = weight;
...
 
@@ -263,4 +296,5 @@ namespace DesertPaintCodex.Models
 
                --remainingReagents;
 
            }
 
            RefreshShouldLog();
 
        }
 

	
...
 
@@ -433,6 +467,23 @@ namespace DesertPaintCodex.Models
 
                }
 
            }
 
            RefreshShouldLog();
 
            return success;
 
        }        
 
        }
 

	
 
        public override string ToString()
 
        {
 
            if (_nextReagentPos == 0)
 
            {
 
                return "No ingredients";
 
            }
 
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
 
            sb.Append(_costSortedReagents[(int)_reagents[0]].Name);
 
            for (int idx = 1; idx < _nextReagentPos; ++idx)
 
            {
 
                Reagent reagent = _costSortedReagents[(int)_reagents[idx]];
 
                sb.Append($", {reagent.Name}");
 
            }
 
            return sb.ToString();
 
        }
 
    }
 
}
...
 
\ No newline at end of file
Models/Settings.cs
Show inline comments
...
 
@@ -18,6 +18,7 @@ namespace DesertPaintCodex.Models
 
        {
 
            value = false;
 
            return _settings.TryGetValue(key.ToLower(), out string? valStr)
 
                && bool.TryParse(valStr, out value);
 
            bool found = _settings.TryGetValue(key.ToLower(), out string? valStr);
 
            found = found && bool.TryParse(valStr, out value);
 
            return found;
 

	
 
        }
Services/RecipeGenerator.cs
Show inline comments
...
 
@@ -94,4 +94,33 @@ namespace DesertPaintCodex.Services
 
        }
 

	
 
        public void FlushLog()
 
        {
 
            _log?.Flush();
 
        }
 

	
 
        public void CloseLog()
 
        {
 
            _log?.Close();
 
            _log = null;
 
        }
 

	
 
        private void WriteLog(string msg)
 
        {
 
            if (_log == null) return;
 
            lock (_workerLock)
 
            {
 
                _log.WriteLine(msg);
 
            }
 
        }
 

	
 
        private void WriteLog(string msg, params object[] args)
 
        {
 
            if (_log == null) return;
 
            lock(_workerLock)
 
            {
 
                _log.WriteLine(msg, args);
 
            }
 
        }
 

	
 
        private class ReagentCostSort : IComparer<Reagent>
 
        {
...
 
@@ -166,4 +195,6 @@ namespace DesertPaintCodex.Services
 
            // 1) 1-ingredient recipes @ min concentration for all enabled ingredients with a count >= min concentration
 
            // 2) any previously-generated recipes
 
            WriteLog($"===================== BEGIN RECIPE GENERATION ========================== {DateTime.Now}");
 
            WriteLog("Pre-populating basic recipes.");
 
            int enabledReagentCount = 0;
 
            PaintRecipe recipe = new();
...
 
@@ -197,4 +228,5 @@ namespace DesertPaintCodex.Services
 
                    MaxReagents       = maxReagents
 
                };
 
                initialNode.WriteLog = WriteLog;
 

	
 
                if (MinReagents > 1)
...
 
@@ -206,4 +238,5 @@ namespace DesertPaintCodex.Services
 
                        //Console.WriteLine("Initial node at size {0}/{1} with recipe: {2}", initialNode.ReagentCount, minReagents, initialNode.TestRecipe.ToString());
 
                        RecipeSearchNode searchNode = new RecipeSearchNode(initialNode);
 
                        searchNode.WriteLog = WriteLog;
 
                        _searchQueue.Enqueue(searchNode);
 
                    }
...
 
@@ -217,5 +250,5 @@ namespace DesertPaintCodex.Services
 
            _recipeCount = 0;
 

	
 
            _log?.WriteLine("Begin recipe generation: MaxConcentration={0} MinReagents={1} MaxReagents={2} FullQuantity={3} FullQuantityDepth={4}", MaxConcentration, MinReagents, MaxReagents, FullQuantity, FullQuantityDepth);
 
            WriteLog("Begin recipe generation: MaxConcentration={0} MinReagents={1} MaxReagents={2} FullQuantity={3} FullQuantityDepth={4}", MaxConcentration, MinReagents, MaxReagents, FullQuantity, FullQuantityDepth);
 

	
 
            // start worker threads to do the actual work
...
 
@@ -233,5 +266,5 @@ namespace DesertPaintCodex.Services
 
            _requestCancel = false;
 

	
 
            _log?.WriteLine("Resuming recipe generation: pre-threads={0} reagent count={1} search queue={2}", _runningThreads, _costSortedReagents.Count, _searchQueue.Count);
 
            WriteLog("Resuming recipe generation: pre-threads={0} reagent count={1} search queue={2}", _runningThreads, _costSortedReagents.Count, _searchQueue.Count);
 
            _runningThreads = 0; // presumably!
 

	
...
 
@@ -243,4 +276,5 @@ namespace DesertPaintCodex.Services
 
            _generatorThreads.Clear();
 
            Console.WriteLine("Starting {0} generator threads.", threadCount);
 
            WriteLog($"===== Starting {threadCount} threads.");
 
            for (int i = 0; i < threadCount; ++i)
 
            {
...
 
@@ -382,4 +416,5 @@ namespace DesertPaintCodex.Services
 
                                    MaxConcentration  = MaxConcentration
 
                                };
 
                                node.WriteLog = WriteLog;
 
                                success                = success && node.LoadState(reader);
 
                                if (success)
...
 
@@ -421,8 +456,10 @@ namespace DesertPaintCodex.Services
 
                    ok = _searchQueue.TryDequeue(out node);
 
                }
 
                
 
                if (!ok) continue;
 

	
 
                if (!ok) break;
 
                
 
                Debug.Assert(node != null);
 

	
 
                node.WriteLog = WriteLog;
 
                
 
                if (Mode == SearchType.DepthFirst)
...
 
@@ -433,4 +470,8 @@ namespace DesertPaintCodex.Services
 
                    do {
 
                        --targetQuantity;
 
                        if (node.ShouldLog)
 
                        {
 
                            WriteLog($" == Initializing {node} for quantity {targetQuantity}");
 
                        }
 
                        node.InitForQuantity(targetQuantity);
 
                    } while (targetQuantity > MinConcentration && (node.CurrentTargetQuantity != node.UsedQuantity));
...
 
@@ -450,4 +491,8 @@ namespace DesertPaintCodex.Services
 
                    do {
 
                        ++targetQuantity;
 
                        if (node.ShouldLog)
 
                        {
 
                            WriteLog($" == Initializing {node} for quantity {targetQuantity}");
 
                        }
 
                        node.InitForQuantity(targetQuantity);
 
                    } while ((targetQuantity < quantityLimit) && (node.CurrentTargetQuantity != node.UsedQuantity));
...
 
@@ -463,5 +508,5 @@ namespace DesertPaintCodex.Services
 
                    _searchQueue.Enqueue(node);
 
                }
 
            } while (!_requestCancel && ok);
 
            } while (!_requestCancel);
 

	
 
            bool done;
...
 
@@ -490,15 +535,27 @@ namespace DesertPaintCodex.Services
 
            lock (_workerLock)
 
            {
 
                uint newCost = recipe.Cost;
 
                if (_recipeCosts.TryGetValue(colorName, out var cost))
 
                {
 
                    if (cost <= recipe.Cost) return;
 
                    if (cost < newCost)
 
                    {
 
                        // WriteLog($"Skipping recipe (cost {newCost} > {cost}): {recipe}");
 
                        return;
 
                    }
 
                    PaintRecipe origRecipe = _recipes[colorName];
 
                    if (cost == newCost && recipe.Concentration >= origRecipe.Concentration)
 
                    {
 
                        // Same cost, greater concentration - use the lower concentration recipe
 
                        WriteLog($"Skipping recipe (cost {newCost}, {recipe.Concentration} >= {origRecipe.Concentration}): {recipe}");
 
                        return;
 
                    }
 
                        
 
                    _recipes[colorName].CopyFrom(recipe);
 
                    _recipeCosts[colorName] = recipe.Cost;
 
                    _log?.WriteLine("New recipe (cost {0}): {1}", recipe.Cost, recipe);
 
                    _recipes[colorName].CopyFrom(recipe); // Copy recipe because the one passed in should be const and will get overwritten
 
                    _recipeCosts[colorName] = newCost;
 
                    WriteLog($"Replacing recipe (cost {newCost} < {cost}): {recipe}");
 
                        
 
                    if (NewRecipe == null) return;
 
                        
 
                    NewRecipeEventArgs args = new(colorName, recipe);
 
                    NewRecipeEventArgs args = new(colorName, _recipes[colorName]);
 
                    NewRecipe(this, args);
 
                }
...
 
@@ -506,10 +563,13 @@ namespace DesertPaintCodex.Services
 
                {
 
                    // This would be an error!
 
                    _recipeCosts.Add(colorName, recipe.Cost);
 
                    _recipes.Add(colorName, new PaintRecipe(recipe));
 
                        
 
                    _recipeCosts.Add(colorName, newCost);
 
                    PaintRecipe newRecipe = new PaintRecipe(recipe); // Copy recipe because the one passed in should be const and will get overwritten
 
                    _recipes.Add(colorName, newRecipe);
 

	
 
                    WriteLog($"New recipe (cost {newCost}): {recipe}");
 

	
 
                    if (NewRecipe == null) return;
 
                        
 
                    NewRecipeEventArgs args = new(colorName, recipe);
 
                    NewRecipeEventArgs args = new(colorName, newRecipe);
 
                    NewRecipe(this, args);
 
                }
...
 
@@ -602,4 +662,6 @@ namespace DesertPaintCodex.Services
 
            }
 

	
 
            string origNodeVal = node.ToString();
 

	
 
            // Try next quantity
 
            uint newQuantity;
...
 
@@ -609,9 +671,17 @@ namespace DesertPaintCodex.Services
 
                //Console.WriteLine("Try quantity {0}", newQuantity);
 
                if (newQuantity > quantityLimit) continue;
 
                
 

	
 
                if (node.ShouldLog)
 
                {
 
                    WriteLog($" == Initializing {node} for quantity {newQuantity}");
 
                }
 
                node.InitForQuantity(newQuantity);
 
                if (node.CurrentTargetQuantity <= node.UsedQuantity)
 
                {
 
                    //if (log != null) { lock(log) { log.WriteLine("Update quantity to {0}", node.CurrentTargetQuantity); } }
 
                    if (node.ShouldLog)
 
                    {
 
                        WriteLog($"  == {node} quantity {newQuantity}");
 
                    }
 
                    return true;
 
                }
...
 
@@ -619,4 +689,15 @@ namespace DesertPaintCodex.Services
 

	
 
            bool ok = NextReagentSetBreadthFirst(node, node.MinReagents, node.MaxReagents);
 
            if (node.ShouldLog)
 
            {
 
                if (ok)
 
                {
 
                    WriteLog($"==== {origNodeVal} next reagent set: {node} @ {node.CurrentTargetQuantity}");
 
                }
 
                else
 
                {
 
                    WriteLog($"==== done with reagent set: {origNodeVal}");
 
                }
 
            }
 
            return ok;
 
        }
...
 
@@ -630,4 +711,8 @@ namespace DesertPaintCodex.Services
 
            // if at end, pop reagent
 
            //Console.WriteLine("Finding new recipe after quantity {0}/{1} used {2}", newQuantity, node.MaxConcentration, node.UsedQuantity);
 
            if (node.ShouldLog)
 
            {
 
                WriteLog($" == Initializing {node} for quantity {node.MinConcentration + node.CatalystCount}");
 
            }
 
            node.InitForQuantity(node.MinConcentration + node.CatalystCount); // reset quantity
 
            int currentDepth = node.ReagentCount;
...
 
@@ -692,4 +777,5 @@ namespace DesertPaintCodex.Services
 
                    //Console.WriteLine("Catalysts: {0} Reagents: {1} Min: {2}", node.CatalystCount, node.ReagentCount, node.MinReagents);
 
                } while ((node.CatalystCount >= node.ReagentCount) && (node.ReagentCount >= minReagents)); // make sure to skip all-catalyst combinations
 

	
 
                if (recipeFound)
 
                {
...
 
@@ -703,5 +789,12 @@ namespace DesertPaintCodex.Services
 
            } while (currentDepth <= maxReagents);
 

	
 
            if (!recipeFound) return false;
 
            if (!recipeFound)
 
            {
 
                if (node.ShouldLog)
 
                {
 
                    WriteLog($" == no more recipes for {node}");
 
                }
 
                return false;
 
            }
 
            
 
            node.InitForQuantity(node.MinConcentration+node.CatalystCount); // minimum quantity for this recipe
...
 
@@ -726,4 +819,8 @@ namespace DesertPaintCodex.Services
 
                node.TestRecipe.AddReagent(node.GetReagent(i).Name, node.CurrentWeights[i]);
 
            }
 
            if (node.ShouldLog)
 
            {
 
                WriteLog($"   -> {node.TestRecipe}");
 
            }
 
            AddCheapestRecipe(node.TestRecipe);
 
            //if (log != null) { lock(log) { log.WriteLine("Tested recipe: {0}", node.TestRecipe); } }
...
 
@@ -738,4 +835,8 @@ namespace DesertPaintCodex.Services
 
                // not possible to make a valid recipe
 
                //Console.WriteLine("Insufficient remaining weight");
 
                if (node.ShouldLog)
 
                {
 
                    WriteLog($" ***** Not enough remaining weight in recipe {node} at target quantity {node.CurrentTargetQuantity} catalysts {node.CatalystCount} min concentration {MinConcentration}");
 
                }
 
                return false;
 
            }
...
 
@@ -746,4 +847,5 @@ namespace DesertPaintCodex.Services
 
            uint spaceBelow = 0;
 
            int reagentsBelow = 0;
 
            // Start from the end of the current reagent list and work to the front
 
            for (int i = (int)depth-1 ; i >= 0; --i)
 
            {
...
 
@@ -823,4 +925,14 @@ namespace DesertPaintCodex.Services
 
        }
 

	
 
        public void ResetQueue()
 
        {
 
            lock (_workerLock)
 
            {
 
                // Don't reset the queue ever while running
 
                if (_running) return;
 
                _searchQueue.Clear();
 
            }
 
        }
 

	
 
        public void Reset()
 
        {
ViewModels/RecipeGeneratorViewModel.cs
Show inline comments
...
 
@@ -127,6 +127,6 @@ namespace DesertPaintCodex.ViewModels
 
            if (logGenerator)
 
            {
 
                string logDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
 
                _generator.Log = System.IO.Path.Combine(logDir, "dpl_generator.txt");
 
                string logDir = DesertPaintCodex.Util.FileUtils.AppDataPath;
 
                _generator.Log = System.IO.Path.Combine(logDir, "recipe_generator_log.txt");
 
            }
 
            
...
 
@@ -154,5 +154,13 @@ namespace DesertPaintCodex.ViewModels
 
            _updateTimer.Start();
 
            SelectedView = 1;
 
            
 

	
 
            _generator.CloseLog();
 
            SettingsService.Get("Generator.Logging", out bool logGenerator, false);
 
            if (logGenerator)
 
            {
 
                string logDir = DesertPaintCodex.Util.FileUtils.AppDataPath;
 
                _generator.Log = System.IO.Path.Combine(logDir, "recipe_generator_log.txt");
 
            }
 

	
 
            _generator.BeginRecipeGeneration(
 
                _minConcentration, 
...
 
@@ -172,5 +180,7 @@ namespace DesertPaintCodex.ViewModels
 
            _updateTimer.Stop();
 
            _profile.SaveRecipes();
 
            
 
            _generator.ResetQueue();
 
            SaveState();
 

	
 
            CanClear = true;
 
        }
...
 
@@ -190,5 +200,13 @@ namespace DesertPaintCodex.ViewModels
 
            _updateTimer.Start();
 
            SelectedView = 1;
 
            
 

	
 
            _generator.CloseLog();
 
            SettingsService.Get("Generator.Logging", out bool logGenerator, false);
 
            if (logGenerator)
 
            {
 
                string logDir = DesertPaintCodex.Util.FileUtils.AppDataPath;
 
                _generator.Log = System.IO.Path.Combine(logDir, "recipe_generator_log.txt");
 
            }
 

	
 
            _generator.ResumeRecipeGeneration();
 

	
...
 
@@ -303,8 +321,8 @@ namespace DesertPaintCodex.ViewModels
 
        private void OnGeneratorStopped(object? sender, EventArgs args)
 
        {
 
            SaveState();
 
            
 
            if (_saving)
 
            {
 
                SaveState();
 

	
 
                _generator.ResumeRecipeGeneration();
 
                _lastSave = DateTime.Now;
...
 
@@ -313,7 +331,15 @@ namespace DesertPaintCodex.ViewModels
 
            }
 

	
 
            if (IsPaused) return;
 
            _generator.FlushLog();
 

	
 
            if (IsPaused)
 
            {
 
                SaveState();
 
                _generator.CloseLog();
 
                return;
 
            }
 

	
 
            End();
 
            _generator.CloseLog();
 
        }
 

	
0 comments (0 inline, 0 general)