Changeset - 2c3944282ed8
[Not reviewed]
default
0 2 0
Jason Maltzen (jmaltzen) - 9 years ago 2016-01-23 20:18:05
jason.maltzen@unsanctioned.net
Fixes for starting with a min reagent count > 1
2 files changed with 84 insertions and 28 deletions:
0 comments (0 inline, 0 general)
RecipeGenerator.cs
Show inline comments
 
/*
 
 * Copyright (c) 2015, Jason Maltzen
 

	
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 
 of this software and associated documentation files (the "Software"), to deal
 
 in the Software without restriction, including without limitation the rights
 
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
 copies of the Software, and to permit persons to whom the Software is
 
 furnished to do so, subject to the following conditions:
 

	
 
 The above copyright notice and this permission notice shall be included in
 
 all copies or substantial portions of the Software.
 

	
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
 THE SOFTWARE.
 
*/
 

	
 
using System;
 
using System.IO;
 
using System.Collections.Generic;
 
using System.Collections.Concurrent;
 
using System.Text.RegularExpressions;
 
using System.Threading;
 

	
 
namespace DesertPaintLab
 
{
 
    public class NewRecipeEventArgs : EventArgs
 
    {
 
        string color;
 
        PaintRecipe recipe;
 

	
 
        public NewRecipeEventArgs(string color, PaintRecipe recipe)
 
        {
 
            this.color = color;
 
            this.recipe = recipe;
 
        }
 

	
 
        public string Color
 
        {
 
            get {
 
                return color;
 
            }
 
        }
 

	
 
        public PaintRecipe Recipe
 
        {
 
            get {
 
                return recipe;
 
            }
 
        }
 
    }
 

	
 
    public class RecipeGenerator
 
    {
 
        public enum SearchType {
 
            DEPTH_FIRST,
 
            BREADTH_FIRST
 
        };
 

	
 
        public SearchType Mode { get; set; }
 

	
 
        public uint MaxQuantity { get; private set; } // maximum number of total ingredients
 
        public uint MaxReagents { get; private set; } // maximum number of reagents to use in the recipe
 
        public uint MinReagents { get; private set; } // minimum number of reagents to use in the recipe
 
        public uint FullQuantityDepth { get; private set; } // at or equal this number of reagents, ignore ingredient settings for max quantity
 
        public uint FullQuantity { get; private set; }  // The max number of a reagent to use at full quantity
 

	
 
        public uint MaxThreads { get; set; }
 

	
 
        ReactionSet reactions;
 
        bool running = false;
 

	
 
        int runningThreads = 0;
 
        
 
        SortedDictionary<string, uint> recipeCosts = new SortedDictionary<string, uint>();
 
        SortedDictionary<string, PaintRecipe> recipes = new SortedDictionary<string, PaintRecipe>();
 

	
 
        uint totalReagents;
 

	
 
        List<Reagent> costSortedReagents = new List<Reagent>();
 

	
 
        ConcurrentQueue<RecipeSearchNode> searchQueue = new ConcurrentQueue<RecipeSearchNode>();
 

	
 
        ulong recipeCount = 0;
 

	
 
        List<Thread> generatorThreads = new List<Thread>();
 
        Object workerLock = new Object();
 

	
 
        bool requestCancel = false;
 

	
 
        StreamWriter log;
 

	
 
        // events
 
        public event EventHandler Finished;
 
        public event EventHandler Progress;
 
        public event EventHandler<NewRecipeEventArgs> NewRecipe;
 

	
 
        public RecipeGenerator(ReactionSet reactions)
 
        {
 
            Mode = SearchType.BREADTH_FIRST;
 
            this.reactions = reactions;
 
            foreach (PaintColor color in Palette.Colors)
 
            {
 
                recipes.Add(color.Name, new PaintRecipe());
 
                recipeCosts.Add(color.Name, uint.MaxValue);
 
            }
 
            MinReagents = 1;
 
            MaxReagents = 5;
 
            MaxQuantity = 20;
 
        }
 

	
 
        public SortedDictionary<string, PaintRecipe> Recipes
 
        {
 
            get
 
            {
 
                return recipes;
 
            }
 
        }
 

	
 
        public ulong RecipeCount
 
        {
 
            get
 
            {
 
                return recipeCount;
 
            }
 
        }
 

	
 
        public bool CanResume
 
        {
 
            get
 
            {
 
                return (!running && (searchQueue.Count > 0));
 
            }
 
        }
 

	
 
        public string Log
 
        {
 
            set
 
            {
 
                if (value != null)
 
                {
 
                    log = new StreamWriter(value);
 
                }
 
                else
 
                {
 
                    log = null;
 
                }
 
            }
 
        }
 

	
 
        private class ReagentCostSort : IComparer<Reagent>
 
        {
 
            public int Compare(Reagent reagent1, Reagent reagent2)
 
            {
 
                return (int)reagent1.Cost - (int)reagent2.Cost;
 
            }
 
        }
 

	
 
        public void InitRecipes(SortedDictionary<string, PaintRecipe> initialRecipes)
 
        {
 
            if (running)
 
            {
 
                return;
 
            }
 
            foreach (PaintRecipe recipe in initialRecipes.Values)
 
            {
 
                //PaintRecipe recipeCopy = new PaintRecipe(recipe);
 
                AddCheapestRecipe(recipe);
 
            }
 
        }
 

	
 
        private void InitSortedReagents()
 
        {
 
            costSortedReagents.Clear();
 
            foreach (string name in ReagentManager.Names)
 
            {
 
                Reagent reagent = ReagentManager.GetReagent(name);
 
                costSortedReagents.Add(reagent);
 
            }
 
            costSortedReagents.Sort(new ReagentCostSort());
 
        }
 

	
 
        public void BeginRecipeGeneration(uint maxQuantity, uint minReagents, uint maxReagents, uint fullQuantityDepth, uint fullQuantity)
 
        {
 
            if (running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 

	
 
            this.MaxQuantity = maxQuantity;
 
            this.MinReagents = minReagents;
 
            this.MaxReagents = maxReagents;
 
            this.FullQuantity = fullQuantity;
 
            this.FullQuantityDepth = fullQuantityDepth;
 

	
 
            // first, sort reagents by cost.
 
            InitSortedReagents();
 

	
 
            totalReagents = (uint)costSortedReagents.Count;
 

	
 
            // Pre-populate recipes list with:
 
            // 1) 1-ingredient recipes @ 10db for all enabled ingredients with a count >= 10
 
            // 2) any previously-generated recipes
 
            int enabledReagentCount = 0;
 
            PaintRecipe recipe = new PaintRecipe();
 
            recipe.Reactions = reactions;
 
            foreach (Reagent reagent in costSortedReagents)
 
            {
 
                if (reagent.Enabled)
 
                {
 
                    if (!reagent.IsCatalyst && ((reagent.RecipeMax >= 10) || ((FullQuantityDepth > 0) && (FullQuantity >= 10))))
 
                    {
 
                        recipe.Clear();
 
                        recipe.AddReagent(reagent.Name, 10);
 
                        AddCheapestRecipe(recipe);
 
                    }
 
                    ++enabledReagentCount;
 
                }
 
            }
 
            this.MaxReagents = (uint)Math.Min(enabledReagentCount, this.MaxReagents);
 
            this.MinReagents = (uint)Math.Min(this.MinReagents, this.MaxReagents);
 

	
 
            while (!searchQueue.IsEmpty)
 
            {
 
                RecipeSearchNode node;
 
                searchQueue.TryDequeue(out node);
 
            }
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                if (costSortedReagents[(int)reagentIdx].Enabled)
 
                {
 
                    // queue up all combinations of MinReagents
 
                    RecipeSearchNode initialNode = new RecipeSearchNode(costSortedReagents, reagentIdx);
 
                    initialNode.FullQuantity = FullQuantity;
 
                    initialNode.FullQuantityDepth = FullQuantityDepth;
 
                    initialNode.MaxQuantity = maxQuantity;
 
                    initialNode.MinReagents = minReagents;
 
                    initialNode.MaxReagents = maxReagents;
 
                    searchQueue.Enqueue(initialNode);
 

	
 
                    if (MinReagents > 1)
 
                    {
 
                        while (NextReagentSetBreadthFirst(initialNode, 1, minReagents) == true)
 
                        {
 
                            if (initialNode.ReagentCount == minReagents)
 
                            {
 
                                //Console.WriteLine("Initial node at size {0}/{1} with recipe: {2}", initialNode.ReagentCount, minReagents, initialNode.TestRecipe.ToString());
 
                                RecipeSearchNode searchNode = new RecipeSearchNode(initialNode);
 
                                searchQueue.Enqueue(searchNode);
 
                            }
 
                        }
 
                    }
 
                    else
 
                    {
 
                        searchQueue.Enqueue(initialNode);
 
                    }
 
                }
 
            }
 

	
 
            recipeCount = 0;
 

	
 
            if (log != null)
 
            {
 
                log.WriteLine("Begin recipe generation: MaxQuantity={0} MinReagents={1} MaxReagents={2} FullQuantity={3} FullQuantityDepth={4}", MaxQuantity, 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;
 

	
 
            if (log != null)
 
            {
 
                log.WriteLine("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)
 
            {
 
                if (Finished != null)
 
                {
 
                    Finished(this, null);
 
                }
 
            }
 
            generatorThreads.Clear();
 
            System.Console.WriteLine("Starting {0} generator threads.", threadCount);
 
            for (int i = 0; i < threadCount; ++i)
 
            {
 
                Thread thr = new Thread(new ThreadStart(this.Generate));
 
                thr.Priority = ThreadPriority.BelowNormal;
 
                generatorThreads.Add(thr);
 
            }
 
            foreach (Thread thr in generatorThreads)
 
            {
 
                thr.Start();
 
            }
 
        }
 

	
 
        public bool SaveState(string file)
 
        {
 
            if (running)
 
            {
 
                // can't save state while running
 
                return false;
 
            }
 

	
 
            using (StreamWriter writer = new StreamWriter(file, false))
 
            lock(workerLock)
 
            {
 
                writer.WriteLine("MinReagents: {0}", MinReagents);
 
                writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
                writer.WriteLine("FullQuantityDepth: {0}", FullQuantityDepth);
 
                writer.WriteLine("FullQuantity: {0}", FullQuantity);
 
                writer.WriteLine("TotalReagents: {0}", totalReagents);
 
                writer.WriteLine("RecipeCount: {0}", recipeCount);
 
                writer.WriteLine("SearchType: {0}", Mode.ToString());
 
                foreach (KeyValuePair<string, PaintRecipe> pair in recipes)
 
                using (StreamWriter writer = new StreamWriter(file, false))
 
                {
 
                    PaintRecipe recipe = pair.Value;
 
                    string colorName = Palette.FindNearest(recipe.ReactedColor);
 
                    writer.WriteLine("BeginRecipe: {0}", colorName);
 
                    foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients)
 
                    writer.WriteLine("MinReagents: {0}", MinReagents);
 
                    writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
                    writer.WriteLine("FullQuantityDepth: {0}", FullQuantityDepth);
 
                    writer.WriteLine("FullQuantity: {0}", FullQuantity);
 
                    writer.WriteLine("TotalReagents: {0}", totalReagents);
 
                    writer.WriteLine("RecipeCount: {0}", recipeCount);
 
                    writer.WriteLine("SearchType: {0}", Mode.ToString());
 
                    foreach (KeyValuePair<string, PaintRecipe> pair in recipes)
 
                    {
 
                        writer.WriteLine("Ingredient: {0}={1}", ingredient.name, ingredient.quantity);
 
                        PaintRecipe recipe = pair.Value;
 
                        string colorName = Palette.FindNearest(recipe.ReactedColor);
 
                        writer.WriteLine("BeginRecipe: {0}", colorName);
 
                        foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients)
 
                        {
 
                            writer.WriteLine("Ingredient: {0}={1}", ingredient.name, ingredient.quantity);
 
                        }
 
                        writer.WriteLine("EndRecipe: {0}", colorName);
 
                    }
 
                    writer.WriteLine("EndRecipe: {0}", colorName);
 
                    writer.WriteLine("SearchNodes: {0}", searchQueue.Count);
 
                    foreach (RecipeSearchNode node in searchQueue)
 
                    {
 
                        node.SaveState(writer);
 
                   }
 
                }
 
                writer.WriteLine("SearchNodes: {0}", searchQueue.Count);
 
                foreach (RecipeSearchNode node in searchQueue)
 
                {
 
                    node.SaveState(writer);
 
               }
 
            }
 
            return true;
 
        }
 

	
 
        static Regex keyValueRegex = new Regex(@"(?<key>\w+)\:\s*(?<value>.+)\s*");
 
        static Regex ingredientRegex = new Regex(@"(?<ingredient>(\w+\s)*\w+)\s*=\s*(?<quantity>\d)\s*");
 
        public bool LoadState(string file)
 
        {
 
            // cannot be running, and reactions must be set
 
            if (running)
 
            {
 
                return false;
 
            }
 

	
 
            InitSortedReagents();
 
            bool success = true;
 

	
 
            PaintRecipe currentRecipe = null;
 
            Match match;
 
            string line;
 
            using (StreamReader reader = new StreamReader(file, false))
 
            {
 
                while (success && ((line = reader.ReadLine()) != null))
 
                {
 
                    match = keyValueRegex.Match(line);
 
                    if (match.Success)
 
                    {
 
                        string value = match.Groups["value"].Value;
 
                        switch(match.Groups["key"].Value)
 
                        {
 
                            case "MinReagents":
 
                                MinReagents = uint.Parse(value);
 
                                MaxReagents = (uint)Math.Max(this.MinReagents, this.MaxReagents);
 
                                break;
 
                            case "MaxReagents":
 
                                MaxReagents = uint.Parse(value);
 
                                MinReagents = (uint)Math.Min(this.MinReagents, this.MaxReagents);
 
                                break;
 
                            case "FullQuantityDepth":
 
                                FullQuantityDepth = uint.Parse(value);
 
                                break;
 
                            case "FullQuantity":
 
                                FullQuantity = uint.Parse(value);
 
                                break;
 
                            case "TotalReagents":
 
                                totalReagents = uint.Parse(value);
 
                                break;
 
                            case "RecipeCount":
 
                                if (!ulong.TryParse(value, out recipeCount))
 
                                {
 
                                    // must have rolled to negative - try as an int and convert
 
                                    int recipeCountInt = int.Parse(value);
 
                                    recipeCount = (ulong)((long)recipeCountInt & 0x00000000ffffffffL);
 
                                }
 
                                break;
 
                            case "BeginRecipe":
 
                                currentRecipe = new PaintRecipe();
 
                                currentRecipe.Reactions = reactions;
 
                                break;
 
                            case "EndRecipe":
 
                                if (currentRecipe != null)
 
                                {
 
                                    PaintColor color = currentRecipe.ReactedColor;
 
                                    uint cost = currentRecipe.Cost;
 
                                    recipes[color.Name] = currentRecipe; // replace
 
                                    recipeCosts[color.Name] = cost;
 
                                    currentRecipe = null;
 
                                }
 
                                break;
 
                            case "Ingredient":
 
                                if (currentRecipe != null)
 
                                {
 
                                    Match ingredientMatch = ingredientRegex.Match(match.Groups["value"].Value);
 
                                    if (ingredientMatch.Success)
 
                                    {
 
                                        uint quantity = uint.Parse(ingredientMatch.Groups["quantity"].Value);
 
                                        currentRecipe.AddReagent(ingredientMatch.Groups["ingredient"].Value, quantity);
 
                                    }
 
                                    else
 
                                    {
 
                                        success = false;
 
                                    }
 
                                }
 
                                break;
 
                            case "SearchNodes":
 
                                int nodeCount = int.Parse(match.Groups["value"].Value);
 
                                for (int i = 0; i < nodeCount; ++i)
 
                                {
 
                                    RecipeSearchNode node = new RecipeSearchNode(costSortedReagents);
 
                                    node.FullQuantity = FullQuantity;
 
                                    node.FullQuantityDepth = FullQuantityDepth;
 
                                    node.MinReagents = MinReagents;
 
                                    node.MaxReagents = MaxReagents;
 
                                    node.MaxQuantity = MaxQuantity;
 
                                    success = success && node.LoadState(reader);
 
                                    if (success)
 
                                    {
 
                                        searchQueue.Enqueue(node);
 
                                    }
 
                                }
 
                                break;
 
                            case "SearchType":
 
                                Mode = (SearchType)Enum.Parse(typeof(SearchType), match.Groups["value"].Value);
 
                                break;
 
                            default:
 
                                success = false;
 
                                break;
 
                        }
 
                    }
 
                    else
 
                    {
 
                        success = false;
 
                        break;
 
                    }
 
                }
 
                return success;
 
            }
 
        }
 

	
 
        private void Generate()
 
        {
 
            RecipeSearchNode node;
 

	
 
            lock(workerLock)
 
            {
 
                ++runningThreads;
 
            }
 

	
 
            bool ok = true;
 
            do
 
            {
 
                lock (workerLock)
 
                {
 
                    ok = searchQueue.TryDequeue(out node);
 
                }
 
                if (ok)
 
                {
 
                    if (Mode == SearchType.DEPTH_FIRST)
 
                    {
 
                        uint targetQuantity = (node.ReagentCount <= FullQuantityDepth) ? ((uint)node.ReagentCount * FullQuantity) : node.MaxQuantity + 1;
 
                        do {
 
                            --targetQuantity;
 
                            node.InitForQuantity(targetQuantity);
 
                        } while (targetQuantity > 10 && (node.CurrentTargetQuantity != node.UsedQuantity));
 
    
 
                        while ((ok = IterateDepthFirst(node)) && !requestCancel)
 
                        {
 
                            if (Progress != null)
 
                            {
 
                                Progress(this, null);
 
                            }
 
                        }
 
                    }
 
                    else
 
                    {
 
                        // breadth-first search
 
                        uint targetQuantity = 9;
 
                        uint quantityLimit = (node.ReagentCount <= FullQuantityDepth) ? (FullQuantity * (uint)node.ReagentCount) : node.MaxQuantity;
 
                        do {
 
                            ++targetQuantity;
 
                            node.InitForQuantity(targetQuantity);
 
                        } while ((targetQuantity <= quantityLimit) && (node.CurrentTargetQuantity != node.UsedQuantity));
 
    
 
                        while ((ok = IterateBreadthFirst(node)) && !requestCancel)
 
                        {
 
                            if (Progress != null)
 
                            {
 
                                Progress(this, null);
 
                            }
 
                        }
 
                    }
 
                    if (ok)
 
                    {
 
                        // stopped because cancel was requested - requeue the node in its current state for resume
 
                        searchQueue.Enqueue(node);
 
                    }
 
                }
 
            } while (!requestCancel && ok);
 

	
 
            bool done = false;
 
            lock(workerLock)
 
            {
 
                --runningThreads;
 
                //generatorThreads.Remove(Thread.CurrentThread);
 

	
 
                done = (runningThreads == 0);
 
            }
 
            if (done)
 
            {
 
                running = false;
 
                requestCancel = false;
 
                if (Finished != null)
 
                {
 
                    Finished(this, null);
 
                }
 
            }
 
        }
 

	
 
        // Add the cheapest recipe to the recipe list
 
        // returns the discarded recipe from the pair (or null if no original recipe to replace)
 
        private void AddCheapestRecipe(PaintRecipe recipe)
 
        {
 
            if (recipe.IsValid)
 
            {
 
                recipe.Reactions = reactions;
 

	
 
                string colorName = Palette.FindNearest(recipe.ReactedColor);
 
                //System.Console.WriteLine("Recipe: {0} {1}:", colorName, recipe.Cost);
 
                //foreach (PaintRecipe.RecipeIngredient ingr in recipe.Ingredients)
 
                //{
 
                //    System.Console.WriteLine("    -> {0} {1}", ingr.quantity, ingr.name);
 
                //}
 
                uint cost;
 
                lock (workerLock)
 
                {
 
                    if (recipeCosts.TryGetValue(colorName, out cost))
 
                    {
 
                        if (cost > recipe.Cost)
 
                        {
 
                            recipes[colorName].CopyFrom(recipe);
 
                            recipeCosts[colorName] = recipe.Cost;
 
                            if (NewRecipe != null)
 
                            {
 
                                NewRecipeEventArgs args = new NewRecipeEventArgs(colorName, recipe);
 
                                NewRecipe(this, args);
 
                            }
 
                        }
 
                    }
 
                    else
 
                    {
 
                        // This would be an error!
 
                        recipeCosts.Add(colorName, recipe.Cost);
 
                        recipes.Add(colorName, new PaintRecipe(recipe));
 
                        if (NewRecipe != null)
 
                        {
 
                            NewRecipeEventArgs args = new NewRecipeEventArgs(colorName, recipe);
 
                            NewRecipe(this, args);
 
                        }
 
                    }
 
                }
 
            }
 
            //else
 
            //{
 
            //    string msg = String.Format("Recipe is invalid ({0} ingredients)\n", recipe.Ingredients.Count);
 
            //    foreach (PaintRecipe.RecipeIngredient ingr in recipe.Ingredients)
 
            //    {
 
            //        msg += String.Format("    -> {0} {1}", ingr.quantity, ingr.name);
 
            //    }
 
            //    lock (workerLock) {
 
            //        Console.WriteLine(msg);
 
            //    }
 
            //}
 
        }
 

	
 
        private bool IterateDepthFirst(RecipeSearchNode node)
 
        {
 
            TestCurrentRecipe(node);
 

	
 
            // pick recipe quantities at current recipe ingredients/size
 
            if (NextRecipe(node))
 
            {
 
                lock(workerLock)
 
                {
 
                    ++recipeCount;
 
                }
 
                //System.Console.WriteLine("Found next recipe at size {0} qty {1}", node.Reagents.Count, node.CurrentTargetQuantity);
 
                return true;
 
            }
 

	
 
            if (NextRecipeSize(node))
 
            {
 
                //System.Console.WriteLine("Found next recipe size {0}", node.CurrentTargetQuantity);
 
                return true;
 
            }
 

	
 
            // Search for next ingredient combo - all quantity combos for previous were searched
 
            //System.Console.WriteLine("Finding next ingredient combo");
 
            do
 
            {
 
                if (!node.AddNextReagent())
 
                {
 
                    while ((node.ReagentCount > node.MinReagents) && (node.LastReagent == (totalReagents-1)))
 
                    {
 
                        node.RemoveLastReagent();
 
                    }
 
                    if (node.ReagentCount == node.MinReagents)
 
                    {
 
                        // done
 
                        return false;
 
                    }
 
                    uint nextReagent = node.NextFreeReagent(node.LastReagent);
 
                    while ((node.ReagentCount > node.MinReagents) && (nextReagent >= totalReagents))
 
                    {
 
                        // No more reagents to try at this level
 
                        node.RemoveLastReagent();
 
                        if (node.ReagentCount > node.MinReagents)
 
                        {
 
                            nextReagent = node.NextFreeReagent(node.LastReagent);
 
                        }
 
                    }
 
                    if (node.ReagentCount == node.MinReagents)
 
                    {
 
                        // done
 
                        return false;
 
                    }
 
                    node.ReplaceLastReagent(nextReagent);
 
                }
 
            } while (node.MaxQuantity < (10 + node.CatalystCount));
 
            node.InitForQuantity(node.MaxQuantity);
 

	
 
            //string outStr = "{0} : {1} : ";
 
            //for (int i = 0; i < currentReagents.Count; ++i)
 
            //{
 
            //    Reagent reagent = costSortedReagents[(int)currentReagents[i]];
 
            //    if (i > 0)
 
            //    {
 
            //        outStr += ", ";
 
            //    }
 
            //    outStr += reagent.Name + " (" + reagent.Cost + ")";
 
            //}
 
            //Console.WriteLine(outStr, currentReagents.Count, recipeCount);
 
            return true;
 
        }
 

	
 
        private bool IterateBreadthFirst(RecipeSearchNode node)
 
        {
 
            // pick recipe quantities at current recipe ingredients/size
 
            TestCurrentRecipe(node);
 
            lock(workerLock)
 
            {
 
                ++recipeCount;
 
            }
 

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

	
 
            // Try next quantity
 
            uint newQuantity;
 
            uint quantityLimit = ((uint)node.ReagentCount <= FullQuantityDepth) ? ((uint)node.ReagentCount * FullQuantity) : node.MaxQuantity;
 
            do {
 
                newQuantity = node.CurrentTargetQuantity + 1;
 
                //Console.WriteLine("Try quantity {0}", newQuantity);
 
                if (newQuantity <= quantityLimit)
 
                {
 
                    node.InitForQuantity(newQuantity);
 
                    if (node.CurrentTargetQuantity <= node.UsedQuantity)
 
                    {
 
                        if (log != null) { lock(log) { log.WriteLine("Update quantity to {0}", node.CurrentTargetQuantity); } }
 
                        return true;
 
                    }
 
                }
 
            } while (newQuantity < quantityLimit);
 

	
 
            bool ok = NextReagentSetBreadthFirst(node, node.MinReagents, node.MaxReagents);
 
            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.MaxQuantity, node.UsedQuantity);
 
            node.InitForQuantity(10+node.CatalystCount); // reset quantity
 
            int currentDepth = node.ReagentCount;
 
            bool recipeFound = false;
 
            uint nextReagent;
 
            do {
 
                //Console.WriteLine("Current depth: {0}/{1}", currentDepth, node.MaxReagents);
 
                do {
 
                    recipeFound = false;
 
                    // back out until we find a node that can be incremented
 
                    if (currentDepth > node.MinReagents)
 
                    if (currentDepth > minReagents)
 
                    {
 
                        while (node.ReagentCount > node.MinReagents)
 
                        while (node.ReagentCount > minReagents)
 
                        {
 
                            if (node.LastReagent < (totalReagents - 1))
 
                            {
 
                                nextReagent = node.NextFreeReagent(node.LastReagent);
 
                                if (nextReagent < totalReagents)
 
                                {
 
                                    //Console.WriteLine("Replace last reagent with {0}", nextReagent);
 
                                    node.ReplaceLastReagent(nextReagent);
 
                                    break;
 
                                }
 
                                else
 
                                {
 
                                    // shouldn't happen
 
                                    //Console.WriteLine("No available reagents at depth {0}!", node.ReagentCount);
 
                                    node.RemoveLastReagent();
 
                                    if (node.ReagentCount == node.MinReagents)
 
                                    if (node.ReagentCount == minReagents)
 
                                    {
 
                                        // just popped the last reagent at the top level
 
                                        ++currentDepth;
 
                                        if (log != null) { lock(log) { log.WriteLine("Increased depth to {0}/{1}", currentDepth, node.MaxReagents); } }
 
                                    }
 
                                }
 
                            }
 
                            else
 
                            {
 
                                //Console.WriteLine("Pop last reagent");
 
                                node.RemoveLastReagent();
 
                                if (node.ReagentCount == node.MinReagents)
 
                                if (node.ReagentCount == minReagents)
 
                                {
 
                                    // just popped the last reagent at the top level
 
                                    ++currentDepth;
 
                                    if (log != null) { lock(log) { log.WriteLine("Increased depth to {0}/{1} [pop last reagent at top level]", currentDepth, node.MaxReagents); } }
 
                                }
 
                            }
 
                        }
 
                        // fill in the nodes up to the current depth
 
                        if (node.ReagentCount >= node.MinReagents)
 
                        if (node.ReagentCount >= minReagents && currentDepth <= maxReagents)
 
                        {
 
                            recipeFound = true;
 
                            while (node.ReagentCount < currentDepth)
 
                            {
 
                                if (! node.AddNextReagent())
 
                                {
 
                                    if (log != null) { lock(log) { log.WriteLine("Failed to 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 >= node.MinReagents)); // make sure to skip all-catalyst combinations
 
                } 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 <= node.MaxReagents);
 
            } while (currentDepth <= maxReagents);
 
            if (recipeFound)
 
            {
 
                node.InitForQuantity(10+node.CatalystCount); // minimum quantity for this recipe
 
                if (node.TestRecipe == null)
 
                {
 
                    node.TestRecipe = new PaintRecipe();
 
                    node.TestRecipe.Reactions = reactions;
 
                }
 
                node.TestRecipe.Clear();
 
                for (int i = 0; i < node.ReagentCount; ++i)
 
                {
 
                    node.TestRecipe.AddReagent(node.Reagent(i).Name, node.CurrentWeights[i]);
 
                }
 
                if (log != null) { 
 
                    string combo = "";
 
                    foreach (PaintRecipe.RecipeIngredient ingr in node.TestRecipe.Ingredients)
 
                    {
 
                        combo += " " + ingr.name;
 
                    }
 
                    lock(log) { log.WriteLine("New ingredients: " + combo); }
 
                }
 
            }
 

	
 
            return recipeFound;
 
        }
 

	
 
        private void TestCurrentRecipe(RecipeSearchNode node)
 
        {
 
            if (node.TestRecipe == null)
 
            {
 
                node.TestRecipe = new PaintRecipe();
 
                node.TestRecipe.Reactions = reactions;
 
            }
 
            node.TestRecipe.Clear();
 
            for (int i = 0; i < node.ReagentCount; ++i)
 
            {
 
                node.TestRecipe.AddReagent(node.Reagent(i).Name, node.CurrentWeights[i]);
 
            }
 
            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 < 10)
 
            {
 
                // not possible to make a valid recipe
 
                Console.WriteLine("Insufficient remaining weight");
 
                return false;
 
            }
 
            //uint remainingReagents = (uint)node.Reagents.Count - node.CatalystCount;
 

	
 
            uint depth = (uint)node.ReagentCount;
 
            uint weightToConsume = 0;
 
            uint spaceBelow = 0;
 
            int reagentsBelow = 0;
 
            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);
 
                    weightToConsume += 1;
 
                    for (int j = i+1; j < depth; ++j)
 
                    {
 
                        --reagentsBelow;
 
                        Reagent reagent = node.Reagent(j);
 
                        uint allocated = (uint)Math.Min(reagent.IsCatalyst ? 1 : (depth <= FullQuantityDepth ? FullQuantity : reagent.RecipeMax), weightToConsume - reagentsBelow);
 
                        if (allocated > 100)
 
                        {
 
                            Console.WriteLine("ACK: allocated = {0}", allocated);
 
                        }
 
                        node.SetWeight(j, allocated);
 
                        weightToConsume -= allocated;
 
                    }
 
                    break;
 
                }
 
                else
 
                {
 
                    Reagent reagent = node.Reagent(i);
 
                    spaceBelow += (reagent.IsCatalyst ? 1 : (depth <= FullQuantityDepth ? FullQuantity : reagent.RecipeMax));
 
                    weightToConsume += currentWeight;
 
                    ++reagentsBelow;
 
                }
 
            }
 

	
 
            //int recipeWeight = 0;
 
            //foreach (int weight in node.CurrentWeights)
 
            //{
 
            //    recipeWeight += weight;
 
            //}
 
            //if ((weightToConsume != 0) || (recipeWeight != node.CurrentTargetQuantity))
 
            //{
 
            //    Console.WriteLine("Failed recipe with leftover weight {0} ({1}/{2}):", weightToConsume, recipeWeight, node.CurrentTargetQuantity);
 
            //    for (int i = 0; i < node.Reagents.Count; ++i)
 
            //    {
 
            //        Console.WriteLine("   > {0} {1}", node.Reagent(i).Name, node.CurrentWeights[i]);
 
            //    }
 
            //}
 
            
 
            return (weightToConsume == 0);
 
        }
 

	
 
        private bool NextRecipeSize(RecipeSearchNode node)
 
        {
 
            uint newQuantity = node.CurrentTargetQuantity - 1;
 
            if (newQuantity < (10 + node.CatalystCount))
 
            {
 
                return false;
 
            }
 

	
 
            node.InitForQuantity(newQuantity);
 
            if (node.CurrentTargetQuantity > node.UsedQuantity)
 
            {
 
                return false;
 
            }
 

	
 
            return true;
 
        }
 

	
 
        public void Wait()
 
        {
 
            if (generatorThreads.Count > 0)
 
            {
 
                foreach (Thread thr in generatorThreads)
 
                {
 
                    thr.Join();
 
                }
 
                generatorThreads.Clear();
 
            }
 
        }
 

	
 
        public void Stop()
 
        {
 
            this.requestCancel = true;
 
        }
 

	
 
        public void Reset()
 
        {
 
            foreach (PaintRecipe recipe in recipes.Values)
 
            {
 
                recipe.Clear();
 
            }
 
            foreach (string key in recipeCosts.Keys)
 
            {
 
                recipeCosts[key] = uint.MaxValue;
 
            }
 
        }
 
    }
 
}
 

	
RecipeSearchNode.cs
Show inline comments
 
/*
 
 * Copyright (c) 2015, Jason Maltzen
 

	
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 
 of this software and associated documentation files (the "Software"), to deal
 
 in the Software without restriction, including without limitation the rights
 
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
 copies of the Software, and to permit persons to whom the Software is
 
 furnished to do so, subject to the following conditions:
 

	
 
 The above copyright notice and this permission notice shall be included in
 
 all copies or substantial portions of the Software.
 

	
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
 THE SOFTWARE.
 
*/
 

	
 
using System;
 
using System.Collections.Generic;
 
using System.IO;
 
using System.Text.RegularExpressions;
 

	
 
namespace DesertPaintLab
 
{
 
    public class RecipeSearchNode
 
    {
 
        //int initialReagentCount;
 
        uint[] reagents;
 
        public uint[] Reagents
 
        {
 
            get
 
            {
 
                return reagents;
 
            }
 
        }
 
        uint INVALID_REAGENT;
 
        int nextReagentPos;
 
        public int ReagentCount
 
        {
 
            get
 
            {
 
                return nextReagentPos;
 
            }
 
        }
 

	
 
        bool[] reagentInUse;
 
        List<Reagent> costSortedReagents;
 
        PaintRecipe testRecipe = null;
 
        public PaintRecipe TestRecipe
 
        {
 
            get
 
            {
 
                return testRecipe;
 
            }
 
            set
 
            {
 
                testRecipe = value;
 
            }
 
        }
 

	
 
        public uint CurrentTargetQuantity { get; set; }
 
        public uint MaxQuantity { 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; }
 

	
 
        uint maxReagents;
 
        public uint MaxReagents
 
        {
 
            get
 
            {
 
                return maxReagents;
 
            }
 
            set
 
            {
 
                maxReagents = value;
 
                currentWeights = new uint[maxReagents];
 
            }
 
        }
 

	
 
        uint[] currentWeights;
 
        public uint[] CurrentWeights
 
        {
 
            get
 
            {
 
                return currentWeights;
 
            }
 
        }
 

	
 
        public RecipeSearchNode(RecipeSearchNode other)
 
        {
 
            this.costSortedReagents = new List<Reagent>(other.costSortedReagents);
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            for (int i = 0; i < costSortedReagents.Count; ++i)
 
            {
 
                this.reagents[i] = other.reagents[i];
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint i = 0; i < costSortedReagents.Count; ++i)
 
            {
 
                reagentInUse[i] = other.reagentInUse[i];
 
            }
 
            nextReagentPos = other.nextReagentPos;
 

	
 
            CurrentTargetQuantity = other.CurrentTargetQuantity;
 
            MaxQuantity = other.MaxQuantity;
 
            UsedQuantity = other.UsedQuantity;
 
            CatalystCount = other.CatalystCount;
 
            FullQuantityDepth = other.FullQuantityDepth;
 
            FullQuantity = other.FullQuantity;
 
            MinReagents = other.MinReagents;
 
            MaxReagents = other.MaxReagents;
 
            for (int i = 0; i < MaxReagents; ++i)
 
            {
 
                currentWeights[i] = other.currentWeights[i];
 
            }
 
        }
 

	
 
        public RecipeSearchNode(List<Reagent> costSortedReagents, uint[] reagents)
 
        {
 
            this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            nextReagentPos = reagents.Length;
 
            for (int i = this.reagents.Length-1; i >= this.reagents.Length; --i)
 
            {
 
                reagents[i] = INVALID_REAGENT;
 
            }
 
            for (int i = reagents.Length-1; i >= 0; --i)
 
            {
 
                this.reagents[i] = reagents[i];
 
                if (reagents[i] == INVALID_REAGENT)
 
                {
 
                    nextReagentPos = i;
 
                }
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
            foreach (uint reagentIdx in this.reagents)
 
            {
 
                if (reagentIdx != INVALID_REAGENT)
 
                {
 
                    reagentInUse[reagentIdx] = true;
 
                }
 
            }
 
            MinReagents = (uint)nextReagentPos;
 
            MaxReagents = (uint)nextReagentPos; // better set this later!
 
            UsedQuantity = 0;
 
        }
 

	
 
        // top-level search
 
        public RecipeSearchNode(List<Reagent> costSortedReagents, uint startReagent)
 
        {
 
            this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            nextReagentPos = 0;
 
            for (int i = 0; i < reagents.Length; ++i)
 
            {
 
                this.reagents[i] = INVALID_REAGENT;
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
            this.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;
 
            UsedQuantity = 0;
 
        }
 

	
 
        public RecipeSearchNode(List<Reagent> costSortedReagents)
 
        {
 
            this.costSortedReagents = costSortedReagents;
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            nextReagentPos = 0;
 
            for (int i = 0; i < reagents.Length; ++i)
 
            {
 
                this.reagents[i] = INVALID_REAGENT;
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
            this.reagents[nextReagentPos++] = NextFreeReagent(0);
 
            MinReagents = 0;
 
            MaxReagents = 1;
 
            UsedQuantity = 0;
 
        }
 

	
 
        public Reagent Reagent(int idx)
 
        {
 
            return costSortedReagents[(int)reagents[idx]];
 
        }
 

	
 
        public uint LastReagent
 
        {
 
            get
 
            {
 
                return reagents[nextReagentPos - 1];
 
            }
 
        }
 

	
 
        public void RemoveLastReagent()
 
        {
 
            uint reagentIdx = reagents[nextReagentPos-1];
 
            ReleaseReagent(reagentIdx);
 
            if (costSortedReagents[(int)reagentIdx].IsCatalyst)
 
            {
 
                --CatalystCount;
 
            }
 
            reagents[nextReagentPos-1] = INVALID_REAGENT;
 
            --nextReagentPos;
 
        }
 

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

	
 
        public uint NextFreeReagent(uint startIdx)
 
        {
 
            uint idx = startIdx;
 
            for (; idx < costSortedReagents.Count; ++idx)
 
            {
 
                bool inUse = reagentInUse[idx];
 
                if ((inUse == false) && (costSortedReagents[(int)idx].Enabled))
 
                {
 
                    //Console.WriteLine("Found free reagent idx {0}", idx);
 
                    reagentInUse[idx] = true;
 
                    return idx;
 
                }
 
            }
 
            //Console.WriteLine("Failed to find free reagent.");
 
            return (uint)costSortedReagents.Count;
 
        }
 

	
 
        private void ReleaseReagent(uint reagentIdx)
 
        {
 
            reagentInUse[reagentIdx] = false;
 
        }
 

	
 
        public bool AddNextReagent()
 
        {
 
            bool ok = (nextReagentPos < MaxReagents);
 
            if (ok)
 
            {
 
                uint nextReagent = NextFreeReagent(0);
 
                reagents[nextReagentPos++] = nextReagent;
 
                if (costSortedReagents[(int)nextReagent].IsCatalyst)
 
                {
 
                    ++CatalystCount;
 
                }
 
                InitForQuantity(CurrentTargetQuantity);
 
            }
 
            return ok;
 
        }
 

	
 
        public void InitForQuantity(uint quantity)
 
        {
 
            //System.Console.WriteLine("Init for quantity: {0}, reagent count: {1} ({2} catalysts)", quantity, ReagentCount, CatalystCount);
 
            CurrentTargetQuantity = quantity;
 
            if (CurrentTargetQuantity < (10 + CatalystCount))
 
            {
 
                // invalid quantity
 
                return;
 
            }
 
            UsedQuantity = 0;
 
            uint remainingReagents = ((uint)nextReagentPos - CatalystCount);
 
            uint remainingWeight = CurrentTargetQuantity - CatalystCount;
 
            for (int i = 0; i < nextReagentPos; ++i)
 
            {
 
                Reagent reagent = Reagent(i);
 

	
 
                if (reagent.IsCatalyst)
 
                {
 
                    //Console.WriteLine("Init catalyst {0} weight 1", reagent.Name);
 
                    currentWeights[i] = 1;
 
                    ++UsedQuantity;
 
                }
 
                else
 
                {
 
                    uint reagentMaxWeight = reagent.RecipeMax;
 
                    if (ReagentCount <= FullQuantityDepth)
 
                    {
 
                        reagentMaxWeight = Math.Max(FullQuantity, reagentMaxWeight);
 
                    }
 
                    uint weight = (uint)Math.Min(remainingWeight - (remainingReagents-1), reagentMaxWeight);
 
                    //Console.WriteLine("Init reagent {0} weight {1}", reagent.Name, weight);
 
                    remainingWeight -= weight;
 
                    currentWeights[i] = weight;
 
                    UsedQuantity += weight;
 
                }
 
                --remainingReagents;
 
            }
 
        }
 

	
 
        public void SetWeight(int idx, uint quantity)
 
        {
 
            UsedQuantity -= currentWeights[idx];
 
            currentWeights[idx] = quantity;
 
            UsedQuantity += quantity;
 
        }
 

	
 
        public void SaveState(StreamWriter writer)
 
        {
 
            writer.WriteLine("---SearchNode---");
 
            writer.WriteLine("MinReagents: {0}", MinReagents);
 
            writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
            writer.WriteLine("Reagents: {0}", nextReagentPos);
 
            for (int i = 0; i < nextReagentPos; ++i)
 
            {
 
                uint idx = reagents[i];
 
                uint weight = currentWeights[i];
 
                writer.WriteLine("Reagent: {0},{1},{2}", idx, reagentInUse[idx] ? 1 : 0, weight);
 
            }
 
            // pulled from parent: List<Reagent> costSortedReagents;
 
            // new on construct: PaintRecipe testRecipe = null;
 
            writer.WriteLine("CurrentTargetQuantity: {0}", CurrentTargetQuantity);
 
            writer.WriteLine("MaxQuantity: {0}", MaxQuantity);
 
            writer.WriteLine("UsedQuantity: {0}", UsedQuantity);
 
            writer.WriteLine("CatalystCount: {0}", CatalystCount);
 
            writer.WriteLine("FullQuantity: {0}", FullQuantity);
 
            writer.WriteLine("FullQuantityDepth: {0}", FullQuantityDepth);
 
            writer.WriteLine("---EndNode---");
 
            
 
        }
 

	
 
        static Regex keyValueRegex = new Regex(@"(\w+)\:\s*(.*)\s*$");
 
        static Regex reagentPartsRegex = new Regex(@"(?<id>\d+),(?<inUse>\d+),(?<weight>\d+)");
 
        public bool LoadState(StreamReader reader)
 
        {
 
            string line = reader.ReadLine();
 
            if (!line.Equals("---SearchNode---"))
 
            {
 
                return false;
 
            }
 

	
 
            bool success = true;
 
            Match match;
 
            while ((line = reader.ReadLine()) != null)
 
            {
 
                if (line.Equals("---EndNode---"))
 
                {
 
                    break;
 
                }
 
                match = keyValueRegex.Match(line);
 
                if (match.Success)
 
                {
 
                    switch (match.Groups[1].Value)
 
                    {
 
                        case "Reagents":
 
                            {
 
                                //int reagentCount = int.Parse(match.Groups[2].Value);
 
                                for (int i = 0; i < reagents.Length; ++i)
 
                                {
 
                                    reagents[i] = INVALID_REAGENT;
 
                                    reagentInUse[i] = false;
 
                                }
 
                                nextReagentPos = 0;
 
                            }
 
                            break;
 
                        case "Reagent":
 
                            {
 
                                Match reagentInfo = reagentPartsRegex.Match(match.Groups[2].Value);
 
                                if (reagentInfo.Success)
 
                                {
 
                                    uint reagentId = uint.Parse(reagentInfo.Groups["id"].Value);
 
                                    int isInUse = int.Parse(reagentInfo.Groups["inUse"].Value);
 
                                    uint weight = uint.Parse(reagentInfo.Groups["weight"].Value);
 
                                    reagents[nextReagentPos] = reagentId;
 
                                    currentWeights[nextReagentPos] = weight;
 
                                    if (isInUse != 0)
 
                                    {
 
                                        if (reagentId != INVALID_REAGENT)
 
                                        {
 
                                            reagentInUse[reagentId] = true;
 
                                        }
 
                                    }
 
                                    ++nextReagentPos;
 
                                }
 
                                else
 
                                {
 
                                    success = false;
 
                                }
 
                            }
 
                            break;
 
                        case "CurrentTargetQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                CurrentTargetQuantity = value;
 
                            }
 
                            break;
 
                        case "MaxQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                MaxQuantity = value;
 
                            }
 
                            break;
 
                        case "MinReagents":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                MinReagents = value;
 
                            }
 
                            break;
 
                        case "MaxReagents":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                MaxReagents = value;
 
                            }
 
                            break;
 
                        case "UsedQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                UsedQuantity = value;
 
                            }
 
                            break;
 
                        case "CatalystCount":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                CatalystCount = value;
 
                            }
 
                            break;
 
                        case "InitialCount":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                MinReagents = value;
 
                            }
 
                            break;
 
                        case "FullQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                FullQuantity = value;
 
                            }
 
                            break;
 
                        case "FullQuantityDepth":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                FullQuantityDepth = value;
 
                            }
 
                            break;
 
                        default:
 
                            success = false;
 
                            break;
 
                    }
 
                }
 
                else
 
                {
 
                    success = false;
 
                    break;
 
                }
 
            }
 
            return success;
 
        }
 
    }
 
}
 

	
0 comments (0 inline, 0 general)