You may reuse this code: it is under the MIT licence. The licence covers the code on this page, not the text of the Q&A nor the design of the pages; Hamlet is in the public domain.

October 2015, version 2.1
FileWhat it doesIn 2026
Source.cppThe whole program in one loop: for ever, type Hamlet line after line and, for each line, evaluate, select, breed and mutate the troop until one monkey types it.Changed in 20266 lines added, none removed
Header.hThe constants (70 characters to a line, 20 monkeys, the mutation rate), what a monkey, the book and the troop are made of, and the list of the functions.Unchanged since 2015
Evolution.cppThe genetic algorithm itself: the random engine, the reading of the book, and the four steps repeated at each generation, which are evaluation, selection, reproduction and mutation.Unchanged since 2015
Type.cppFrom genes to characters: the decoder that reads a finger, its condition and the shift key in twelve bits, and the keyboard that says which key that finger reaches.Unchanged since 2015
Output.cppWhat visitors saw: it wrote the text files that the pages of 2015 read several times a second, with the lines typed, the best attempt at the current line and the statistics of each line.Unchanged since 2015

Written in October 2015

The program of 2015

Version 2.1: the five files below are the whole program as it was published in 2015. Each can be downloaded as it was, byte for byte. A file changed in 2026 is shown in full as one diff: a line removed from the 2015 file has a red background and a − in the margin, a line added in 2026 a green background and a +, and the other lines are those of 2015. Its 2026 version can be downloaded too.

The 2015 files have Windows line ends (CR LF) and no line end after their last line. Line ends are not shown in the listings and are left out of the comparison: a line counts as changed only when its text differs, spaces and tabs included. Each download keeps the line ends of its file.

Source.cpp

Changed in 2026

The whole program in one loop: for ever, type Hamlet line after line and, for each line, evaluate, select, breed and mutate the troop until one monkey types it.

Download the 2015 file · October 2015, v. 2.1 · 788 bytes, Windows line ends, none after the last line · SHA-256 78d03deb66870dbbf5a59fa85990f4c5fad8e4f0620071b469ee705b7a989175

Download the 2026 file · November 2026 · 1,164 bytes, Windows line ends, none after the last line · SHA-256 2d0ad7b4cc2802c44127177da5f6c9cf381e66f837ef95d63c406071cc615770

In 2026: 6 lines added, none removed. The 2015 file has 52 lines, the 2026 file 58. A removed line is marked − on red, an added line + on green.

/* www.glthr.com/20monkeys
   Guillaume Lethuillier
   Oct. 2015  v.2.1      
*/

#include "Header.h"

// 2026: the two things this program asks the browser (monkeys-worker.js answers)
extern "C" int line_to_type();    // the line of the book to type now, counted from 0
extern "C" void back_to_the_browser();  // shows the attempt; waits when the monkeys go faster than the server of 2016

int main()
{
	bool next { false };
	std::vector<Primate> monkeys(POPULATION * 2);
	Population troop;
	Text book;

	initialize(monkeys, book, troop);
	
	for (;;) 
	{
		restart(book, troop);
		log(monkeys, book, troop, 0);

		do
		{
			book.current_line = line_to_type(); // 2026
			reset(monkeys, troop);
			
			do
			{ 
				back_to_the_browser(); // 2026
				evaluation(monkeys, book, troop, next);

				selection(monkeys, book, troop);

				if (next) break;

				reproduction(monkeys);

				++troop.generations;

				mutation(monkeys);

			} while (!next);

			log(monkeys, book, troop, 2);
	
			next = false;
			
			++book.current_line;
	
		} while (book.current_line < book.lines.size());

		++book.editions;
	}
}

Header.h

Unchanged since 2015

The constants (70 characters to a line, 20 monkeys, the mutation rate), what a monkey, the book and the troop are made of, and the list of the functions.

Download Header.h · October 2015, v. 2.1 · 1,302 bytes, Windows line ends, none after the last line · SHA-256 b27387a94576330386aab4ef036ba9649b95b58392635e9edaea6466a026e770

#include <random>
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>
#include <tuple>
#include <iomanip>

const unsigned int
  LENGTH = 70
, POPULATION = 20
, MUTATION = 1
;

struct Primate
{
	std::string dna, line;
	enum fingers
	{
		  lPinky, lRing, lMiddle, lIndex, lThumb
		, rThumb, rIndex, rMiddle, rRing, rPinky
	};

	double intelligence;
	bool best;	
};

struct Text
{
	std::vector<std::string> lines;
	unsigned int editions; // number of cycles
	int current_line;
};

struct Population
{
	bool better;
	int best, generations;
	double fitness_reference;
};

void log(std::vector<Primate>& monkeys, Text& book, Population& troop, int selector);
std::tuple<int, double, bool> decode(std::string& dna, int& idx);
char keyboard(int& first, double& second, bool& third);

void initialize(std::vector<Primate>& monkey, Text& book, Population& troop);
void restart(Text& book, Population& troop);
void reset(std::vector<Primate>& monkeys, Population& troop);

void evaluation(std::vector<Primate>& monkeys, Text& book, Population& troop, bool& next);
void selection(std::vector<Primate>& monkeys, Text& book, Population& troop);
void reproduction(std::vector<Primate>& monkeys);
void mutation(std::vector<Primate>& monkeys);

Evolution.cpp

Unchanged since 2015

The genetic algorithm itself: the random engine, the reading of the book, and the four steps repeated at each generation, which are evaluation, selection, reproduction and mutation.

Download Evolution.cpp · October 2015, v. 2.1 · 4,971 bytes, Windows line ends, none after the last line · SHA-256 a488ce29349063c6ea871094f55871240520ea83af05e149e42b996314458aba

#include "Header.h"

std::random_device rd;
std::mt19937 engine(rd());

std::uniform_int_distribution<int> randna(0, 1), randomonkey(0, POPULATION), randomutation(0, 100);

void initialize(std::vector<Primate>& monkeys, Text& book, Population& troop)
{
	// Text of reference
	std::ifstream file("book.txt");
	std::string str;

	while (std::getline(file, str))
	{
		while (str.length() < LENGTH)
			str += " ";

		book.lines.emplace_back(str);
	}
	book.editions = 0;

	// Reads the number of cycles
	std::ifstream read_log("log.txt");
	if (read_log.good())
		read_log >> book.editions;

	// Initialization of DNA
	std::string blank(LENGTH * 12, '0');
	for (int i = 0; i < POPULATION * 2; ++i)
		monkeys[i].dna = blank;
}

void restart(Text& book, Population& troop)
{
	book.current_line = 0;
	troop.better = false;

	remove("progress.txt");
	remove("progress_tmp.txt");
	remove("progress_light.txt");

	for (int i = 0; i < book.lines.size(); ++i)
	{
		remove(("stats/snapshot" + std::to_string(i) + ".txt").c_str());
		remove(("stats/basics" + std::to_string(i) + ".txt").c_str());
		remove(("stats/DNA" + std::to_string(i) + ".txt").c_str());
	}
}

/* Before searching for the next sentence, the DNA is randomized */
void reset(std::vector<Primate>& monkeys, Population& troop)
{
	troop.generations = 0;
	troop.fitness_reference = 0.0;

	for (int i = 0; i < POPULATION; ++i)
		for (int j = 0; j < LENGTH * 12; ++j)
			monkeys[i].dna[j] = randna(engine) + '0';
	
	remove("progress_line.txt");
}

void evaluation(std::vector<Primate>& monkeys, Text& book, Population& troop, bool& next)
{
	int i;

	for (i = 0; i < POPULATION; ++i)
	{
		monkeys[i].line.clear();
		monkeys[i].intelligence = 0.0;
		bool shift = false; char c;

		for (int j = 0; j < LENGTH; ++j)
		{
			auto expression = decode(monkeys[i].dna, j);
			c = keyboard(std::get<0>(expression), std::get<1>(expression), std::get<2>(expression));
			monkeys[i].line += c;
			
			// Comparison char by char between the sentence generated by the monkey and the expected output
			monkeys[i].intelligence += abs(monkeys[i].line[j] - book.lines[book.current_line][j]);
		}
		// The only information given to the monkeys: this score
		monkeys[i].intelligence = 1 / pow(monkeys[i].intelligence + 1, 2);
	}
	
	for (i = 0; i < POPULATION; ++i)
	{
		if (monkeys[i].intelligence == 1)
		{
			troop.better = true;
			troop.best = i;
			next = true;
			break;
		}
	}
}

void selection(std::vector<Primate>& monkeys, Text& book, Population& troop)
{
	int i;

	for (i = 0; i < POPULATION * 2; ++i)
		monkeys[i].best = false;

	std::vector<double> fitness;

	for (i = 0; i < POPULATION; ++i)
		fitness.emplace_back(monkeys[i].intelligence);

	sort(std::begin(fitness), std::end(fitness));

	for (i = 0; i < POPULATION; ++i)
		if (monkeys[i].intelligence == fitness.back())
		{
			monkeys[i].best = true;
			break;
		}

	troop.better = false;

	if (fitness.back() > troop.fitness_reference)
	{
		troop.fitness_reference = fitness.back();

		for (i = 0; i < POPULATION; ++i)
			if (monkeys[i].best)
			{
				troop.better = true;
				troop.best = i;
				log(monkeys, book, troop, 1);
				break;
			}
	}
}

void reproduction(std::vector<Primate>& monkeys)
{
	int i, j, k;
	std::vector<int> breeders;

	// ELITE: best monkey is cloned twice
	for (i = 0; i < POPULATION; ++i)
		if (monkeys[i].best == true)
		{
			breeders.emplace_back(i);
			monkeys[POPULATION].dna = monkeys[i].dna;
			monkeys[POPULATION + 1].dna = monkeys[i].dna;
			break;
		}

	// ROULETTE
	double total_fitness = 0.0;

	for (i = 0; i < POPULATION; ++i)
		total_fitness += monkeys[i].intelligence;

	std::uniform_real_distribution<double> roulette(0.0, total_fitness);

	double r;

	do
	{
		i = 0;
		r = roulette(engine);

		do
		{
			r -= monkeys[i].intelligence;
			++i;
		} while (r > 0);

		breeders.emplace_back(i - 1);

	} 
	while (breeders.size() < POPULATION);

	// 50/50 DNA CROSSOVER: 2 babies per couple
	for (i = 2; i < POPULATION; i+=2)
	{
		k = randomonkey(engine);

		for (j = 0; j < (LENGTH * 12) / 2; ++j)
		{
			monkeys[i + POPULATION].dna[j] = monkeys[breeders[i]].dna[j];
			monkeys[i + POPULATION + 1].dna[j] = monkeys[k].dna[j];
		}
		
		for (; j < LENGTH * 12; ++j)
		{
			monkeys[i + POPULATION].dna[j] = monkeys[k].dna[j];
			monkeys[i + POPULATION + 1].dna[j] = monkeys[breeders[i]].dna[j];
		}			
	}
	breeders.clear();
}

void mutation(std::vector<Primate>& monkeys)
{
	int i, j, k;

	for (i = 1; i < POPULATION; ++i)
		for (j = 0; j < LENGTH; ++j)
			if (randomutation(engine) < MUTATION)
				for (k = j * 12; k < j * 12 + 12; ++k)
					monkeys[i + POPULATION].dna[k] = randna(engine) + '0';

	// Baby monkeys become adults of next generation
	for (i = 0; i < POPULATION; ++i)
		monkeys[i].dna = monkeys[i + POPULATION].dna;
}

Type.cpp

Unchanged since 2015

From genes to characters: the decoder that reads a finger, its condition and the shift key in twelve bits, and the keyboard that says which key that finger reaches.

Download Type.cpp · October 2015, v. 2.1 · 7,118 bytes, Windows line ends, none after the last line · SHA-256 afa15edc89cb8efd469c935bf5e833303614dc56c8a81636ec004d74ec9753b9

#include "Header.h"

/* Genes decoder: genotype -> phenotype */
std::tuple<int, double, bool> decode(std::string& dna, int& it)
{
	int i;
	std::string genes;

	// Finger
	for (i = it * 12; i < (it * 12) + 4; ++i)
		genes += dna[i];

	int first = std::stoi(genes, nullptr, 2);

	// Condition of the finger (folded or not)
	genes = "";

	for (; i < (it * 12) + 11; ++i)
		genes += dna[i];

	double second = (double)std::stoi(genes, nullptr, 2)/100;

	// Lowercase or uppercase
	bool third = dna[(it * 12) + 11] != '0';

	return std::make_tuple(first, second, third);
}


/* What key is pressed according to the condition
    of the finger */
bool pos(int& first, double& second, int index)
{
	switch (first)
	{
		// 4 positions
	case Primate::lRing:
	case Primate::lMiddle:
	case Primate::rMiddle:
	case Primate::rRing:
		if (second >= .25 * index && second < .25 * (index + 1))
			return true;
		else
			return false;
		break;

		// 6 positions
	case Primate::lPinky:
		if (second >= .167 * index && second < .167 * (index + 1))
			return true;
		else
			return false;
		break;

		// 8 positions
	case Primate::lIndex:
	case Primate::rIndex:
		if (second >= .125 * index && second < .125 * (index + 1))
			return true;
		else
			return false;
		break;

		// 10 positions
	case Primate::rPinky:
		if (second >= .1 * index && second < .1 * (index + 1))
			return true;
		else
			return false;
		break;
	}
}


/* Keyboard ANSI-INCITS 154-1988 (US standard) 'emulator' */
char keyboard(int& first, double& second, bool& third)
{
	// Default: null
	char key = char(0); 

	if (!third)
	{
		switch (first)
		{
		case Primate::lPinky:
			if	(pos(first, second, 5))	key = '`';
			else if (pos(first, second, 4)) key = '1';
			else if (pos(first, second, 3)) key = '\t';
			else if (pos(first, second, 2)) key = 'q';
			else if (pos(first, second, 1)) key = 'a';
			else if (pos(first, second, 0)) key = 'z';
			break;

		case Primate::lRing:
			if	(pos(first, second, 3))	key = '2';
			else if (pos(first, second, 2)) key = 'w';
			else if (pos(first, second, 1)) key = 's';
			else if (pos(first, second, 0)) key = 'x';
			break;

		case Primate::lMiddle:
			if	(pos(first, second, 3))	key = '3';
			else if (pos(first, second, 2)) key = 'e';
			else if (pos(first, second, 1)) key = 'd';
			else if (pos(first, second, 0)) key = 'c';
			break;

		case Primate::lIndex:
			if	(pos(first, second, 7))	key = '4';
			else if (pos(first, second, 6)) key = '5';
			else if (pos(first, second, 5)) key = 'r';
			else if (pos(first, second, 4)) key = 't';
			else if (pos(first, second, 3)) key = 'f';
			else if (pos(first, second, 2)) key = 'g';
			else if (pos(first, second, 1)) key = 'v';
			else if (pos(first, second, 0))	key = 'b';
			break;

		case Primate::lThumb:
		case Primate::rThumb:
			key = ' ';
			break;

		case Primate::rIndex:
			if	(pos(first, second, 7))	key = '6';
			else if (pos(first, second, 6)) key = '7';
			else if (pos(first, second, 5)) key = 'y';
			else if (pos(first, second, 4)) key = 'u';
			else if (pos(first, second, 3)) key = 'h';
			else if (pos(first, second, 2)) key = 'j';
			else if (pos(first, second, 1)) key = 'n';
			else if (pos(first, second, 0)) key = 'm';
			break;

		case Primate::rMiddle:
			if	(pos(first, second, 3))	key = '8';
			else if (pos(first, second, 2)) key = 'i';
			else if (pos(first, second, 1)) key = 'k';
			else if (pos(first, second, 0)) key = ',';
			break;

		case Primate::rRing:
			if	(pos(first, second, 3))	key = '9';
			else if (pos(first, second, 2)) key = 'o';
			else if (pos(first, second, 1)) key = 'l';
			else if (pos(first, second, 0)) key = '.';
			break;

		case Primate::rPinky:
			if	(pos(first, second, 9))	key = '0';
			else if (pos(first, second, 8)) key = '-';
			else if (pos(first, second, 7)) key = '=';
			else if (pos(first, second, 6)) key = 'p';
			else if (pos(first, second, 5)) key = '[';
			else if (pos(first, second, 4)) key = ']';
			else if (pos(first, second, 3)) key = '\\';
			else if (pos(first, second, 2)) key = ';';
			else if (pos(first, second, 1)) key = '\'';
			else if (pos(first, second, 0)) key = '/';
			break;

		default:
			// If the gene is repressed: null
			key = char(0); 
			break;
		}
	}
	else
	{
		switch (first)
		{
		case Primate::lPinky:
			if	(pos(first, second, 5))	key = '~';
			else if (pos(first, second, 4)) key = '!';
			else if (pos(first, second, 3)) key = '\t';
			else if (pos(first, second, 2)) key = 'Q';
			else if (pos(first, second, 1)) key = 'A';
			else if (pos(first, second, 0)) key = 'Z';
			break;

		case Primate::lRing:
			if	(pos(first, second, 3))	key = '@';
			else if (pos(first, second, 2)) key = 'W';
			else if (pos(first, second, 1)) key = 'S';
			else if (pos(first, second, 0)) key = 'X';
			break;

		case Primate::lMiddle:
			if	(pos(first, second, 3))	key = '#';
			else if (pos(first, second, 2)) key = 'E';
			else if (pos(first, second, 1)) key = 'D';
			else if (pos(first, second, 0)) key = 'C';
			break;

		case Primate::lIndex:
			if	(pos(first, second, 7))	key = '$';
			else if (pos(first, second, 6)) key = '%';
			else if (pos(first, second, 5)) key = 'R';
			else if (pos(first, second, 4)) key = 'T';
			else if (pos(first, second, 3)) key = 'F';
			else if (pos(first, second, 2)) key = 'G';
			else if (pos(first, second, 1)) key = 'V';
			else if (pos(first, second, 0)) key = 'B';
			break;

		case Primate::lThumb:
		case Primate::rThumb:
			key = ' ';
			break;

		case Primate::rIndex:
			if	(pos(first, second, 7))	key = '^';
			else if (pos(first, second, 6)) key = '&';
			else if (pos(first, second, 5)) key = 'Y';
			else if (pos(first, second, 4)) key = 'U';
			else if (pos(first, second, 3)) key = 'H';
			else if (pos(first, second, 2)) key = 'J';
			else if (pos(first, second, 1)) key = 'N';
			else if (pos(first, second, 0)) key = 'M';
			break;

		case Primate::rMiddle:
			if	(pos(first, second, 3))	key = '*';
			else if (pos(first, second, 2)) key = 'I';
			else if (pos(first, second, 1)) key = 'K';
			else if (pos(first, second, 0)) key = '<';
			break;

		case Primate::rRing:
			if	(pos(first, second, 3))	key = '(';
			else if (pos(first, second, 2)) key = 'O';
			else if (pos(first, second, 1)) key = 'L';
			else if (pos(first, second, 0)) key = '>';
			break;

		case Primate::rPinky:
			if	(pos(first, second, 9))	key = ')';
			else if (pos(first, second, 8)) key = '_';
			else if (pos(first, second, 7)) key = '+';
			else if (pos(first, second, 6)) key = 'P';
			else if (pos(first, second, 5)) key = '{';
			else if (pos(first, second, 4)) key = '}';
			else if (pos(first, second, 3)) key = '|';
			else if (pos(first, second, 2)) key = ':';
			else if (pos(first, second, 1)) key = '\"';
			else if (pos(first, second, 0)) key = '?';
			break;

		default:
			// If the gene is repressed: null
			key = char(0); 
			break;
		}
	}

	return key;
}

Output.cpp

Unchanged since 2015

What visitors saw: it wrote the text files that the pages of 2015 read several times a second, with the lines typed, the best attempt at the current line and the statistics of each line.

Download Output.cpp · October 2015, v. 2.1 · 4,156 bytes, Windows line ends, none after the last line · SHA-256 6e85174c7934dbe08b2da44fc520c2d753ec5aef400be841a02968ceb28222f9

#include "Header.h"
#include <iomanip>


void htmlspecialchars(std::string& line)
{
	std::string buffer;
	buffer.reserve(line.size());
	for (int i = 0; i < line.size(); ++i)
	{
		if (line[i] == '<') buffer.append("&#60;");
		else if (line[i] == '>') buffer.append("&#62;");
		else if (line[i] == '&') buffer.append("&#38;");
		else buffer.append(&line[i], 1);
	}
	line.swap(buffer);
}

void log(std::vector<Primate>& monkeys, Text& book, Population& troop, int selector)
{
	switch (selector)
	{
		case 0:
		{	
			std::ofstream log("log.txt", std::ios::trunc);
			log << std::to_string(book.editions);
			log.close();
		}
		break;
		
		case 1:
		{
			htmlspecialchars(monkeys[troop.best].line);

			std::string file_progression = "stats/snapshot" + std::to_string(book.current_line + 1) + ".txt";
			std::ofstream progression(file_progression, std::ios::app);
			progression << "<span id=\"line\">(" << std::setprecision(8) << std::fixed;
			progression << monkeys[troop.best].intelligence << ")  " << monkeys[troop.best].line << "</span>" << std::endl;
			progression.close();

			std::ofstream line("progress_line.txt", std::ios::trunc);
			line << "\t\t" << monkeys[troop.best].line;
			line.close();
		}
		break;

		case 2:
		{
			std::vector<std::string> fingers_images
			{
				"lp", "lr", "lm", "li", "lt"
				, "rt", "ri", "rm", "rr", "rp"
				, "null"
			};

			std::vector<std::string> fingers_names
			{
				"left pinky", "left ring", "left middle", "left index", "left thumb"
				, "right thumb", "right index", "right middle", "right ring", "right pinky"
				, "null"
			};

			std::vector<std::string> lettercase{ "<img src='blank.png'>", "<img src='shift.png' title='shift key pressed'>" };

			std::ofstream progress("progress.txt", std::ios::app);

			if (book.current_line != 0) progress << std::endl;
			progress << troop.generations << "\t<a href = 'stats.php?id=" << std::to_string(book.current_line + 1);
			progress << "' onclick = \"window.open(this.href,'targetWindow', 'toolbar = no, location = no, scrollbars = yes, width = 600, height = 800');";
			progress << "return false; \"><img src='info.png' style='border: 0px'></a>\t";
			progress << monkeys[troop.best].line << "\t\t" << book.current_line + 1;

			progress.close();

			std::ofstream progress_light_tmp("progress_tmp.txt", std::ios::trunc);
			std::string line;
			std::ifstream progress_read("progress.txt");

			int count = 0;
			while (getline(progress_read, line))
			{
				if (count >= book.current_line - 50)
					progress_light_tmp << line << std::endl;
				++count;
			}

			progress_light_tmp.close();
			progress_read.close();

			std::ifstream tmp("progress_tmp.txt");
			std::ofstream progress_light("progress_light.txt", std::ios::trunc);

			progress_light << tmp.rdbuf();

			progress_light.close();
			tmp.close();

			std::string file_basics = "stats/basics" + std::to_string(book.current_line + 1) + ".txt";
			std::ofstream basics(file_basics, std::ios::trunc);
			basics << monkeys[troop.best].line << std::endl << troop.generations;
			basics.close();

			std::string file_DNA = "stats/DNA" + std::to_string(book.current_line + 1) + ".txt";
			std::ofstream DNA(file_DNA, std::ios::app);

			for (int i = 0; i < LENGTH; ++i)
			{
				auto expression = decode(monkeys[troop.best].dna, i);

				DNA << "<span id=\"line\">Gene " << i+1 << "  \t";
				for (int j = i * 12; j < i * 12 + 12; j++) DNA << monkeys[troop.best].dna[j];
				DNA << std::setw(25) << "  <img src='fingers/" << fingers_images[std::get<0>(expression)];
				DNA << ".png' title=\'" << fingers_names[std::get<0>(expression)] << "\'>";
				DNA << std::setw(5);

				// Fix for the display of the conditions of the thumbs when > 1
				if (std::get<1>(expression) <= 1)
					DNA << std::get<1>(expression);
				else
					DNA << std::get<1>(expression) - 1;

				DNA << "  " << std::setw(10);
				DNA << lettercase[std::get<2>(expression)] << std::setw(14) << monkeys[troop.best].line[i] << "</span>" << std::endl;

			}

			DNA.close();	
		}
		break;
	}
}