public FitnessInfo Evaluate(IBlackBox phenome)
        {
            var fitness = 0.0;

            foreach (var world in worlds)
            {
                var lidar      = createLidarFor(world.Track);
                var agent      = new NeuralNetworkAgent(lidar, phenome);
                var simulation = new Simulation.Simulation(agent, world);

                var simulationResult = simulation.Simulate(simulationStep, perceptionStep, maximumSimulationTime);
                fitness += fitnessOf(simulationResult);
            }

            lock (evaluationLock)
            {
                EvaluationCount++;
            }

            return(new FitnessInfo(
                       fitness: fitness / worlds.Length,
                       new AuxFitnessInfo[0]));
        }
        public static void Main(string[] args)
        {
            var random   = new Random();
            var circuits = circuitsFilePaths().ToArray();

            var perceptionStep        = TimeSpan.FromSeconds(0.1);
            var simulationStep        = TimeSpan.FromSeconds(0.05); // 20Hz
            var maximumSimulationTime = TimeSpan.FromSeconds(60);

            var tracks = circuits.Select(circuitPath => Track.Load($"{circuitPath}/circuit_definition.json"));
            var worlds = tracks.Select(track => new StandardWorld(track, simulationStep)).ToArray();

            var inputSamplesCount       = 3;
            var maximumScanningDistance = 200;

            ILidar createLidarFor(ITrack track)
            => new Lidar(track, inputSamplesCount, Angle.FromDegrees(135), maximumScanningDistance);

            var settings = new EvolutionSettings
            {
                PopulationSize      = 1000,
                SpeciesCount        = 30,
                ElitismProportion   = 0,
                ComplexityThreshold = 50
            };

            // prepare simulation
            var parameters = new NeatEvolutionAlgorithmParameters
            {
                ElitismProportion = settings.ElitismProportion,
                SpecieCount       = settings.SpeciesCount
            };

            var distanceMetric  = new ManhattanDistanceMetric(1.0, 0.0, 10.0);
            var parallelOptions = new ParallelOptions {
                MaxDegreeOfParallelism = 4
            };
            var speciationStrategy           = new ParallelKMeansClusteringStrategy <NeatGenome>(distanceMetric, parallelOptions);
            var complexityRegulationStrategy = new DefaultComplexityRegulationStrategy(ComplexityCeilingType.Absolute, settings.ComplexityThreshold);

            var evolutionaryAlgorithm = new NeatEvolutionAlgorithm <NeatGenome>(
                parameters,
                speciationStrategy,
                complexityRegulationStrategy);

            var phenomeEvaluator = new RaceSimulationEvaluator(
                random,
                simulationStep,
                perceptionStep,
                maximumSimulationTime,
                worlds,
                createLidarFor);

            var genomeDecoder       = new NeatGenomeDecoder(NetworkActivationScheme.CreateAcyclicScheme());
            var genomeListEvaluator = new ParallelGenomeListEvaluator <NeatGenome, IBlackBox>(
                genomeDecoder,
                phenomeEvaluator);

            evolutionaryAlgorithm.Initialize(
                genomeListEvaluator,
                genomeFactory: new NeatGenomeFactory(
                    inputNeuronCount: inputSamplesCount,
                    outputNeuronCount: 2,
                    DefaultActivationFunctionLibrary.CreateLibraryNeat(new BipolarSigmoid()),
                    new NeatGenomeParameters
            {
                FeedforwardOnly                     = true,
                AddNodeMutationProbability          = 0.03,
                DeleteConnectionMutationProbability = 0.05,
                ConnectionWeightMutationProbability = 0.08,
                FitnessHistoryLength                = 10,
            }),
                settings.PopulationSize);

            var lastVisualization = DateTimeOffset.Now;

            evolutionaryAlgorithm.UpdateEvent += onUpdate;
            evolutionaryAlgorithm.StartContinue();

            Console.WriteLine("Press enter to stop the evolution.");
            Console.ReadLine();
            Console.WriteLine("Finishing the evolution.");

            evolutionaryAlgorithm.Stop();
            Console.WriteLine("Evolution is stopped.");

            // simulate best individual
            Console.WriteLine("Simulating best individual...");
            evaluate(evolutionaryAlgorithm.CurrentChampGenome);
            Console.WriteLine("Done.");

            void onUpdate(object sender, EventArgs e)
            {
                Console.WriteLine($"Generation #{evolutionaryAlgorithm.CurrentGeneration}");
                Console.WriteLine($"- max fitness:  {evolutionaryAlgorithm.Statistics._maxFitness}");
                Console.WriteLine($"- mean fitness: {evolutionaryAlgorithm.Statistics._meanFitness}");
                Console.WriteLine();

                if (DateTimeOffset.Now - lastVisualization > TimeSpan.FromSeconds(35))
                {
                    lastVisualization = DateTimeOffset.Now;
                    Console.WriteLine("Simulating currently best individual...");
                    evaluate(evolutionaryAlgorithm.CurrentChampGenome);
                }
            }

            void evaluate(NeatGenome genome)
            {
                var worldId = random.Next(0, worlds.Length - 1);
                var world   = worlds[worldId];

                var bestIndividual = genomeDecoder.Decode(genome);
                var agent          = new NeuralNetworkAgent(createLidarFor(world.Track), bestIndividual);
                var simulation     = new Simulation.Simulation(agent, world);
                var summary        = simulation.Simulate(simulationStep, perceptionStep, maximumSimulationTime);

                File.Copy($"{circuits[worldId]}/visualization.svg", "C:/Users/simon/Projects/racer-experiment/simulator/src/visualization.svg", overwrite: true);
                IO.Simulation.StoreResult(world.Track, world.VehicleModel, summary, "", "C:/Users/simon/Projects/racer-experiment/simulator/src/report.json");
            }
        }