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 232 insertions and 21 deletions:
0 comments (0 inline, 0 general)
Models/PaintRecipe.cs
Show inline comments
...
 
@@ -267,16 +267,37 @@ namespace DesertPaintCodex.Models
 

	
 
                    Reagent reagent = ReagentService.GetReagent(reagentName);
 
                    cost += (reagent.Cost * ingredient.Quantity);
 
                }
 
                return cost;
 
            }
 
        }
 

	
 
        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)
 
        {
 
            uint cost = 0;
 
            foreach (ReagentQuantity ingredient in _recipe)
 
            {
 
                string reagentName = ingredient.Name;
 
                if (string.IsNullOrEmpty(reagentName))
 
                {
Models/RecipeSearchNode.cs
Show inline comments
...
 
@@ -22,16 +22,20 @@ namespace DesertPaintCodex.Models
 
        public uint CurrentTargetQuantity { get; set; }
 
        public uint MaxConcentration { get; set; }
 
        public uint UsedQuantity { get; private set; }
 
        public uint CatalystCount { get; set; }
 
        public uint FullQuantityDepth { get; set; }
 
        public uint FullQuantity { get; set; }
 
        public uint MinReagents { get; set; }
 

	
 
        public bool ShouldLog { get; private set; }
 

	
 
        public Action<string>? WriteLog = null;
 

	
 
        private uint _maxReagents;
 
        public uint MaxReagents
 
        {
 
            get => _maxReagents;
 
            set
 
            {
 
                _maxReagents = value;
 
                CurrentWeights = new uint[_maxReagents];
...
 
@@ -66,16 +70,18 @@ namespace DesertPaintCodex.Models
 
            FullQuantity = other.FullQuantity;
 
            MinReagents = other.MinReagents;
 
            MaxReagents = other.MaxReagents;
 
            CurrentWeights = new uint[MaxReagents];
 
            for (int i = 0; i < MaxReagents; ++i)
 
            {
 
                CurrentWeights[i] = other.CurrentWeights[i];
 
            }
 

	
 
            RefreshShouldLog();
 
        }
 

	
 
        public RecipeSearchNode(List<Reagent> costSortedReagents, uint[] reagents)
 
        {
 
            _costSortedReagents = new List<Reagent>(costSortedReagents);
 
            _reagents = new uint[costSortedReagents.Count];
 
            _invalidReagent = (uint)costSortedReagents.Count;
 
            _nextReagentPos = reagents.Length;
...
 
@@ -103,16 +109,18 @@ namespace DesertPaintCodex.Models
 
                    _reagentInUse[reagentIdx] = true;
 
                }
 
            }
 

	
 
            MinReagents = (uint) _nextReagentPos;
 
            MaxReagents = (uint) _nextReagentPos;
 
            CurrentWeights = new uint[MaxReagents];
 
            UsedQuantity = 0;
 

	
 
            RefreshShouldLog();
 
        }
 

	
 
        // top-level search
 
        public RecipeSearchNode(List<Reagent> costSortedReagents, uint startReagent)
 
        {
 
            _costSortedReagents = new List<Reagent>(costSortedReagents);
 
            _reagents = new uint[costSortedReagents.Count];
 
            _invalidReagent = (uint)costSortedReagents.Count;
...
 
@@ -127,16 +135,18 @@ namespace DesertPaintCodex.Models
 
                _reagentInUse[reagentIdx] = false;
 
            }
 
            _reagents[_nextReagentPos++] = NextFreeReagent(startReagent);
 
            //Console.WriteLine("Added reagent {0} at pos {1}", this.reagents[nextReagentPos-1], nextReagentPos-1);
 
            MinReagents = 1; // don't iterate up beyond the start reagent
 
            MaxReagents = 1;
 
            CurrentWeights = new uint[MaxReagents];
 
            UsedQuantity = 0;
 

	
 
            RefreshShouldLog();
 
        }
 

	
 
        public RecipeSearchNode(List<Reagent> costSortedReagents)
 
        {
 
            _costSortedReagents = costSortedReagents;
 
            _reagents = new uint[costSortedReagents.Count];
 
            _invalidReagent = (uint)costSortedReagents.Count;
 
            _nextReagentPos = 0;
...
 
@@ -149,16 +159,24 @@ namespace DesertPaintCodex.Models
 
            {
 
                _reagentInUse[reagentIdx] = false;
 
            }
 
            _reagents[_nextReagentPos++] = NextFreeReagent(0);
 
            MinReagents = 0;
 
            MaxReagents = 1;
 
            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");
 
        }
 

	
 
        public Reagent GetReagent(int idx)
 
        {
 
            return _costSortedReagents[(int)_reagents[idx]];
 
        }
 

	
 
        public void RemoveLastReagent()
...
 
@@ -166,31 +184,33 @@ namespace DesertPaintCodex.Models
 
            uint reagentIdx = _reagents[_nextReagentPos - 1];
 
            ReleaseReagent(reagentIdx);
 
            if (_costSortedReagents[(int)reagentIdx].IsCatalyst)
 
            {
 
                --CatalystCount;
 
            }
 
            _reagents[_nextReagentPos - 1] = _invalidReagent;
 
            --_nextReagentPos;
 
            RefreshShouldLog();
 
        }
 

	
 
        public void ReplaceLastReagent(uint reagentIdx)
 
        {
 
            uint oldReagentIdx = _reagents[_nextReagentPos - 1];
 
            ReleaseReagent(oldReagentIdx);
 
            _reagents[_nextReagentPos - 1] = reagentIdx;
 
            if (_costSortedReagents[(int)oldReagentIdx].IsCatalyst)
 
            {
 
                --CatalystCount;
 
            }
 
            if (_costSortedReagents[(int)reagentIdx].IsCatalyst)
 
            {
 
                ++CatalystCount;
 
            }
 
            RefreshShouldLog();
 
        }
 

	
 
        public uint NextFreeReagent(uint startIdx)
 
        {
 
            uint idx = startIdx;
 
            for (; idx < _costSortedReagents.Count; ++idx)
 
            {
 
                bool inUse = _reagentInUse[idx];
...
 
@@ -230,43 +250,57 @@ namespace DesertPaintCodex.Models
 
            //System.Console.WriteLine("Init for quantity: {0}, reagent count: {1} ({2} catalysts)", quantity, ReagentCount, CatalystCount);
 
            CurrentTargetQuantity = quantity;
 
            if (CurrentTargetQuantity < (MinConcentration + CatalystCount))
 
            {
 
                // invalid quantity
 
                return;
 
            }
 
            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)
 
            {
 
                Reagent reagent = GetReagent(i);
 

	
 
                if (reagent.IsCatalyst)
 
                {
 
                    //Console.WriteLine("Init catalyst {0} weight 1", reagent.Name);
 
                    CurrentWeights[i] = 1;
 
                    ++UsedQuantity;
 
                    // This takes quantity but not weight (concentration)
 
                    if (ShouldLog)
 
                    {
 
                        WriteLog?.Invoke($"    + 1 {reagent.Name} (catalyst)");
 
                    }
 
                }
 
                else
 
                {
 
                    uint reagentMaxWeight = reagent.RecipeMax;
 
                    if (ReagentCount <= FullQuantityDepth)
 
                    {
 
                        reagentMaxWeight = Math.Max(FullQuantity, reagentMaxWeight);
 
                    }
 
                    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;
 
                    UsedQuantity += weight;
 
                }
 
                --remainingReagents;
 
            }
 
            RefreshShouldLog();
 
        }
 

	
 
        public void SetWeight(int idx, uint quantity)
 
        {
 
            UsedQuantity -= CurrentWeights[idx];
 
            CurrentWeights[idx] = quantity;
 
            UsedQuantity += quantity;
 
        }
...
 
@@ -427,12 +461,29 @@ namespace DesertPaintCodex.Models
 
                    }
 
                }
 
                else
 
                {
 
                    success = false;
 
                    break;
 
                }
 
            }
 
            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
...
 
@@ -12,18 +12,19 @@ namespace DesertPaintCodex.Models
 
        {
 
            value = 0;
 
            return _settings.TryGetValue(key.ToLower(), out string? valStr)
 
                && int.TryParse(valStr, out value);
 
        }
 
        public bool TryGet(string key, out bool value)
 
        {
 
            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;
 

	
 
        }
 
        public void Set(string key, int value)
 
        {
 
            _settings[key.ToLower()] = value.ToString();
 
        }
 
        public void Set(string key, bool value)
 
        {
Services/RecipeGenerator.cs
Show inline comments
...
 
@@ -88,16 +88,45 @@ namespace DesertPaintCodex.Services
 

	
 
        public bool CanResume => (!_running && (_searchQueue.Count > 0));
 

	
 
        public string? Log
 
        {
 
            set => _log = value != null ? new StreamWriter(value) : null;
 
        }
 

	
 
        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>
 
        {
 
            public int Compare(Reagent? reagent1, Reagent? reagent2)
 
            {
 
                if (reagent1 == null)
 
                {
 
                    if (reagent2 == null) return 0;
 
                    return -1;
...
 
@@ -160,16 +189,18 @@ namespace DesertPaintCodex.Services
 
            // first, sort reagents by cost.
 
            InitSortedReagents();
 

	
 
            _totalReagents = (uint)_costSortedReagents.Count;
 

	
 
            // Pre-populate recipes list with:
 
            // 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();
 
            foreach (var reagent in _costSortedReagents.Where(reagent => reagent.Enabled))
 
            {
 
                if (!reagent.IsCatalyst && ((reagent.RecipeMax >= minConcentration) || ((FullQuantityDepth > 0) && (FullQuantity >= minConcentration))))
 
                {
 
                    recipe.Clear();
 
                    recipe.AddReagent(reagent.Name, minConcentration);
...
 
@@ -191,62 +222,65 @@ namespace DesertPaintCodex.Services
 
                {
 
                    FullQuantity      = FullQuantity,
 
                    FullQuantityDepth = FullQuantityDepth,
 
                    MinConcentration  = minConcentration,
 
                    MaxConcentration  = maxConcentration,
 
                    MinReagents       = minReagents,
 
                    MaxReagents       = maxReagents
 
                };
 
                initialNode.WriteLog = WriteLog;
 

	
 
                if (MinReagents > 1)
 
                {
 
                    while (NextReagentSetBreadthFirst(initialNode, 1, minReagents))
 
                    {
 
                        if (initialNode.ReagentCount != minReagents) continue;
 
                        
 
                        //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);
 
                    }
 
                }
 
                else
 
                {
 
                    _searchQueue.Enqueue(initialNode);
 
                }
 
            }
 

	
 
            _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
 
            ResumeRecipeGeneration();
 
        }
 

	
 
        public void ResumeRecipeGeneration()
 
        {
 
            if (_running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 
            _running = true;
 
            _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!
 

	
 
            int threadCount = Math.Min(Math.Min(_costSortedReagents.Count, _searchQueue.Count), (int)MaxThreads);
 
            if (threadCount == 0)
 
            {
 
                Finished?.Invoke(this, EventArgs.Empty);
 
            }
 
            _generatorThreads.Clear();
 
            Console.WriteLine("Starting {0} generator threads.", threadCount);
 
            WriteLog($"===== Starting {threadCount} threads.");
 
            for (int i = 0; i < threadCount; ++i)
 
            {
 
                Thread thr = new(Generate) {Priority = ThreadPriority.BelowNormal};
 
                _generatorThreads.Add(thr);
 
            }
 
            foreach (Thread thr in _generatorThreads)
 
            {
 
                thr.Start();
...
 
@@ -376,16 +410,17 @@ namespace DesertPaintCodex.Services
 
                                RecipeSearchNode node = new(_costSortedReagents)
 
                                {
 
                                    FullQuantity      = FullQuantity,
 
                                    FullQuantityDepth = FullQuantityDepth,
 
                                    MinReagents       = MinReagents,
 
                                    MaxReagents       = MaxReagents,
 
                                    MaxConcentration  = MaxConcentration
 
                                };
 
                                node.WriteLog = WriteLog;
 
                                success                = success && node.LoadState(reader);
 
                                if (success)
 
                                {
 
                                    _searchQueue.Enqueue(node);
 
                                }
 
                            }
 
                            break;
 
                        case "SearchType":
...
 
@@ -416,27 +451,33 @@ namespace DesertPaintCodex.Services
 
            do
 
            {
 
                RecipeSearchNode? node;
 
                lock (_workerLock)
 
                {
 
                    ok = _searchQueue.TryDequeue(out node);
 
                }
 
                
 
                if (!ok) continue;
 
                if (!ok) break;
 
                
 
                Debug.Assert(node != null);
 
                
 
                node.WriteLog = WriteLog;
 
                
 
                if (Mode == SearchType.DepthFirst)
 
                {
 
                    uint targetQuantity = (node.ReagentCount <= FullQuantityDepth)
 
                        ? ((uint)node.ReagentCount * FullQuantity) 
 
                        : node.MaxConcentration + 1;
 
                    do {
 
                        --targetQuantity;
 
                        if (node.ShouldLog)
 
                        {
 
                            WriteLog($" == Initializing {node} for quantity {targetQuantity}");
 
                        }
 
                        node.InitForQuantity(targetQuantity);
 
                    } while (targetQuantity > MinConcentration && (node.CurrentTargetQuantity != node.UsedQuantity));
 
    
 
                    while ((ok = IterateDepthFirst(node)) && !_requestCancel)
 
                    {
 
                        Progress?.Invoke(this, EventArgs.Empty);
 
                    }
 
                }
...
 
@@ -444,30 +485,34 @@ namespace DesertPaintCodex.Services
 
                {
 
                    // breadth-first search
 
                    uint targetQuantity = MinConcentration - 1;
 
                    uint quantityLimit = (node.ReagentCount <= FullQuantityDepth) 
 
                        ? (FullQuantity * (uint)node.ReagentCount) 
 
                        : node.MaxConcentration;
 
                    do {
 
                        ++targetQuantity;
 
                        if (node.ShouldLog)
 
                        {
 
                            WriteLog($" == Initializing {node} for quantity {targetQuantity}");
 
                        }
 
                        node.InitForQuantity(targetQuantity);
 
                    } while ((targetQuantity < quantityLimit) && (node.CurrentTargetQuantity != node.UsedQuantity));
 
    
 
                    while ((ok = IterateBreadthFirst(node)) && !_requestCancel)
 
                    {
 
                        Progress?.Invoke(this, EventArgs.Empty);
 
                    }
 
                }
 
                if (ok)
 
                {
 
                    // stopped because cancel was requested - requeue the node in its current state for resume
 
                    _searchQueue.Enqueue(node);
 
                }
 
            } while (!_requestCancel && ok);
 
            } while (!_requestCancel);
 

	
 
            bool done;
 
            lock(_workerLock)
 
            {
 
                --_runningThreads;
 
                //generatorThreads.Remove(Thread.CurrentThread);
 

	
 
                done = (_runningThreads == 0);
...
 
@@ -484,38 +529,53 @@ namespace DesertPaintCodex.Services
 
        // returns the discarded recipe from the pair (or null if no original recipe to replace)
 
        private void AddCheapestRecipe(PaintRecipe recipe)
 
        {
 
            if (!recipe.IsValidForConcentration(MinConcentration)) return;
 
            
 
            string colorName = PaletteService.FindNearest(recipe.ReactedColor);
 
            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);
 
                }
 
                else
 
                {
 
                    // 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);
 
                }
 
            }
 
        }
 

	
 
        private bool IterateDepthFirst(RecipeSearchNode node)
 
        {
 
            TestCurrentRecipe(node);
...
 
@@ -596,44 +656,69 @@ namespace DesertPaintCodex.Services
 

	
 
            // search all quantities of current recipe
 
            if (NextRecipe(node))
 
            {
 
                //System.Console.WriteLine("Found next recipe at size {0} qty {1}", node.ReagentCount, node.CurrentTargetQuantity);
 
                return true;
 
            }
 

	
 
            string origNodeVal = node.ToString();
 

	
 
            // Try next quantity
 
            uint newQuantity;
 
            uint quantityLimit = ((uint)node.ReagentCount <= FullQuantityDepth) ? ((uint)node.ReagentCount * FullQuantity) : node.MaxConcentration;
 
            do {
 
                newQuantity = node.CurrentTargetQuantity + 1;
 
                //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;
 
                }
 
            } while (newQuantity < quantityLimit);
 

	
 
            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;
 
        }
 

	
 
        private bool NextReagentSetBreadthFirst(RecipeSearchNode node, uint minReagents, uint maxReagents)
 
        {
 
            // search all variants at this depth of recipe
 
            // increase recipe depth
 

	
 
            // next reagent in last position
 
            // 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;
 
            bool recipeFound;
 
            do {
 
                //Console.WriteLine("Current depth: {0}/{1}", currentDepth, node.MaxReagents);
 
                do {
 
                    recipeFound = false;
 
                    
...
 
@@ -686,28 +771,36 @@ namespace DesertPaintCodex.Services
 
                            {
 
                                //if (log != null) { lock(log) { log.WriteLine("Failed to add reagent {0}/{1}", node.ReagentCount+1, currentDepth); } }
 
                                recipeFound = false;
 
                            }
 
                        }
 
                    }
 
                    //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)
 
                {
 
                    break;
 
                }
 
                else
 
                {
 
                    ++currentDepth;
 
                    //if (log != null) { lock(log) { log.WriteLine("Increased depth to {0}/{1} [no recipe]", currentDepth, node.MaxReagents); } }
 
                }
 
            } 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
 
            
 
            node.TestRecipe ??= new PaintRecipe();
 
            node.TestRecipe.Clear();
 
            
 
            for (int i = 0; i < node.ReagentCount; ++i)
 
            {
...
 
@@ -720,36 +813,45 @@ namespace DesertPaintCodex.Services
 
        private void TestCurrentRecipe(RecipeSearchNode node)
 
        {
 
            node.TestRecipe ??= new PaintRecipe();
 
            node.TestRecipe.Clear();
 
            for (int i = 0; i < node.ReagentCount; ++i)
 
            {
 
                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); } }
 
        }
 

	
 
        private bool NextRecipe(RecipeSearchNode node)
 
        {
 
            // check for the next recipe
 
            uint remainingWeight = node.CurrentTargetQuantity - node.CatalystCount;
 
            if (remainingWeight < MinConcentration)
 
            {
 
                // 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;
 
            }
 
            //uint remainingReagents = (uint)node.Reagents.Count - node.CatalystCount;
 

	
 
            uint depth = (uint)node.ReagentCount;
 
            uint weightToConsume = 0;
 
            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)
 
            {
 
                uint currentWeight = node.CurrentWeights[i];
 

	
 
                if ((spaceBelow >= (weightToConsume+1)) && (currentWeight > 1))
 
                {
 
                    // reduce this node by 1, allocate remaining weight to reagents below it
 
                    node.SetWeight(i, currentWeight-1);
...
 
@@ -817,16 +919,26 @@ namespace DesertPaintCodex.Services
 
            _generatorThreads.Clear();
 
        }
 

	
 
        public void Stop()
 
        {
 
            _requestCancel = true;
 
        }
 

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

	
 
        public void Reset()
 
        {
 
            foreach (PaintRecipe recipe in _recipes.Values)
 
            {
 
                recipe.Clear();
 
            }
 
            foreach (string key in _recipeCosts.Keys)
 
            {
ViewModels/RecipeGeneratorViewModel.cs
Show inline comments
...
 
@@ -121,18 +121,18 @@ namespace DesertPaintCodex.ViewModels
 

	
 
            _generator.Progress  += OnProgress;
 
            _generator.NewRecipe += OnNewRecipe;
 
            _generator.Finished  += OnGeneratorStopped;
 

	
 
            SettingsService.Get("Generator.Logging", out bool logGenerator, false);
 
            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");
 
            }
 
            
 
            if (ribbonMode)
 
            {
 
                InitStateForRibbons();
 
            }
 
            else
 
            {
...
 
@@ -149,16 +149,24 @@ namespace DesertPaintCodex.ViewModels
 
        {
 
            IsInProgress = true;
 
            IsRunning = true;
 
            ReagentService.SaveProfileReagents(_profile.ReagentFile);
 
            SaveSettings(_ribbonMode ? "Ribbon" : "Paint");
 
            _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, 
 
                (uint)MaxConcentration, 
 
                1, 
 
                (uint)MaxReagents, 
 
                (uint)FullQuantityDepth, 
 
                (uint)FullQuantity);
 
        }
...
 
@@ -166,16 +174,18 @@ namespace DesertPaintCodex.ViewModels
 
        
 
        public void End()
 
        {
 
            IsPaused     = false;
 
            IsRunning    = false;
 
            IsInProgress = false;
 
            _updateTimer.Stop();
 
            _profile.SaveRecipes();
 
            _generator.ResetQueue();
 
            SaveState();
 
            
 
            CanClear = true;
 
        }
 

	
 
        public void Pause()
 
        {
 
            IsPaused  = true;
 
            IsRunning = false;
...
 
@@ -185,16 +195,24 @@ namespace DesertPaintCodex.ViewModels
 

	
 
        public void Resume()
 
        {
 
            IsPaused  = false;
 
            IsRunning = true;
 
            _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();
 

	
 
        }
 

	
 
        public async Task Clear()
 
        {
 
            bool result = await ShowYesNoBox("Clear Recipes", "Are you sure you want to clear your recipes?");
 
            if (result)
...
 
@@ -297,29 +315,37 @@ namespace DesertPaintCodex.ViewModels
 
            PaintRecipe recipe = new(args.Recipe);
 
            _pendingNewRecipes.Enqueue(recipe);
 
            _updatesAvailable = true;
 
        }
 
        
 
        
 
        private void OnGeneratorStopped(object? sender, EventArgs args)
 
        {
 
            if (_saving)
 
            {
 
            SaveState();
 
            
 
            if (_saving)
 
            {
 
                _generator.ResumeRecipeGeneration();
 
                _lastSave = DateTime.Now;
 
                _saving = false;
 
                return;
 
            }
 

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

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

	
 
            End();
 
            _generator.CloseLog();
 
        }
 

	
 
        private void Update(object? sender, EventArgs e)
 
        {
 
            if (!_updatesAvailable) return;
 
            if (_saving) return;
 

	
 
            _updatesAvailable = false;
0 comments (0 inline, 0 general)