Write the small methods your bot is allowed to call. GeneCodeCS breeds random programs from them, compiles each one in memory, scores it with your fitness function, and lets the best ones reproduce.
# .NET 10 SDK (library targets .NET 8 and 10)
git clone https://github.com/EddPorter/GeneCodeCS
cd GeneCodeCS && dotnet test
This is a small working model of what the library does, running in your browser. Each card is one bot’s program drawn as a tree. Press play, or step through a generation one stage at a time.
The task: a counter starts at 0. Each bot’s program runs 20 times. The bot is fitter the closer the counter finishes to 10 (fitness = 1000 − distance). The building blocks are Increment(n), Decrement(n) and the question BelowTarget(). No bot is told how to combine them.
The demo is a miniature written in JavaScript for this page. It uses the same ingredients as the library (tree genomes, tournament selection, subtree crossover, mutation, elitism and random newcomers) but isn’t the library itself. The C# below is.
You supply the vocabulary and the scoring. The library does the evolving.
Public void methods on your bot become actions. Public bool methods become decisions. Parameters are evolvable constants you control the range of.
The first generation is random expression trees built from those parts, with Grow or Full initialisation up to a depth you choose.
Each tree is turned into a delegate with System.Linq.Expressions, in memory. There are no files to write and no assemblies to leak.
Your Evaluate sets Fitness. Fitter bots are more likely to be parents; subtrees are swapped, nodes occasionally mutated, and the loop repeats.
The demo above is this C# class. Nothing else describes the problem.
public class CounterBot : BaseBot { private int _steps; public int Counter { get; private set; } // Actions: public void methods1 public void Increment(StepParameter s)2 { Counter += s.Value; } public void Decrement(StepParameter s) { Counter -= s.Value; } // Decisions: public bool methods3 public bool BelowTarget() { return Counter < 10; } protected override bool NotFinished()4 { return ++_steps <= 20; } protected override void Evaluate()5 { Fitness = 1000 - Math.Abs(10 - Counter); } } // An evolvable constant, kept to the range 1 to 3 public class StepParameter : IParameter<int> { public StepParameter(Random r)6 { Value = r.Next(1, 4); } public StepParameter(int value) { Value = value; } public int Value { get; private set; } } // Evolve var population = new Population<CounterBot>(); var best = population.SimulateGenerations<object>(null, generationLimit: 30, botsPerGeneration: 60, fitnessThreshold: 999);7
Your methods are the gene pool. The library finds them by reflection. void methods become the leaves of a program tree.
Parameters are part of the genome. Each Increment in a tree carries its own evolved step size.
Decisions make branches. A bool method becomes an if/else with a subtree on each side.
You say when a bot is done. The evolved program runs in a loop until NotFinished returns false.
You define “good”. Set Fitness from whatever the bot did. Higher is better. A bot that throws is scored int.MinValue.
Two constructors. One takes a Random to pick the starting value, so you control the range. The other restores a stored value when the tree is compiled.
One call evolves it. You get back the best bot or bots, each with its tree, name, parents and compiled instance.
| You write | The library treats it as | Shown in the demo as |
|---|---|---|
public void Increment(StepParameter step) | A terminal gene (a leaf), with one evolvable parameter | A green +n node |
public bool BelowTarget() | A branch gene: if / else | A blue Below? node with yes and no subtrees |
| Several genes in a row | A sequence, run top to bottom | A grey then node |
Evaluate() | The fitness function | The bar on each card |
| Setting | What it does | Default |
|---|---|---|
botsPerGeneration, generationLimit, maxTreeDepth | Population size, how long to run, how large the first random programs can be | 30, 20, 3 |
fitnessThreshold | Stop early once a bot beats this fitness | Never |
InitialisationMethod | Grow mixes functions and terminals; Full fills every level | Grow |
SelectionMethod | Tournament, or FitnessProportionate (chance of parenthood in proportion to fitness) | Tournament |
CrossoverRate, MutationRate | Chance, 0–100, of swapping subtrees or mutating during breeding | 90, 5 |
RandomBotPercentage | Share of each new generation made of fresh random bots, to keep diversity up | 20 |
starterBots | Seed the first generation with programs you already have, or the output of an earlier run | None |
| Optimise callback | Simplify each new tree (for example, remove impossible branches) before it is compiled | None |
| Random seed | Make a run reproducible | Time-based |
bool and void methods, not typed expressions that return numbers.