namespace MrGameEng.Genetics; /// /// An individual's managed genome: a variable-composition map from gene id to the /// pair it carries. Because composition is open, two organisms need not share /// the same gene set and a genome can gain "foreign" genes — the basis for arbitrary hybrids. The /// genome is generated from a set of s, bred meiotically with mutation, and /// expressed into phenotype values; all randomness flows through a caller-owned seeded /// so the simulation stays deterministic. /// public sealed class Genome { private readonly Dictionary _alleles; /// Creates an empty genome. public Genome() => _alleles = new Dictionary(StringComparer.Ordinal); /// Creates a genome from an existing allele map (copied). public Genome(IReadOnlyDictionary alleles) => _alleles = new Dictionary(alleles, StringComparer.Ordinal); /// The carried genes and their allele pairs. public IReadOnlyDictionary Alleles => _alleles; /// Whether the genome carries the gene . public bool Has(string geneId) => _alleles.ContainsKey(geneId); /// Gets or sets the allele pair for . public Allele this[string geneId] { get => _alleles[geneId]; set => _alleles[geneId] = value; } /// Removes a gene from the genome; returns whether it was present. public bool Remove(string geneId) => _alleles.Remove(geneId); /// /// Expresses the gene's phenotype value: the mean of the alleles for a numeric gene, the /// dominant (lower) allele for a discrete one. Throws if the genome does not carry the gene. /// public float Express(GeneDef gene) { if (!_alleles.TryGetValue(gene.DefName, out var allele)) { throw new KeyNotFoundException($"Genome does not carry gene '{gene.DefName}'."); } return gene.Kind == GeneKind.Numeric ? allele.Mean : allele.Dominant; } /// Builds the allele map for serialization (a copy). public Dictionary ToDictionary() => new(_alleles, StringComparer.Ordinal); /// /// Generates a fresh genome carrying every gene in , each allele drawn /// independently around the gene's default with its spread (numeric) or from its variant /// distribution (discrete). /// public static Genome Generate(IEnumerable genes, Random random) { var genome = new Genome(); foreach (var gene in genes) { genome._alleles[gene.DefName] = new Allele( GenerateAllele(gene, random), GenerateAllele(gene, random) ); } return genome; } /// /// Breeds a child genome from two parents (meiosis): the child carries every gene either parent /// has. For a gene both carry, one allele is drawn from each parent; for a gene only one parent /// carries, it is inherited (from that parent, on both sides) with 50% probability. Every /// inherited allele may then mutate per its . /// supplies the def for each gene id; genes absent from it are skipped. /// , when given, overrides every gene's /// — letting the caller drive mutation from an evolvable /// trait rather than a fixed per-gene constant. /// public static Genome Breed( Genome a, Genome b, IReadOnlyDictionary registry, Random random, float? mutationChance = null ) { var child = new Genome(); foreach (var geneId in UnionKeys(a, b)) { if (!registry.TryGetValue(geneId, out var gene)) { continue; } var chance = mutationChance ?? gene.MutationChance; var inA = a.Has(geneId); var inB = b.Has(geneId); if (inA && inB) { child._alleles[geneId] = new Allele( Meiosis(gene, a[geneId], chance, random), Meiosis(gene, b[geneId], chance, random) ); } else if (random.NextSingle() < 0.5f) { var parent = inA ? a : b; child._alleles[geneId] = new Allele( Meiosis(gene, parent[geneId], chance, random), Meiosis(gene, parent[geneId], chance, random) ); } } return child; } // One inherited allele: pick one of the parent slot's two alleles, then maybe mutate it. private static float Meiosis(GeneDef gene, Allele parent, float mutationChance, Random random) { var inherited = random.NextSingle() < 0.5f ? parent.A : parent.B; if (random.NextSingle() >= mutationChance) { return inherited; } if (gene.Kind == GeneKind.Discrete) { return GeneSampling.Variant(gene, null, random); } var shifted = inherited + (random.NextSingle() * 2f - 1f) * gene.MutationMagnitude * MathF.Abs(inherited); return Math.Clamp(shifted, gene.Min, gene.Max); } private static float GenerateAllele(GeneDef gene, Random random) => gene.Kind == GeneKind.Discrete ? GeneSampling.Variant(gene, null, random) : GeneSampling.Numeric(gene, gene.Default, gene.Spread, random); // Deterministic union of both parents' gene ids (ordered) so breeding is reproducible. private static IEnumerable UnionKeys(Genome a, Genome b) { var keys = new SortedSet(StringComparer.Ordinal); keys.UnionWith(a._alleles.Keys); keys.UnionWith(b._alleles.Keys); return keys; } }