Source Code
The five C++ files of October 2015, and what was changed in them in 2026
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.
| File | What it does | In 2026 |
|---|---|---|
Source.cpp | 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. | Changed in 20266 lines added, none removed |
Header.h | 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. | Unchanged since 2015 |
Evolution.cpp | 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. | Unchanged since 2015 |
Type.cpp | 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. | Unchanged since 2015 |
Output.cpp | 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. | 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 2026The 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.
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 2015The 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.
#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 2015The 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.
#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 2015From 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.
#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 2015What 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.
#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("<");
else if (line[i] == '>') buffer.append(">");
else if (line[i] == '&') buffer.append("&");
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;
}
}