├── sketch.properties ├── sprites.png ├── LinearAlgebra.pde ├── GameObject.pde ├── Enemy.pde ├── main.pde ├── Genome.pde ├── readme.md ├── Brain.pde ├── Dino.pde └── Simulation.pde /sketch.properties: -------------------------------------------------------------------------------- 1 | main=main.pde 2 | -------------------------------------------------------------------------------- /sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santifiorino/dino-reinforcement-learning/HEAD/sprites.png -------------------------------------------------------------------------------- /LinearAlgebra.pde: -------------------------------------------------------------------------------- 1 | float[] matrix_vector_multiplication(float[][] matrix, float[] vector) { 2 | float[] result = new float[matrix.length]; 3 | for (int i = 0; i < matrix.length; i++) { 4 | float sum = 0; 5 | for (int j = 0; j < matrix[0].length; j++) { 6 | sum += matrix[i][j] * vector[j]; 7 | } 8 | result[i] = sum; 9 | } 10 | return result; 11 | } 12 | 13 | float[][] zeroes_matrix(int rows, int cols) { 14 | float[][] matrix = new float[rows][cols]; 15 | for (int i = 0; i < rows; i++) { 16 | matrix[i] = new float[cols]; 17 | for (int j = 0; j < cols; j++) { 18 | matrix[i][j] = 0; 19 | } 20 | } 21 | return matrix; 22 | } 23 | 24 | float[] random_vector(int size) { 25 | float[] vector = new float[size]; 26 | for (int i = 0; i < size; i++) { 27 | vector[i] = random(-1, 1); 28 | } 29 | return vector; 30 | } 31 | -------------------------------------------------------------------------------- /GameObject.pde: -------------------------------------------------------------------------------- 1 | abstract class GameObject { 2 | 3 | float x_pos, y_pos; 4 | float obj_width, obj_height; 5 | String sprite; 6 | int[] sprite_offset = {0, 0}; 7 | 8 | void print() { 9 | // fill(0); 10 | // rect(x_pos, y_pos, obj_width, obj_height); // hitbox 11 | image(game_sprites.get(sprite), x_pos + sprite_offset[0], y_pos + sprite_offset[1]); 12 | } 13 | 14 | boolean is_collisioning_with(GameObject anObject) { 15 | return (x_pos + obj_width > anObject.x_pos && x_pos < anObject.x_pos + anObject.obj_width) && 16 | (y_pos + obj_height > anObject.y_pos && y_pos < anObject.y_pos + anObject.obj_height); 17 | } 18 | 19 | void toggle_sprite() {} 20 | 21 | } 22 | 23 | class Ground extends GameObject { 24 | 25 | Ground() { 26 | x_pos = 2400; 27 | y_pos = 515; 28 | sprite = "ground"; 29 | } 30 | 31 | void update(int speed) { 32 | x_pos -= speed; 33 | if (x_pos <= 0) { 34 | x_pos = 2400; 35 | } 36 | } 37 | 38 | void print() { 39 | image(game_sprites.get(sprite), x_pos, y_pos); 40 | image(game_sprites.get(sprite), x_pos - 2400, y_pos); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Enemy.pde: -------------------------------------------------------------------------------- 1 | abstract class Enemy extends GameObject { 2 | 3 | Enemy() { 4 | x_pos = 1350; 5 | } 6 | 7 | void update(int speed) { 8 | x_pos -= speed; 9 | } 10 | 11 | boolean is_offscreen() { 12 | return x_pos + obj_width < 0; 13 | } 14 | } 15 | 16 | class Cactus extends Enemy { 17 | 18 | int type; 19 | int[] cactus_widths = {30, 64, 98, 46, 96, 146}; 20 | int[] cactus_heights = {66, 66, 66, 96, 96, 96}; 21 | int[] cactus_y_pos = {470, 470, 470, 444, 444, 444}; 22 | 23 | Cactus() { 24 | type = (int)random(6); 25 | obj_width = cactus_widths[type]; 26 | obj_height = cactus_heights[type]; 27 | y_pos = cactus_y_pos[type]; 28 | sprite = "cactus_type_" + (type + 1); 29 | sprite_offset[0] = -2; 30 | sprite_offset[1] = -2; 31 | } 32 | 33 | } 34 | 35 | class Bird extends Enemy { 36 | 37 | int type; 38 | int[] birds_y_pos = {435, 480, 370}; 39 | 40 | Bird() { 41 | x_pos = 1350; 42 | obj_width = 84; 43 | obj_height = 40; 44 | type = (int)random(3); 45 | y_pos = birds_y_pos[type]; 46 | sprite = "bird_flying_1"; 47 | sprite_offset[0] = -4; 48 | sprite_offset[1] = -16; 49 | } 50 | 51 | void toggle_sprite() { 52 | if (sprite.equals("bird_flying_1")) { 53 | sprite = "bird_flying_2"; 54 | } else { 55 | sprite = "bird_flying_1"; 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /main.pde: -------------------------------------------------------------------------------- 1 | import java.util.Collections; 2 | import java.util.Iterator; 3 | 4 | HashMap game_sprites = new HashMap(); 5 | 6 | Simulation simulation; 7 | 8 | int tenth = 0; 9 | int clock = 0; 10 | 11 | void setup() { 12 | size(1280, 720); 13 | initialize_sprites(); 14 | simulation = new Simulation(); 15 | } 16 | 17 | void draw() { 18 | background(247); 19 | simulation.update(); 20 | simulation.print(); 21 | if (millis() - tenth > 50){ 22 | // every 0.05 seconds 23 | tenth = millis(); 24 | clock++; 25 | if (clock % 2 == 0){ 26 | // every 0.1 seconds 27 | simulation.tenth_of_second(); 28 | } 29 | if (clock % 5 == 0){ 30 | // every 0.25 seconds 31 | simulation.quarter_of_second(); 32 | } 33 | } 34 | } 35 | 36 | void initialize_sprites(){ 37 | PImage sprite_sheet = loadImage("sprites.png"); 38 | game_sprites.put("standing_dino", sprite_sheet.get(1338, 2, 88, 94)); 39 | game_sprites.put("walking_dino_1", sprite_sheet.get(1514, 2, 88, 94)); 40 | game_sprites.put("walking_dino_2", sprite_sheet.get(1602, 2, 88, 94)); 41 | game_sprites.put("dead_dino", sprite_sheet.get(1690, 2, 88, 94)); 42 | game_sprites.put("crouching_dino_1", sprite_sheet.get(1866, 36, 118, 60)); 43 | game_sprites.put("crouching_dino_2", sprite_sheet.get(1984, 36, 118, 60)); 44 | game_sprites.put("cactus_type_1", sprite_sheet.get(446, 2, 34, 70)); 45 | game_sprites.put("cactus_type_2", sprite_sheet.get(480, 2, 68, 70)); 46 | game_sprites.put("cactus_type_3", sprite_sheet.get(548, 2, 102, 70)); 47 | game_sprites.put("cactus_type_4", sprite_sheet.get(652, 2, 50, 100)); 48 | game_sprites.put("cactus_type_5", sprite_sheet.get(702, 2, 100, 100)); 49 | game_sprites.put("cactus_type_6", sprite_sheet.get(802, 2, 150, 100)); 50 | game_sprites.put("bird_flying_1", sprite_sheet.get(260, 2, 92, 80)); 51 | game_sprites.put("bird_flying_2", sprite_sheet.get(352, 2, 92, 80)); 52 | game_sprites.put("ground", sprite_sheet.get(2, 104, 2400, 24)); 53 | } 54 | -------------------------------------------------------------------------------- /Genome.pde: -------------------------------------------------------------------------------- 1 | class Gen { 2 | 3 | boolean source_hidden_layer; 4 | int id_source_neuron; 5 | int id_target_neuron; 6 | float weight; 7 | 8 | Gen(){ 9 | source_hidden_layer = (random(1) < 0.5); 10 | id_source_neuron = (int)random(0, 7); 11 | if (source_hidden_layer){ 12 | id_target_neuron = (int)random(0, 7); 13 | } else { 14 | id_target_neuron = (int)random(0, 2); 15 | } 16 | weight = random(-1, 1); 17 | } 18 | 19 | } 20 | 21 | class Genome { 22 | 23 | int length; 24 | ArrayList genes; 25 | float[] hidden_layer_bias; 26 | float[] output_layer_bias; 27 | 28 | Genome(){ 29 | length = 16; 30 | genes = new ArrayList(); 31 | for (int i = 0; i < length; i++){ 32 | genes.add(new Gen()); 33 | } 34 | hidden_layer_bias = random_vector(7); 35 | output_layer_bias = random_vector(2); 36 | } 37 | 38 | Genome copy(){ 39 | Genome copy = new Genome(); 40 | for (int i = 0; i < length; i++){ 41 | copy.genes.set(i, genes.get(i)); 42 | } 43 | for (int i = 0; i < 7; i++){ 44 | copy.hidden_layer_bias[i] = hidden_layer_bias[i]; 45 | } 46 | for (int i = 0; i < 2; i++){ 47 | copy.output_layer_bias[i] = output_layer_bias[i]; 48 | } 49 | return copy; 50 | } 51 | 52 | Genome mutate() { 53 | // Start with a copy of the genome (this one, copy) 54 | Genome mutated_genome = copy(); 55 | // But change some of the genes for new random ones 56 | int amount_of_mutations = (int)random(1, 5); 57 | for (int i = 0; i < amount_of_mutations; i++){ 58 | int index = (int)random(0, length); 59 | mutated_genome.genes.set(index, new Gen()); 60 | } 61 | return mutated_genome; 62 | } 63 | 64 | Genome crossover(Genome anotherGenome) { 65 | // Start with a copy of the genome 66 | Genome crossed_genome = copy(); 67 | // But change some of the genes for some of the other genome's genes 68 | int amount_of_crossovers = (int)random(1, 5); 69 | for (int i = 0; i < amount_of_crossovers; i++){ 70 | int index = (int)random(0, length); 71 | crossed_genome.genes.set(index, anotherGenome.genes.get(index)); 72 | } 73 | return crossed_genome; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Evolutionary Reinforcement Learning for Dino Game 2 | 3 | 4 | 5 | ## Introduction 6 | 7 | Welcome to the Evolutionary Reinforcement Learning for Dino Game project! In this repository, I have implemented a genetic algorithm combined with reinforcement learning to teach an AI agent how to play the popular Dino Game from Google Chrome. 8 | 9 | ### Watch it on YouTube 10 | 11 | I created a YouTube video explaining the underlying techniques and concepts utilized in the project, and showcasing the evolution of the AI agent as it learns to play the Dino Game. This video has gained significant attention, accumulating over a million views. You can watch the video here (it's in spanish). 12 | 13 | ## How to use 14 | 15 | ### Processing 16 | To run the simulations yourself, you will need to have Processing installed. Processing is a flexible software sketchbook and a programming language designed for visual arts and creative coding. Processing provides a simplified environment for writing code and creating interactive graphics, making it an ideal choice for implementing the Dino Game AI agent. 17 | 18 | To get started, simply download Processing from their official website https://processing.org/ 19 | 20 | Once installed, open the folder containing the Dino Game project and click on the "run" button within Processing. This will initiate the program and allow you to observe the AI's learning process as it strives to master the game. 21 | 22 | ### Why Processing? 23 | 24 | You may wonder why I chose to develop this project entirely from scratch using Processing, instead of using Python and imoprting TensorFlow or other machine learning libraries. The decision was driven by my desire to deeply understand all the princples and details of the AI model. By coding every sigle aspect of the project myself, I had to immerse myself into the smallest features of the algorithms and understand the underlying mathematical concepts behind them. So yes, I could've implemented this way faster and easier using Python and it's advanced libraries, but I believe that coding everything from scratch has provided a lot more of valuable insights and knowledge. 25 | 26 | -------------------------------------------------------------------------------- /Brain.pde: -------------------------------------------------------------------------------- 1 | class Brain { 2 | 3 | float[] inputs; 4 | float[] outputs = {1, 0}; 5 | float[][] hidden_layer_weights; 6 | float[] hidden_layer_bias; 7 | float[] hidden_outputs; 8 | float[][] output_layer_weights; 9 | float[] output_layer_bias; 10 | 11 | 12 | Brain(Genome genome) { 13 | // Initialize weights and biases from genome 14 | hidden_layer_weights = zeroes_matrix(7, 7); 15 | output_layer_weights = zeroes_matrix(2, 7); 16 | for (Gen gen : genome.genes) { 17 | if (gen.source_hidden_layer) { 18 | hidden_layer_weights[gen.id_target_neuron][gen.id_source_neuron] = gen.weight; 19 | } else { 20 | output_layer_weights[gen.id_target_neuron][gen.id_source_neuron] = gen.weight; 21 | } 22 | } 23 | hidden_layer_bias = genome.hidden_layer_bias; 24 | output_layer_bias = genome.output_layer_bias; 25 | } 26 | 27 | void feed_forward(float[] input_layer_values) { 28 | inputs = input_layer_values; 29 | hidden_outputs = matrix_vector_multiplication(hidden_layer_weights, input_layer_values); 30 | for (int i = 0; i < hidden_outputs.length; i++) { 31 | hidden_outputs[i] += hidden_layer_bias[i]; 32 | hidden_outputs[i] = ReLU(hidden_outputs[i]); 33 | } 34 | outputs = matrix_vector_multiplication(output_layer_weights, hidden_outputs); 35 | for (int i = 0; i < outputs.length; i++) { 36 | outputs[i] += output_layer_bias[i]; 37 | outputs[i] = ReLU(outputs[i]); 38 | } 39 | } 40 | 41 | float ReLU(float x) { 42 | return max(0, x); 43 | } 44 | 45 | void set_neural_connection_stroke(float weight){ 46 | if (weight > 0){ 47 | stroke(0, 255, 0); 48 | } else if (weight < 0) { 49 | stroke(255, 0, 0); 50 | } else { 51 | stroke(200); 52 | } 53 | weight = abs(weight); 54 | weight = map(weight, 0, 1, 0.5, 5); 55 | strokeWeight(weight); 56 | } 57 | 58 | void print() { 59 | fill(0); 60 | textSize(16); 61 | text("(obstacle) distance", 550, 67); 62 | text("(obstacle) x", 598, 107); 63 | text("(obstacle) y", 598, 147); 64 | text("(obstacle) width", 568, 187); 65 | text("(obstacle) height", 563, 227); 66 | text("(dino) y", 625, 267); 67 | text("(game) speed", 586, 307); 68 | text("jump", 927, 168); 69 | text("crouch", 925, 208); 70 | 71 | for (int i = 0; i < 7; i++){ 72 | // Input layer to hidden layer lines 73 | for (int j = 0; j < 7; j++){ 74 | float weight = hidden_layer_weights[i][j]; 75 | set_neural_connection_stroke(weight); 76 | line(700 + 16, 64+i*40, 800 - 16, 64+j*40); 77 | } 78 | // Hidden layer to output layer lines 79 | for (int j = 0; j < 2; j++){ 80 | float weight = output_layer_weights[j][i]; 81 | set_neural_connection_stroke(weight); 82 | line(800 + 16, 64 + i * 40, 900 - 16, 165 + j * 40); 83 | } 84 | // Input layer circles 85 | strokeWeight(1); 86 | stroke(83); 87 | fill(255); 88 | circle(700, 64 + i * 40, 32); 89 | // Hidden layer circles 90 | stroke(0); 91 | if (hidden_outputs[i] == 0){ 92 | fill(255); 93 | } else { 94 | fill(170); 95 | } 96 | circle(800, 64 + i * 40, 32); 97 | // Input texts 98 | textSize(11); 99 | fill(0); 100 | if (inputs[i] > 99){ 101 | text(inputs[i], 688, 68+i*40); 102 | } else { 103 | text(inputs[i], 688, 68+i*40); 104 | } 105 | } 106 | // Output circles 107 | if (outputs[0] == 0){ 108 | fill(255); 109 | } else { 110 | fill(170); 111 | } 112 | circle(900, 165, 32); 113 | if (outputs[1] == 0){ 114 | fill(255); 115 | } else { 116 | fill(170); 117 | } 118 | circle(900, 205, 32); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Dino.pde: -------------------------------------------------------------------------------- 1 | class Dino extends GameObject implements Comparable{ 2 | 3 | float jump_stage; 4 | boolean alive = true; 5 | int score; 6 | 7 | Genome genome; 8 | Brain brain; 9 | float[] brain_inputs = new float[7]; 10 | 11 | Dino() { 12 | x_pos = (int)random(100, 300); 13 | y_pos = 450; 14 | obj_width = 80; 15 | obj_height = 86; 16 | 17 | genome = new Genome(); 18 | init_brain(); 19 | jump_stage = 0; 20 | 21 | sprite = "walking_dino_1"; 22 | sprite_offset[0] = -4; 23 | sprite_offset[1] = -2; 24 | } 25 | 26 | void init_brain() { 27 | brain = new Brain(genome); 28 | } 29 | 30 | void print() { 31 | if (alive) { 32 | image(game_sprites.get(sprite), x_pos + sprite_offset[0], y_pos + sprite_offset[1]); 33 | } 34 | } 35 | 36 | void update(float[] next_obstacle_info, int speed) { 37 | update_brain_inputs(next_obstacle_info, speed); 38 | brain.feed_forward(brain_inputs); 39 | process_brain_output(); 40 | if ( jumping() ){ 41 | update_jump(); 42 | } 43 | } 44 | 45 | void update_brain_inputs(float[] next_obstacle_info, int speed){ 46 | brain_inputs[0] = next_obstacle_info[0] / 900; // normalized distance 47 | brain_inputs[1] = (next_obstacle_info[1] - 450) / (1350 - 450); // normalized obstacle x pos 48 | brain_inputs[2] = (next_obstacle_info[2] - 370) / (480 - 370); // normalized obstacle y pos 49 | brain_inputs[3] = (next_obstacle_info[3] - 30) / (146 - 30); // normalized obstacle width 50 | brain_inputs[4] = (next_obstacle_info[4] - 40) / (96 - 40); // normalized obstacle height 51 | brain_inputs[5] = (y_pos - 278) / (484-278); // normalized dino y pos 52 | brain_inputs[6] = (speed - 15) / (30 - 15); // normalized speed 53 | 54 | } 55 | 56 | void update_jump() { 57 | y_pos = (int)(450 - ((-4 * jump_stage * (jump_stage - 1)) * 172)); 58 | jump_stage += 0.03; 59 | if ( jump_stage > 1 ){ 60 | stop_jump(); 61 | } 62 | } 63 | 64 | void process_brain_output() { 65 | // brain.outputs[0] = up key 66 | // brain.outputs[1] = down key 67 | 68 | if ( brain.outputs[0] != 0 ) { 69 | if (!crouching() && !jumping()) { 70 | jump(); 71 | } 72 | } 73 | if ( brain.outputs[1] == 0 ) { 74 | if (crouching()){ 75 | stop_crouch(); 76 | } 77 | } else { 78 | if ( jumping() ) { 79 | stop_jump(); 80 | } 81 | crouch(); 82 | } 83 | } 84 | 85 | void jump(){ 86 | jump_stage = 0.0001; 87 | sprite = "standing_dino"; 88 | } 89 | 90 | void stop_jump() { 91 | jump_stage = 0; 92 | y_pos = 450; 93 | sprite = "walking_dino_1"; 94 | } 95 | 96 | void crouch(){ 97 | if ( !crouching() ) { 98 | y_pos = 484; 99 | obj_width = 110; 100 | obj_height = 52; 101 | sprite = "crouching_dino_1"; 102 | } 103 | } 104 | 105 | void stop_crouch(){ 106 | y_pos = 450; 107 | obj_width = 80; 108 | obj_height = 86; 109 | sprite = "walking_dino_1"; 110 | } 111 | 112 | boolean jumping() { 113 | return jump_stage > 0; 114 | } 115 | 116 | boolean crouching() { 117 | return obj_width == 110; 118 | } 119 | 120 | void die(int sim_score) { 121 | alive = false; 122 | score = sim_score; 123 | } 124 | 125 | void reset() { 126 | alive = true; 127 | score = 0; 128 | } 129 | 130 | void toggle_sprite() { 131 | if ( sprite.equals("walking_dino_1") ) { 132 | sprite = "walking_dino_2"; 133 | } else if ( sprite.equals("walking_dino_2") ) { 134 | sprite = "walking_dino_1"; 135 | } else if ( sprite.equals("crouching_dino_1") ) { 136 | sprite = "crouching_dino_2"; 137 | } else if ( sprite.equals("crouching_dino_2") ) { 138 | sprite = "crouching_dino_1"; 139 | } 140 | } 141 | 142 | @Override 143 | public int compareTo(Dino otherDino) { 144 | return Integer.compare(this.score, otherDino.score); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Simulation.pde: -------------------------------------------------------------------------------- 1 | int DINOS_PER_GENERATION = 1000; 2 | float MIN_SPAWN_MILLIS = 500; 3 | float MAX_SPAWN_MILLIS = 1500; 4 | 5 | class Simulation { 6 | ArrayList dinos; 7 | ArrayList enemies; 8 | float speed; 9 | Ground ground; 10 | int score; 11 | int generation; 12 | int last_gen_avg_score; 13 | int last_gen_max_score; 14 | int dinos_alive; 15 | 16 | // to control enemies spawn time 17 | float last_spawn_time; 18 | float time_to_spawn; 19 | 20 | Simulation() { 21 | dinos = new ArrayList(); 22 | for (int i = 0; i < DINOS_PER_GENERATION; i++) { 23 | dinos.add(new Dino()); 24 | } 25 | enemies = new ArrayList(); 26 | speed = 15; 27 | ground = new Ground(); 28 | score = 0; 29 | generation = 1; 30 | last_gen_avg_score = 0; 31 | last_gen_max_score = 0; 32 | dinos_alive = DINOS_PER_GENERATION; 33 | last_spawn_time = millis(); 34 | time_to_spawn = random(MIN_SPAWN_MILLIS, MAX_SPAWN_MILLIS); 35 | } 36 | 37 | void update() { 38 | for (Dino dino : dinos) { 39 | if (dino.alive){ 40 | dino.update(next_obstacle_info(dino), (int)speed); 41 | } 42 | } 43 | Iterator iterator = enemies.iterator(); 44 | while (iterator.hasNext()) { 45 | Enemy enemy = iterator.next(); 46 | enemy.update((int)speed); 47 | if (enemy.is_offscreen()) { 48 | iterator.remove(); 49 | } 50 | } 51 | if (millis() - last_spawn_time > time_to_spawn) { 52 | spawn_enemy(); 53 | last_spawn_time = millis(); 54 | time_to_spawn = random(MIN_SPAWN_MILLIS, MAX_SPAWN_MILLIS); 55 | } 56 | check_collisions(); 57 | ground.update((int)speed); 58 | speed += 0.001; 59 | } 60 | 61 | void check_collisions() { 62 | dinos_alive = 0; 63 | for (Dino dino : dinos) { 64 | for (Enemy enemy : enemies) { 65 | if (dino.alive && dino.is_collisioning_with(enemy)) { 66 | dino.die(score); 67 | } 68 | } 69 | if (dino.alive) { 70 | dinos_alive++; 71 | } 72 | } 73 | if (dinos_alive == 0) { 74 | next_generation(); 75 | } 76 | } 77 | 78 | void next_generation() { 79 | score = 0; 80 | generation++; 81 | speed = 15; 82 | enemies.clear(); 83 | int dinos_score_sum = 0; 84 | for (Dino dino : dinos) { 85 | dinos_score_sum += dino.score; 86 | } 87 | last_gen_avg_score = dinos_score_sum / DINOS_PER_GENERATION; 88 | Collections.sort(dinos); 89 | Collections.reverse(dinos); 90 | last_gen_max_score = dinos.get(0).score; 91 | // The best 5% will be kept as they are 92 | ArrayList new_dinos = new ArrayList(); 93 | for (int i = 0; i < DINOS_PER_GENERATION * 0.05; i++) { 94 | new_dinos.add(dinos.get(i)); 95 | new_dinos.get(i).reset(); 96 | } 97 | // Another 5% will be completely new 98 | for (int i = 0; i < DINOS_PER_GENERATION * 0.05; i++) { 99 | new_dinos.add(new Dino()); 100 | } 101 | // Another 30% will mutate from the best 102 | for (int i = 0; i < DINOS_PER_GENERATION * 0.3; i++) { 103 | Dino father = dinos.get(0); 104 | Dino son = new Dino(); 105 | son.genome = father.genome.mutate(); 106 | son.init_brain(); 107 | new_dinos.add(son); 108 | } 109 | // Another 40% will have a father from the best 5% and mutate its brain 110 | for (int i = 0; i < DINOS_PER_GENERATION * 0.4; i++) { 111 | Dino father = dinos.get((int)random(DINOS_PER_GENERATION * 0.05)); 112 | Dino son = new Dino(); 113 | son.genome = father.genome.mutate(); 114 | son.init_brain(); 115 | new_dinos.add(son); 116 | } 117 | // The last 20% will have a father and a mother from the best 5% 118 | for (int i = 0; i < DINOS_PER_GENERATION * 0.2; i++) { 119 | Dino father = dinos.get((int)random(DINOS_PER_GENERATION * 0.05)); 120 | Dino mother = dinos.get((int)random(DINOS_PER_GENERATION * 0.05)); 121 | Dino son = new Dino(); 122 | son.genome = father.genome.crossover(mother.genome); 123 | son.init_brain(); 124 | new_dinos.add(son); 125 | } 126 | dinos = new_dinos; 127 | } 128 | 129 | float[] next_obstacle_info(Dino dino){ 130 | float[] result = {1280, 0, 0, 0, 0}; 131 | for (Enemy enemy : enemies){ 132 | if (enemy.x_pos > dino.x_pos){ 133 | result[0] = enemy.x_pos - dino.x_pos; 134 | result[1] = enemy.x_pos; 135 | result[2] = enemy.y_pos; 136 | result[3] = enemy.obj_width; 137 | result[4] = enemy.obj_height; 138 | break; 139 | } 140 | } 141 | return result; 142 | } 143 | 144 | void print() { 145 | ground.print(); 146 | for (Enemy enemy : enemies ) { 147 | enemy.print(); 148 | } 149 | for (Dino dino : dinos) { 150 | dino.print(); 151 | } 152 | print_info(); 153 | } 154 | 155 | void print_info(){ 156 | fill(0); 157 | textSize(30); 158 | text(score, 1200, 80); 159 | text("Generation: " + generation, 80, 80); 160 | text("Average Score (last gen): " + last_gen_avg_score, 80, 120); 161 | text("Max Score (last gen): " + last_gen_max_score, 80, 160); 162 | text("Alive: " + dinos_alive, 80, 200); 163 | print_network(); 164 | } 165 | 166 | void print_network() { 167 | for (Dino dino : dinos) { 168 | if (dino.alive) { 169 | dino.brain.print(); 170 | break; 171 | } 172 | } 173 | } 174 | 175 | void tenth_of_second() { 176 | for (Dino dino : dinos) { 177 | if (dino.alive) { 178 | dino.toggle_sprite(); 179 | } 180 | } 181 | score++; 182 | } 183 | 184 | void quarter_of_second() { 185 | for (Enemy enemy : enemies ) { 186 | enemy.toggle_sprite(); 187 | } 188 | } 189 | 190 | void spawn_enemy() { 191 | if (random(1) < 0.5) { 192 | enemies.add(new Cactus()); 193 | } else { 194 | enemies.add(new Bird()); 195 | } 196 | } 197 | 198 | } 199 | --------------------------------------------------------------------------------