Index: /cpp/frams/_demos/evol_test.cpp
===================================================================
--- /cpp/frams/_demos/evol_test.cpp	(revision 1007)
+++ /cpp/frams/_demos/evol_test.cpp	(revision 1008)
@@ -5,4 +5,5 @@
 
 #include <vector>
+#include <numeric> //std::accumulate()
 #include "common/loggers/loggertostdout.h"
 #include "frams/genetics/preconfigured.h"
@@ -22,5 +23,5 @@
 }
 
-void evaluate_fitness(Individual &ind, const char *fitness_def)
+double get_fitness(const Individual &ind, const char *fitness_def)
 {
 	SString genotype = ind.geno.getGenes();
@@ -32,4 +33,9 @@
 		switch (*p)
 		{
+		case '0':
+			break;
+		case '!': //special symbol for current fitness (used only in printing population stats)
+			fitness += ind.fitness;
+			break;
 		case 'l':
 		case 'L':
@@ -59,5 +65,22 @@
 		p++;
 	}
-	ind.fitness = fitness;
+	return fitness;
+}
+
+void update_fitness(Individual &ind, const char *fitness_def)
+{
+	ind.fitness = get_fitness(ind, fitness_def);
+}
+
+void print_stats(const vector<Individual> &population, char criterion)
+{
+	vector<double> criterion_values;
+	char crit[2] = { 0 };
+	crit[0] = criterion;
+	for (const Individual& ind : population)
+		criterion_values.push_back(get_fitness(ind, crit));
+	printf("%g,%g,%g", *std::min_element(criterion_values.begin(), criterion_values.end()),
+		std::accumulate(criterion_values.begin(), criterion_values.end(), 0.0) / criterion_values.size(),
+		*std::max_element(criterion_values.begin(), criterion_values.end()));
 }
 
@@ -76,5 +99,5 @@
 
 
-/** A minimalistic steady-state evolutionary algorithm. */
+// A minimalistic steady-state evolutionary algorithm.
 int main(int argc, char *argv[])
 {
@@ -93,7 +116,8 @@
 		printf("Too few parameters!\n");
 		printf("Command line: <deterministic?_0_or_1> <population_size> <nr_evaluations> <prob_mut> <prob_xover> <genetic_format> <fitness_definition>\n");
-		printf("Example: 1 10 50 0.5 0.5 4 NC\n\n\n");
+		printf("Example: 1 10 50 0.6 0.4 4 NC\n\n");
 		printf("Fitness definition is a sequence of capital (+1 weight) and small (-1 weight) letters.\n");
 		printf("Each letter corresponds to one fitness criterion, and they are all weighted and added together.\n");
+		printf("  0      - a constant value of 0 that provides a flat fitness landscape (e.g. for testing biases of genetic operators).\n");
 		printf("  l or L - genotype length in characters.\n");
 		printf("  p or P - the number of Parts.\n");
@@ -103,4 +127,8 @@
 		//TODO add b - bounding box volume (from Model), s - surface area (from geometry), v - volume (from geometry), h,w,d - three consecutive dimensions (from geometry)
 
+		printf("\nThe output contains 7 columns separated by the TAB character.\n");
+		printf("The first column is the number of mutated or crossed over and evaluated genotypes.\n");
+		printf("The remaining columns are triplets of min,avg,max (in the population) of fitness, Parts, Joints, Neurons, Connections, genotype characters.\n");
+		printf("Finally, the genotypes in the last population are printed with their fitness values.\n");
 		return 1;
 	}
@@ -126,5 +154,5 @@
 			return 2;
 		}
-		evaluate_fitness(ind, fitness_def);
+		update_fitness(ind, fitness_def);
 	}
 	for (int i = 0; i < nr_evals; i++)
@@ -136,15 +164,40 @@
 		if (rnd < prob_mut)
 		{
-			population[selected_negative].geno = genman.mutate(population[selected_positive].geno);
-			//TODO handle failed mutation
-			evaluate_fitness(population[selected_negative], fitness_def);
+			Geno mutant = genman.mutate(population[selected_positive].geno);
+			if (mutant.getGenes() == "")
+			{
+				printf("Failed mutation (%s) of '%s'\n", mutant.getComment().c_str(), population[selected_positive].geno.getGenes().c_str());
+			}
+			else
+			{
+				population[selected_negative].geno = mutant;
+				update_fitness(population[selected_negative], fitness_def);
+			}
 		}
 		else
 		{
-			//TODO crossover
+			int selected_positive2 = tournament(population, max(2, int(sqrt(population.size()) / 2)));
+			Geno xover = genman.crossOver(population[selected_positive].geno, population[selected_positive2].geno);
+			if (xover.getGenes() == "")
+			{
+				printf("Failed crossover (%s) of '%s' and '%s'\n", xover.getComment().c_str(), population[selected_positive].geno.getGenes().c_str(), population[selected_positive2].geno.getGenes().c_str());
+			}
+			else
+			{
+				population[selected_negative].geno = xover;
+				update_fitness(population[selected_negative], fitness_def);
+			}
 		}
 
 		if (i % population.size() == 0 || i == nr_evals - 1)
-			printf("Evaluation %d\t...\n", i); //TODO print min,avg,max fitness \t min,avg,max genotype length \t min,avg,max parts \t min,avg,max neurons
+		{
+			printf("Evaluation %d\t", i);
+			for (char c : string("!PJNCL"))
+			{
+				print_stats(population, c);
+				printf("\t");
+			}
+			printf("\n");
+		}
 	}
 	for (const Individual& ind : population)
