diff --git a/simulation_parameters/Constants.cs b/simulation_parameters/Constants.cs index d92cf2f9445..997596b25ac 100644 --- a/simulation_parameters/Constants.cs +++ b/simulation_parameters/Constants.cs @@ -1186,7 +1186,7 @@ public static class Constants // Mutation Variables public const int MAX_VARIANTS_PER_MUTATION = 50; - public const int MAX_VARIANTS_IN_MUTATIONS = 100; + public const int MAX_VARIANTS_IN_MUTATIONS = 250; public const float MUTATION_BACTERIA_TO_EUKARYOTE = 0.01f; public const float MUTATION_CREATION_RATE = 0.25f; public const float MUTATION_NEW_ORGANELLE_CHANCE = 0.25f; diff --git a/simulation_parameters/common/auto-evo_parameters.json b/simulation_parameters/common/auto-evo_parameters.json index 22888e2274d..f7243ec49fb 100644 --- a/simulation_parameters/common/auto-evo_parameters.json +++ b/simulation_parameters/common/auto-evo_parameters.json @@ -1,5 +1,5 @@ { - "MutationsPerSpecies": 3, + "MutationsPerSpecies": 5, "MoveAttemptsPerSpecies": 8, "StrictNicheCompetition": true } diff --git a/src/auto-evo/mutation_strategy/AddOrganelleAnywhere.cs b/src/auto-evo/mutation_strategy/AddOrganelleAnywhere.cs index d11cdea73de..07c670a2e54 100644 --- a/src/auto-evo/mutation_strategy/AddOrganelleAnywhere.cs +++ b/src/auto-evo/mutation_strategy/AddOrganelleAnywhere.cs @@ -3,17 +3,17 @@ using System; using System.Collections.Generic; using System.Linq; +using static CommonMutationFunctions; /// /// Adds a random, valid organelle to a valid position. Doesn't place multicellular or later organelles. /// public class AddOrganelleAnywhere : IMutationStrategy { - private readonly CommonMutationFunctions.Direction direction; + private readonly Direction direction; private readonly OrganelleDefinition[] allOrganelles; - public AddOrganelleAnywhere(Func criteria, CommonMutationFunctions.Direction direction - = CommonMutationFunctions.Direction.Neutral) + public AddOrganelleAnywhere(Func criteria, Direction direction = Direction.Neutral) { allOrganelles = SimulationParameters.Instance.GetAllOrganelles().Where(criteria).Where(IsOrganelleValid) .ToArray(); @@ -26,15 +26,15 @@ public AddOrganelleAnywhere(Func criteria, CommonMuta // Formatter and inspect code disagree here // ReSharper disable InvokeAsExtensionMethod public static AddOrganelleAnywhere ThatUseCompound(CompoundDefinition compound, - CommonMutationFunctions.Direction direction = CommonMutationFunctions.Direction.Neutral) + Direction direction = Direction.Neutral) { return new AddOrganelleAnywhere( organelle => Enumerable.Any(organelle.RunnableProcesses, proc => proc.Process.Inputs.ContainsKey(compound)), direction); } - public static AddOrganelleAnywhere ThatUseCompound(Compound compound, CommonMutationFunctions.Direction direction - = CommonMutationFunctions.Direction.Neutral) + public static AddOrganelleAnywhere ThatUseCompound(Compound compound, Direction direction + = Direction.Neutral) { var compoundResolved = SimulationParameters.GetCompound(compound); @@ -42,7 +42,7 @@ public static AddOrganelleAnywhere ThatUseCompound(Compound compound, CommonMuta } public static AddOrganelleAnywhere ThatCreateCompound(CompoundDefinition compound, - CommonMutationFunctions.Direction direction = CommonMutationFunctions.Direction.Neutral) + Direction direction = Direction.Neutral) { return new AddOrganelleAnywhere(organelle => Enumerable.Any(organelle.RunnableProcesses, proc => proc.Process.Outputs.ContainsKey(compound)), @@ -50,7 +50,7 @@ public static AddOrganelleAnywhere ThatCreateCompound(CompoundDefinition compoun } public static AddOrganelleAnywhere ThatCreateCompound(Compound compound, - CommonMutationFunctions.Direction direction = CommonMutationFunctions.Direction.Neutral) + Direction direction = Direction.Neutral) { var compoundResolved = SimulationParameters.GetCompound(compound); @@ -59,7 +59,7 @@ public static AddOrganelleAnywhere ThatCreateCompound(Compound compound, public static AddOrganelleAnywhere ThatConvertBetweenCompounds(CompoundDefinition fromCompound, CompoundDefinition toCompound, - CommonMutationFunctions.Direction direction = CommonMutationFunctions.Direction.Neutral) + Direction direction = Direction.Neutral) { return new AddOrganelleAnywhere(organelle => Enumerable.Any(organelle.RunnableProcesses, proc => proc.Process.Inputs.ContainsKey(fromCompound) && @@ -69,7 +69,7 @@ public static AddOrganelleAnywhere ThatConvertBetweenCompounds(CompoundDefinitio // ReSharper restore InvokeAsExtensionMethod public static AddOrganelleAnywhere ThatConvertBetweenCompounds(Compound fromCompound, Compound toCompound, - CommonMutationFunctions.Direction direction = CommonMutationFunctions.Direction.Neutral) + Direction direction = Direction.Neutral) { var fromCompoundResolved = SimulationParameters.GetCompound(fromCompound); var toCompoundResolved = SimulationParameters.GetCompound(toCompound); @@ -77,7 +77,7 @@ public static AddOrganelleAnywhere ThatConvertBetweenCompounds(Compound fromComp return ThatConvertBetweenCompounds(fromCompoundResolved, toCompoundResolved, direction); } - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { // If a cheaper organelle gets added, this will need to be updated @@ -93,7 +93,7 @@ public static AddOrganelleAnywhere ThatConvertBetweenCompounds(Compound fromComp var organelles = allOrganelles.OrderBy(_ => random.Next()) .Take(Constants.AUTO_EVO_ORGANELLE_ADD_ATTEMPTS); - var mutated = new List>(); + var mutated = new List(); // TODO: reuse this memory somehow var workMemory1 = new List(); @@ -119,10 +119,10 @@ public static AddOrganelleAnywhere ThatConvertBetweenCompounds(Compound fromComp // In the rare case that adding the organelle fails, this can skip adding it to be tested as the species // is not any different - if (CommonMutationFunctions.AddOrganelle(organelle, direction, newSpecies, workMemory1, workMemory2, + if (AddOrganelle(organelle, direction, newSpecies, workMemory1, workMemory2, workMemory3, random)) { - mutated.Add(Tuple.Create(newSpecies, mp - organelle.MPCost)); + mutated.Add(new Mutant(newSpecies, mp - organelle.MPCost)); } } diff --git a/src/auto-evo/mutation_strategy/ChangeBehaviorScore.cs b/src/auto-evo/mutation_strategy/ChangeBehaviorScore.cs index 3c1f13b497e..b6f4df7fe28 100644 --- a/src/auto-evo/mutation_strategy/ChangeBehaviorScore.cs +++ b/src/auto-evo/mutation_strategy/ChangeBehaviorScore.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using static CommonMutationFunctions; public class ChangeBehaviorScore : IMutationStrategy { @@ -26,7 +27,7 @@ public enum BehaviorAttribute // As it cost no MP the mutation code could just repeat this forever public bool Repeatable => false; - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { // TODO: Make random something passed in @@ -61,6 +62,6 @@ public enum BehaviorAttribute break; } - return [Tuple.Create(newSpecies, mp)]; + return [new Mutant(newSpecies, mp)]; } } diff --git a/src/auto-evo/mutation_strategy/ChangeMembraneRigidity.cs b/src/auto-evo/mutation_strategy/ChangeMembraneRigidity.cs index 044206a6905..2345e7d4037 100644 --- a/src/auto-evo/mutation_strategy/ChangeMembraneRigidity.cs +++ b/src/auto-evo/mutation_strategy/ChangeMembraneRigidity.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using static CommonMutationFunctions; public class ChangeMembraneRigidity : IMutationStrategy { @@ -14,7 +15,7 @@ public ChangeMembraneRigidity(bool lower) public bool Repeatable => true; - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { const float change = Constants.AUTO_EVO_MUTATION_RIGIDITY_STEP; @@ -42,6 +43,6 @@ public ChangeMembraneRigidity(bool lower) newSpecies.MembraneRigidity = Math.Clamp(newSpecies.MembraneRigidity, -1, 1); - return [Tuple.Create(newSpecies, mp - mpCost)]; + return [new Mutant(newSpecies, mp - mpCost)]; } } diff --git a/src/auto-evo/mutation_strategy/ChangeMembraneType.cs b/src/auto-evo/mutation_strategy/ChangeMembraneType.cs index e4e403c7afa..818d6bc8b90 100644 --- a/src/auto-evo/mutation_strategy/ChangeMembraneType.cs +++ b/src/auto-evo/mutation_strategy/ChangeMembraneType.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using static CommonMutationFunctions; public class ChangeMembraneType : IMutationStrategy { @@ -14,7 +15,7 @@ public ChangeMembraneType(string membraneType) public bool Repeatable => false; - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { if (baseSpecies.MembraneType == membraneType) @@ -27,6 +28,6 @@ public ChangeMembraneType(string membraneType) newSpecies.MembraneType = membraneType; - return [Tuple.Create(newSpecies, mp - membraneType.EditorCost)]; + return [new Mutant(newSpecies, mp - membraneType.EditorCost)]; } } diff --git a/src/auto-evo/mutation_strategy/IMutationStrategy.cs b/src/auto-evo/mutation_strategy/IMutationStrategy.cs index 4d5746594f9..8d6144cd20a 100644 --- a/src/auto-evo/mutation_strategy/IMutationStrategy.cs +++ b/src/auto-evo/mutation_strategy/IMutationStrategy.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; +using static CommonMutationFunctions; public interface IMutationStrategy where T : Species @@ -27,6 +28,6 @@ public interface IMutationStrategy /// List of mutated species, null if no possible mutations are found (some strategies may return an empty list /// instead in this case) /// - public List>? MutationsOf(T baseSpecies, double mp, bool lawk, Random random, + public List? MutationsOf(T baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider); } diff --git a/src/auto-evo/mutation_strategy/ModifyEnvironmentalTolerance.cs b/src/auto-evo/mutation_strategy/ModifyEnvironmentalTolerance.cs index 715424c6034..3a900332cf0 100644 --- a/src/auto-evo/mutation_strategy/ModifyEnvironmentalTolerance.cs +++ b/src/auto-evo/mutation_strategy/ModifyEnvironmentalTolerance.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using static CommonMutationFunctions; public class ModifyEnvironmentalTolerance : IMutationStrategy { @@ -12,7 +13,7 @@ public class ModifyEnvironmentalTolerance : IMutationStrategy /// public bool Repeatable => false; - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { if (mp <= 0) @@ -216,6 +217,6 @@ public class ModifyEnvironmentalTolerance : IMutationStrategy newSpecies.Tolerances.SanityCheck(); #endif - return [Tuple.Create(newSpecies, mp)]; + return [new Mutant(newSpecies, mp)]; } } diff --git a/src/auto-evo/mutation_strategy/MoveOrganelleBack.cs b/src/auto-evo/mutation_strategy/MoveOrganelleBack.cs index 40cd4342563..e23ffdf5e9d 100644 --- a/src/auto-evo/mutation_strategy/MoveOrganelleBack.cs +++ b/src/auto-evo/mutation_strategy/MoveOrganelleBack.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AutoEvo; +using static CommonMutationFunctions; internal class MoveOrganelleBack : IMutationStrategy { @@ -15,13 +16,13 @@ public MoveOrganelleBack(Func criteria) public bool Repeatable => true; - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { if (mp < Constants.ORGANELLE_MOVE_COST) return null; - var mutated = new List>(); + var mutated = new List(); // TODO: try to avoid these temporary list allocations var workMemory1 = new List(); @@ -34,13 +35,13 @@ public MoveOrganelleBack(Func criteria) newSpecies.Organelles.Remove(organelle); - if (CommonMutationFunctions.AddOrganelle(organelle.Definition, CommonMutationFunctions.Direction.Rear, - newSpecies, workMemory1, workMemory2, workMemory3, random)) + if (AddOrganelle(organelle.Definition, Direction.Rear, newSpecies, workMemory1, workMemory2, workMemory3, + random)) { // Add mutation attempt only if was able to place the organelle // TODO: maybe this should add the attempt anyway as this may act as a separate remove organelle step // for things that cannot be moved? - mutated.Add(Tuple.Create(newSpecies, mp - Constants.ORGANELLE_MOVE_COST)); + mutated.Add(new Mutant(newSpecies, mp - Constants.ORGANELLE_MOVE_COST)); } } diff --git a/src/auto-evo/mutation_strategy/RemoveOrganelle.cs b/src/auto-evo/mutation_strategy/RemoveOrganelle.cs index 9be5277590c..61a6ea028dd 100644 --- a/src/auto-evo/mutation_strategy/RemoveOrganelle.cs +++ b/src/auto-evo/mutation_strategy/RemoveOrganelle.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using static CommonMutationFunctions; public class RemoveOrganelle : IMutationStrategy { @@ -46,16 +47,19 @@ public static RemoveOrganelle ThatCreateCompound(Compound compound) // ReSharper restore InvokeAsExtensionMethod - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { if (mp < Constants.ORGANELLE_REMOVE_COST) return null; + if (baseSpecies.Organelles.Count <= 1) + return null; + var organelles = baseSpecies.Organelles.Where(x => Criteria(x.Definition)) .OrderBy(_ => random.Next()).Take(Constants.AUTO_EVO_ORGANELLE_REMOVE_ATTEMPTS); - List>? mutated = null; + List? mutated = null; MutationWorkMemory? workMemory = null; @@ -88,10 +92,10 @@ public static RemoveOrganelle ThatCreateCompound(Compound compound) newSpecies.Organelles.AddIfPossible(newOrganelle, workMemory.WorkingMemory1, workMemory.WorkingMemory2); } - CommonMutationFunctions.AttachIslandHexes(newSpecies.Organelles, workMemory); + AttachIslandHexes(newSpecies.Organelles, workMemory); - mutated ??= new List>(); - mutated.Add(Tuple.Create(newSpecies, mp - Constants.ORGANELLE_REMOVE_COST)); + mutated ??= new List(); + mutated.Add(new Mutant(newSpecies, mp - Constants.ORGANELLE_REMOVE_COST)); } return mutated; diff --git a/src/auto-evo/mutation_strategy/UpgradeOrganelle.cs b/src/auto-evo/mutation_strategy/UpgradeOrganelle.cs index d9298892b0e..3a06e9227cd 100644 --- a/src/auto-evo/mutation_strategy/UpgradeOrganelle.cs +++ b/src/auto-evo/mutation_strategy/UpgradeOrganelle.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AutoEvo; +using static CommonMutationFunctions; public class UpgradeOrganelle : IMutationStrategy { @@ -32,7 +33,7 @@ public UpgradeOrganelle(Func criteria, string upgrade public bool Repeatable => false; - public List>? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, + public List? MutationsOf(MicrobeSpecies baseSpecies, double mp, bool lawk, Random random, BiomeConditions biomeToConsider) { if (allOrganelles.Count == 0) @@ -58,7 +59,7 @@ public UpgradeOrganelle(Func criteria, string upgrade if (validMutations) { - var mutated = new List>(); + var mutated = new List(); var newSpecies = (MicrobeSpecies)baseSpecies.Clone(); @@ -84,7 +85,7 @@ public UpgradeOrganelle(Func criteria, string upgrade } } - mutated.Add(new Tuple(newSpecies, mp)); + mutated.Add(new Mutant(newSpecies, mp)); return mutated; } diff --git a/src/auto-evo/mutations/CommonMutationFunctions.cs b/src/auto-evo/mutations/CommonMutationFunctions.cs index d68dcfb53b5..a1446117304 100644 --- a/src/auto-evo/mutations/CommonMutationFunctions.cs +++ b/src/auto-evo/mutations/CommonMutationFunctions.cs @@ -85,8 +85,8 @@ public static MicrobeSpecies GenerateRandomSpecies(MicrobeSpecies mutated, Patch if (mutation == null) break; - mutated = mutation.Item1; - mp -= mutation.Item2; + mutated = mutation.Species; + mp -= mutation.MP; var oldColour = mutated.Colour; @@ -444,4 +444,6 @@ private static Hex.HexSide[] SideTraversalOrder(Hex hex, Direction direction, Ra return TraversalOrder8; } + + public record Mutant(MicrobeSpecies Species, double MP); } diff --git a/src/auto-evo/selection_pressure/CompoundConversionEfficiencyPressure.cs b/src/auto-evo/selection_pressure/CompoundConversionEfficiencyPressure.cs index 71c09618a4a..b608353ab56 100644 --- a/src/auto-evo/selection_pressure/CompoundConversionEfficiencyPressure.cs +++ b/src/auto-evo/selection_pressure/CompoundConversionEfficiencyPressure.cs @@ -1,5 +1,6 @@ namespace AutoEvo; +using System; using Newtonsoft.Json; [JSONDynamicTypeAllowed] @@ -30,8 +31,8 @@ public class CompoundConversionEfficiencyPressure : SelectionPressure public CompoundConversionEfficiencyPressure(Compound compound, Compound outCompound, float weight, bool usedForSurvival) : base(weight, [ - AddOrganelleAnywhere.ThatConvertBetweenCompounds(compound, outCompound), RemoveOrganelle.ThatCreateCompound(outCompound), + AddOrganelleAnywhere.ThatConvertBetweenCompounds(compound, outCompound), ]) { this.compound = compound; @@ -54,7 +55,10 @@ public override float Score(Species species, Patch patch, SimulationCache cache) // we need to factor in both conversion from source to output, and energy expenditure time if (usedForSurvival) - score /= cache.GetEnergyBalanceForSpecies(microbeSpecies, patch.Biome).TotalConsumptionStationary; + { + score /= + MathF.Sqrt(cache.GetEnergyBalanceForSpecies(microbeSpecies, patch.Biome).TotalConsumptionStationary); + } return score; } diff --git a/src/auto-evo/steps/GenerateMiche.cs b/src/auto-evo/steps/GenerateMiche.cs index 623a8b4ee20..872acfa66e8 100644 --- a/src/auto-evo/steps/GenerateMiche.cs +++ b/src/auto-evo/steps/GenerateMiche.cs @@ -148,19 +148,20 @@ public Miche GenerateMicheTree(AutoEvoGlobalCache globalCache) generatedMiche.AddChild(tempMiche); } - var predationRoot = new Miche(globalCache.PredatorRoot); - var predationGlucose = new Miche(globalCache.MinorGlucoseConversionEfficiencyPressure); - - // Heterotrophic Miches - foreach (var possiblePrey in patch.SpeciesInPatch) + if (patch.SpeciesInPatch.Count > 1) { - predationGlucose.AddChild(new Miche(new PredationEffectivenessPressure(possiblePrey.Key, 1.0f))); - } + var predationRoot = new Miche(globalCache.PredatorRoot); + var predationGlucose = new Miche(globalCache.MinorGlucoseConversionEfficiencyPressure); - if (patch.SpeciesInPatch.Count > 1) - predationRoot.AddChild(predationGlucose); + // Heterotrophic Miches + foreach (var possiblePrey in patch.SpeciesInPatch) + { + predationGlucose.AddChild(new Miche(new PredationEffectivenessPressure(possiblePrey.Key, 1.0f))); + } - generatedMiche.AddChild(predationRoot); + predationRoot.AddChild(predationGlucose); + generatedMiche.AddChild(predationRoot); + } return rootMiche; } diff --git a/src/auto-evo/steps/ModifyExistingSpecies.cs b/src/auto-evo/steps/ModifyExistingSpecies.cs index 0788513611d..7b1084d71bb 100644 --- a/src/auto-evo/steps/ModifyExistingSpecies.cs +++ b/src/auto-evo/steps/ModifyExistingSpecies.cs @@ -5,6 +5,7 @@ using System.Linq; using Godot; using Xoshiro.PRNG64; +using static CommonMutationFunctions; /// /// Uses miches to create a mutation for an existing species. Also creates new species from existing ones as @@ -34,21 +35,15 @@ public class ModifyExistingSpecies : IRunStep private readonly List predatorCalculationMemory2 = new(); private readonly List predatorPressuresTemporary = new(); - private readonly List> tempMutationStrategies = new(); - - // TODO: switch to named tuple elements - private readonly List> temporaryMutations1 = new(); - private readonly List> temporaryMutations2 = new(); + private readonly List temporaryMutations1 = new(); + private readonly List temporaryMutations2 = new(); private readonly List lastGeneratedMutations = new(); - private readonly List currentTraversal = new(); - - private readonly List temporaryPressures = new(); - - private readonly List> temporaryResultForTopMutations = new(); + private readonly Stack pressureStack = new(); private readonly MutationSorter mutationSorter; + private readonly GenerateMutationsWorkingMemory generateMutationsWorkingMemory = new(); private readonly Random random; private readonly List mutationsToTry = new(); @@ -169,13 +164,21 @@ public bool RunStep(RunResults results) // Then shuffle and take only as many mutations as we want to try mutationsToTry.Shuffle(random); - while (mutationsToTry.Count > TotalMutationsToTry) + // Rename and colour selected mutations + foreach (var mutation in mutationsToTry.Take(TotalMutationsToTry)) { - mutationsToTry.RemoveAt(mutationsToTry.Count - 1); - } + MutationLogicFunctions.NameNewMicrobeSpecies(mutation.MutatedSpecies, mutation.ParentSpecies); + + var oldColour = mutation.MutatedSpecies.Colour; + + var redShift = (random.NextDouble() - 0.5f) * Constants.AUTO_EVO_COLOR_CHANGE_MAX_STEP; + var greenShift = (random.NextDouble() - 0.5f) * Constants.AUTO_EVO_COLOR_CHANGE_MAX_STEP; + var blueShift = (random.NextDouble() - 0.5f) * Constants.AUTO_EVO_COLOR_CHANGE_MAX_STEP; + + mutation.MutatedSpecies.Colour = new Color(Math.Clamp((float)(oldColour.R + redShift), 0, 1), + Math.Clamp((float)(oldColour.G + greenShift), 0, 1), + Math.Clamp((float)(oldColour.B + blueShift), 0, 1)); - foreach (var mutation in mutationsToTry) - { mutation.MutatedSpecies.OnEdited(); } @@ -238,16 +241,17 @@ public bool RunStep(RunResults results) return false; } - private static void PruneMutations(List> addResultsTo, MicrobeSpecies baseSpecies, - List> mutated, Patch patch, SimulationCache cache, - List selectionPressures) + private static void PruneMutations(List addResultsTo, MicrobeSpecies baseSpecies, + List mutated, Patch patch, SimulationCache cache, + Stack selectionPressures) { foreach (var potentialVariant in mutated) { var combinedScores = 0.0; foreach (var pastPressure in selectionPressures) { - var newScore = cache.GetPressureScore(pastPressure, patch, potentialVariant.Item1); + // Caching a score for a species very likely to be pruned wastes memory + var newScore = pastPressure.Score(potentialVariant.Species, patch, cache); var oldScore = cache.GetPressureScore(pastPressure, patch, baseSpecies); // Break if the mutation fails a new pressure check @@ -260,19 +264,21 @@ private static void PruneMutations(List> addResult combinedScores += pastPressure.WeightedComparedScores(newScore, oldScore); } - if (combinedScores > 0) + // Not pruning species that don't affect the score can inject more + // variety into the species generated + if (combinedScores >= 0) { addResultsTo.Add(potentialVariant); } } } - private static void GetTopMutations(List> result, - List> mutated, int amount, MutationSorter sorter) + private static void GetTopMutations(List result, + List mutated, int amount, MutationSorter sorter) { result.Clear(); - mutated.Sort(sorter); + mutated.Sort((a, b) => sorter.Compare(b, a)); foreach (var tuple in mutated) { @@ -284,62 +290,20 @@ private static void GetTopMutations(List> result, private void GetMutationsForSpecies(MicrobeSpecies microbeSpecies) { - // The traversal end up being re-calculated quite many times, but this way we avoid quite a lot of memory - // allocations - - foreach (var nonEmptyLeaf in nonEmptyLeafNodes) - { - if (nonEmptyLeaf.Occupant == microbeSpecies) - continue; - - currentTraversal.Clear(); - nonEmptyLeaf.BackTraversal(currentTraversal); - - temporaryPressures.Clear(); - foreach (var traversalMiche in currentTraversal) - { - temporaryPressures.Add(traversalMiche.Pressure); - } - - SpeciesDependentPressures(temporaryPressures, miche!, microbeSpecies); - - var variants = GenerateMutations(microbeSpecies, - worldSettings.AutoEvoConfiguration.MutationsPerSpecies, temporaryPressures); - - foreach (var variant in variants) - { - mutationsToTry.Add(new Mutation(microbeSpecies, variant, - RunResults.NewSpeciesType.SplitDueToMutation)); - } - } - - // This section of the code tries to mutate species into unfilled miches - // Not exactly realistic, but more diversity is more fun for the player - // TODO: Make this an auto-evo option - foreach (var emptyLeafNode in emptyLeafNodes) - { - currentTraversal.Clear(); - emptyLeafNode.BackTraversal(currentTraversal); + double totalMP = 100 * worldSettings.AIMutationMultiplier; - temporaryPressures.Clear(); - foreach (var traversalMiche in currentTraversal) - { - temporaryPressures.Add(traversalMiche.Pressure); - } + generateMutationsWorkingMemory.Clear(); + pressureStack.Clear(); - SpeciesDependentPressures(temporaryPressures, miche!, microbeSpecies); + SpeciesDependentPressures(pressureStack, miche!, microbeSpecies); - var variants = GenerateMutations(microbeSpecies, - worldSettings.AutoEvoConfiguration.MutationsPerSpecies, temporaryPressures); + var inputSpecies = generateMutationsWorkingMemory.GetMutationsAtDepth(0); + inputSpecies.Add(new Mutant(microbeSpecies, totalMP)); - foreach (var variant in variants) - { - mutationsToTry.Add(new Mutation(microbeSpecies, variant, RunResults.NewSpeciesType.FillNiche)); - } - } + GenerateMutations(microbeSpecies, miche!, 1); } - private void SpeciesDependentPressures(List dataReceiver, Miche targetMiche, Species species) + private void SpeciesDependentPressures(Stack dataReceiver, Miche targetMiche, Species species) { predatorPressuresTemporary.Clear(); @@ -348,7 +312,7 @@ private void SpeciesDependentPressures(List dataReceiver, Mic foreach (var predator in predatorPressuresTemporary) { // TODO: Make that weight a constant - dataReceiver.Add(new AvoidPredationSelectionPressure(predator, 5.0f)); + dataReceiver.Push(new AvoidPredationSelectionPressure(predator, 5.0f)); } } @@ -382,89 +346,61 @@ private void PredatorsOf(List result, Miche micheTree, Species species) } /// - /// Returns a new list of all possible species that might emerge in response to the provided pressures, - /// as well as a copy of the original species. + /// Adds a new list of all possible species that might emerge in response to the provided pressures, + /// as well as a copy of the original species to . /// - /// List of viable variants and the provided species - private List GenerateMutations(MicrobeSpecies baseSpecies, int amount, - List selectionPressures) + private void GenerateMutations(MicrobeSpecies baseSpecies, Miche currentMiche, int depth) { - double totalMP = 100 * worldSettings.AIMutationMultiplier; + var inputSpecies = generateMutationsWorkingMemory.GetMutationsAtDepth(depth - 1); - temporaryMutations1.Clear(); - temporaryMutations1.Add(Tuple.Create(baseSpecies, totalMP)); - var viableVariants = temporaryMutations1; + var outputSpecies = generateMutationsWorkingMemory.GetMutationsAtDepth(depth); + outputSpecies.Clear(); + outputSpecies.AddRange(inputSpecies); - // Auto-evo assumes that the order mutations are applied doesn't matter when it actually can affect - // results quite a bit. Maybe they should have weights? - tempMutationStrategies.Clear(); - - // Collect and shuffle unique strategies - foreach (var selectionPressure in selectionPressures) - { - foreach (var mutation in selectionPressure.Mutations) - { - if (!tempMutationStrategies.Contains(mutation)) - tempMutationStrategies.Add(mutation); - } - } + pressureStack.Push(currentMiche.Pressure); + mutationSorter.Setup(baseSpecies, pressureStack); + var mutations = currentMiche.Pressure.Mutations; bool lawk = worldSettings.LAWK; - mutationSorter.Setup(baseSpecies, selectionPressures); - - tempMutationStrategies.Shuffle(random); - - foreach (var mutationStrategy in tempMutationStrategies) + foreach (var mutationStrategy in mutations) { - var inputSpecies = viableVariants; + temporaryMutations1.Clear(); + temporaryMutations1.AddRange(outputSpecies); for (int i = 0; i < Constants.AUTO_EVO_MAX_MUTATION_RECURSIONS; ++i) { temporaryMutations2.Clear(); - foreach (var speciesTuple in inputSpecies) + foreach (var speciesTuple in temporaryMutations1) { // TODO: this seems like the longest part, so splitting this into multiple steps (maybe bundling // up mutation strategies) would be good to have the auto-evo steps flow more smoothly - var mutated = mutationStrategy.MutationsOf(speciesTuple.Item1, speciesTuple.Item2, lawk, random, + var mutated = mutationStrategy.MutationsOf(speciesTuple.Species, speciesTuple.MP, lawk, random, patch.Biome); if (mutated != null) { - PruneMutations(temporaryMutations2, speciesTuple.Item1, mutated, patch, cache, - selectionPressures); + PruneMutations(temporaryMutations2, speciesTuple.Species, mutated, patch, cache, + pressureStack); } } - // TODO: Make these a performance setting? - if (temporaryMutations2.Count > Constants.MAX_VARIANTS_PER_MUTATION) - { - GetTopMutations(temporaryResultForTopMutations, temporaryMutations2, - Constants.MAX_VARIANTS_PER_MUTATION / 2, mutationSorter); - - // TODO: switch to a set of rotating buffers to avoid memory allocations here - inputSpecies = temporaryResultForTopMutations.ToList(); - } - else - { - // TODO: switch to a set of rotating buffers to avoid memory allocations here - inputSpecies = new List>(); - PruneMutations(inputSpecies, baseSpecies, temporaryMutations2, patch, cache, selectionPressures); - } + temporaryMutations1.Clear(); + PruneMutations(temporaryMutations1, baseSpecies, temporaryMutations2, patch, cache, pressureStack); - viableVariants.AddRange(inputSpecies); + outputSpecies.AddRange(temporaryMutations1); - if (viableVariants.Count > Constants.MAX_VARIANTS_IN_MUTATIONS) + if (outputSpecies.Count > Constants.MAX_VARIANTS_IN_MUTATIONS) { - GetTopMutations(temporaryResultForTopMutations, viableVariants, + GetTopMutations(temporaryMutations2, outputSpecies, Constants.MAX_VARIANTS_IN_MUTATIONS / 2, mutationSorter); - // TODO: switch to a set of rotating buffers to avoid memory allocations here - viableVariants = temporaryResultForTopMutations.ToList(); + outputSpecies.Clear(); + outputSpecies.AddRange(temporaryMutations2); } - if (temporaryMutations2.Count == 0) + if (temporaryMutations1.Count == 0) break; if (!mutationStrategy.Repeatable) @@ -472,54 +408,89 @@ private List GenerateMutations(MicrobeSpecies baseSpecies, int a } } - lastGeneratedMutations.Clear(); - - GetTopMutations(temporaryResultForTopMutations, viableVariants, amount, mutationSorter); - foreach (var topMutation in temporaryResultForTopMutations) + if (currentMiche.IsLeafNode()) { - lastGeneratedMutations.Add(topMutation.Item1); - } + lastGeneratedMutations.Clear(); - // TODO: could maybe optimize things by only giving name and colour changes for mutations that are selected - // in the end - foreach (var variant in lastGeneratedMutations) - { - if (variant == baseSpecies) - continue; + GetTopMutations(temporaryMutations1, outputSpecies, worldSettings.AutoEvoConfiguration.MutationsPerSpecies, + mutationSorter); + foreach (var topMutation in temporaryMutations1) + { + lastGeneratedMutations.Add(topMutation.Species); + } - MutationLogicFunctions.NameNewMicrobeSpecies(variant, baseSpecies); + var resultType = (currentMiche.Occupant == baseSpecies) ? + RunResults.NewSpeciesType.SplitDueToMutation : + RunResults.NewSpeciesType.FillNiche; - var oldColour = variant.Colour; + foreach (var species in lastGeneratedMutations) + { + mutationsToTry.Add(new Mutation(baseSpecies, species, resultType)); + } + } + else + { + // Prune out any results that don't improve this branch + temporaryMutations1.Clear(); + PruneMutations(temporaryMutations1, baseSpecies, outputSpecies, patch, cache, pressureStack); + outputSpecies.Clear(); + outputSpecies.AddRange(temporaryMutations1); - var redShift = (random.NextDouble() - 0.5f) * Constants.AUTO_EVO_COLOR_CHANGE_MAX_STEP; - var greenShift = (random.NextDouble() - 0.5f) * Constants.AUTO_EVO_COLOR_CHANGE_MAX_STEP; - var blueShift = (random.NextDouble() - 0.5f) * Constants.AUTO_EVO_COLOR_CHANGE_MAX_STEP; + if (outputSpecies.Count != 0) + { + foreach (var child in currentMiche.Children) + { + GenerateMutations(baseSpecies, child, depth + 1); + } + } - variant.Colour = new Color(Math.Clamp((float)(oldColour.R + redShift), 0, 1), - Math.Clamp((float)(oldColour.G + greenShift), 0, 1), - Math.Clamp((float)(oldColour.B + blueShift), 0, 1)); + pressureStack.Pop(); } - - return lastGeneratedMutations; } private record struct Mutation(MicrobeSpecies ParentSpecies, MicrobeSpecies MutatedSpecies, RunResults.NewSpeciesType AddType); - private class MutationSorter(Patch patch, SimulationCache cache) : IComparer> + /// + /// Working memory used to reduce memory allocations in . + /// + private class GenerateMutationsWorkingMemory + { + private readonly List> currentSpecies = new(); + + public List GetMutationsAtDepth(int depth) + { + while (currentSpecies.Count <= depth) + currentSpecies.Add(new List()); + + var result = currentSpecies[depth]; + + return result; + } + + public void Clear() + { + foreach (var speciesList in currentSpecies) + { + speciesList.Clear(); + } + } + } + + private class MutationSorter(Patch patch, SimulationCache cache) : IComparer { // This isn't the cleanest but this class is just optimized for performance so if someone forgets to set up // this then bad things will happen - private List pressures = null!; + private IEnumerable pressures = null!; private MicrobeSpecies baseSpecies = null!; - public void Setup(MicrobeSpecies species, List selectionPressures) + public void Setup(MicrobeSpecies species, IEnumerable selectionPressures) { pressures = selectionPressures; baseSpecies = species; } - public int Compare(Tuple? x, Tuple? y) + public int Compare(Mutant? x, Mutant? y) { if (ReferenceEquals(x, y)) return 0; @@ -533,10 +504,10 @@ public int Compare(Tuple? x, Tuple? x, Tuple strengthX) return -1; - // Second float in tuple is apparently compared in ascending order - // TODO: switch to named tuples to figure out what is going on here - if (x.Item2 > y.Item2) - return -1; - - if (x.Item2 < y.Item2) + if (x.MP > y.MP) return 1; + if (x.MP < y.MP) + return -1; + return 0; } }