using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; namespace DesertPaintCodex.Models { public class RecipeSearchNode { private readonly uint[] _reagents; private readonly uint _invalidReagent; private int _nextReagentPos; public int ReagentCount => _nextReagentPos; private uint _minConcentration = 10; public uint MinConcentration { get => _minConcentration; set { if (value < 10) value = 10; _minConcentration = value; } } private readonly bool[] _reagentInUse; private readonly List _costSortedReagents; public PaintRecipe? TestRecipe { get; set; } 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? WriteLog = null; private uint _maxReagents; public uint MaxReagents { get => _maxReagents; set { if (value < 1) value = 1; _maxReagents = value; CurrentWeights = new uint[_maxReagents]; } } public uint[] CurrentWeights { get; private set; } public uint LastReagent => _reagents[_nextReagentPos - 1]; public RecipeSearchNode(RecipeSearchNode other) { _costSortedReagents = new List(other._costSortedReagents); _reagents = new uint[_costSortedReagents.Count]; _invalidReagent = (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.ReagentCount; CurrentTargetQuantity = other.CurrentTargetQuantity; MaxConcentration = other.MaxConcentration; UsedQuantity = other.UsedQuantity; CatalystCount = other.CatalystCount; FullQuantityDepth = other.FullQuantityDepth; 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 costSortedReagents, uint[] reagents) { _costSortedReagents = new List(costSortedReagents); _reagents = new uint[costSortedReagents.Count]; _invalidReagent = (uint)costSortedReagents.Count; _nextReagentPos = reagents.Length; for (int i = _reagents.Length - 1; i >= _reagents.Length; --i) { reagents[i] = _invalidReagent; } for (int i = reagents.Length - 1; i >= 0; --i) { _reagents[i] = reagents[i]; if (reagents[i] == _invalidReagent) { _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 != _invalidReagent) { _reagentInUse[reagentIdx] = true; } } MinReagents = (uint) _nextReagentPos; MaxReagents = (uint) _nextReagentPos; CurrentWeights = new uint[MaxReagents]; UsedQuantity = 0; RefreshShouldLog(); } // top-level search public RecipeSearchNode(List costSortedReagents, uint startReagent) { _costSortedReagents = new List(costSortedReagents); _reagents = new uint[costSortedReagents.Count]; _invalidReagent = (uint)costSortedReagents.Count; _nextReagentPos = 0; for (int i = 0; i < _reagents.Length; ++i) { _reagents[i] = _invalidReagent; } _reagentInUse = new bool[costSortedReagents.Count]; for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx) { _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 costSortedReagents) { _costSortedReagents = costSortedReagents; _reagents = new uint[costSortedReagents.Count]; _invalidReagent = (uint)costSortedReagents.Count; _nextReagentPos = 0; for (int i = 0; i < _reagents.Length; ++i) { _reagents[i] = _invalidReagent; } _reagentInUse = new bool[costSortedReagents.Count]; for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx) { _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() { 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]; 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() { if (ReagentCount >= MaxReagents) return false; uint nextReagent = NextFreeReagent(0); _reagents[_nextReagentPos++] = nextReagent; if (_costSortedReagents[(int)nextReagent].IsCatalyst) { ++CatalystCount; } InitForQuantity(CurrentTargetQuantity); return true; } public void InitForQuantity(uint quantity) { //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; 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; } 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 costSortedReagents; // new on construct: PaintRecipe testRecipe = null; writer.WriteLine("CurrentTargetQuantity: {0}", CurrentTargetQuantity); writer.WriteLine("MinConcentration: {0}", MinConcentration); writer.WriteLine("MaxConcentration: {0}", MaxConcentration); writer.WriteLine("UsedQuantity: {0}", UsedQuantity); writer.WriteLine("CatalystCount: {0}", CatalystCount); writer.WriteLine("FullQuantity: {0}", FullQuantity); writer.WriteLine("FullQuantityDepth: {0}", FullQuantityDepth); writer.WriteLine("---EndNode---"); } static readonly Regex keyValueRegex = new Regex(@"(\w+)\:\s*(.*)\s*$"); static readonly Regex reagentPartsRegex = new Regex(@"(?\d+),(?\d+),(?\d+)"); public bool LoadState(StreamReader reader) { string? line = reader.ReadLine(); if ((line == null) || !line.Equals("---SearchNode---")) { return false; } bool success = true; while ((line = reader.ReadLine()) != null) { if (line.Equals("---EndNode---")) { break; } Match 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] = _invalidReagent; _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 != _invalidReagent) { _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); MaxConcentration = value; } break; case "MinConcentration": { uint value = uint.Parse(match.Groups[2].Value); MinConcentration = value; } break; case "MaxConcentration": { uint value = uint.Parse(match.Groups[2].Value); MaxConcentration = 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; } } 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(); } } }