From 02be835802e046f570842d8e4c36be377be89639 Mon Sep 17 00:00:00 2001 From: atmandarji Date: Thu, 7 Mar 2019 12:02:09 +0530 Subject: [PATCH 1/2] New UI has been added and reset button has been added --- Neuroevolution.js | 296 +++------------------------------------ game.js | 120 ++++++++++++++-- img/background.png | Bin 3175 -> 4399 bytes img/bird.png | Bin 382 -> 9001 bytes img/pipebottom.png | Bin 1241 -> 3937 bytes img/pipetop.png | Bin 1191 -> 3848 bytes img1/background.png | Bin 0 -> 3175 bytes img1/bird.png | Bin 0 -> 382 bytes {img => img1}/flappy.png | Bin img1/pipebottom.png | Bin 0 -> 1241 bytes img1/pipetop.png | Bin 0 -> 1191 bytes index.html | 80 +++++++++-- 12 files changed, 201 insertions(+), 295 deletions(-) mode change 100644 => 100755 img/background.png mode change 100644 => 100755 img/bird.png mode change 100644 => 100755 img/pipebottom.png mode change 100644 => 100755 img/pipetop.png create mode 100755 img1/background.png create mode 100755 img1/bird.png rename {img => img1}/flappy.png (100%) mode change 100644 => 100755 create mode 100755 img1/pipebottom.png create mode 100755 img1/pipetop.png diff --git a/Neuroevolution.js b/Neuroevolution.js index f1bc43d..7108bfb 100644 --- a/Neuroevolution.js +++ b/Neuroevolution.js @@ -1,118 +1,46 @@ -/** - * Provides a set of classes and methods for handling Neuroevolution and - * genetic algorithms. - * - * @param {options} An object of options for Neuroevolution. - */ var Neuroevolution = function (options) { - var self = this; // reference to the top scope of this module - - // Declaration of module parameters (options) and default values + var self = this; self.options = { - /** - * Logistic activation function. - * - * @param {a} Input value. - * @return Logistic function output. - */ activation: function (a) { ap = (-a) / 1; return (1 / (1 + Math.exp(ap))) }, - - /** - * Returns a random value between -1 and 1. - * - * @return Random value. - */ randomClamped: function () { return Math.random() * 2 - 1; }, - - // various factors and parameters (along with default values). - network: [1, [1], 1], // Perceptron network structure (1 hidden - // layer). - population: 50, // Population by generation. - elitism: 0.2, // Best networks kepts unchanged for the next - // generation (rate). - randomBehaviour: 0.2, // New random networks for the next generation - // (rate). - mutationRate: 0.1, // Mutation rate on the weights of synapses. - mutationRange: 0.5, // Interval of the mutation changes on the - // synapse weight. - historic: 0, // Latest generations saved. - lowHistoric: false, // Only save score (not the network). - scoreSort: -1, // Sort order (-1 = desc, 1 = asc). - nbChild: 1 // Number of children by breeding. - + network: [1, [1], 1], + population: 50, + elitism: 0.2, + randomBehaviour: 0.2, + mutationRate: 0.1, + mutationRange: 0.5, + historic: 0, + lowHistoric: false, + scoreSort: -1, + nbChild: 1 } - - /** - * Override default options. - * - * @param {options} An object of Neuroevolution options. - * @return void - */ self.set = function (options) { for (var i in options) { - if (this.options[i] != undefined) { // Only override if the passed in value - // is actually defined. + if (this.options[i] != undefined) { self.options[i] = options[i]; } } } - - // Overriding default options with the pass in options self.set(options); - - - /*NEURON**********************************************************************/ - /** - * Artificial Neuron class - * - * @constructor - */ var Neuron = function () { this.value = 0; this.weights = []; } - - /** - * Initialize number of neuron weights to random clamped values. - * - * @param {nb} Number of neuron weights (number of inputs). - * @return void - */ Neuron.prototype.populate = function (nb) { this.weights = []; for (var i = 0; i < nb; i++) { this.weights.push(self.options.randomClamped()); } } - - - /*LAYER***********************************************************************/ - /** - * Neural Network Layer class. - * - * @constructor - * @param {index} Index of this Layer in the Network. - */ var Layer = function (index) { this.id = index || 0; this.neurons = []; } - - /** - * Populate the Layer with a set of randomly weighted Neurons. - * - * Each Neuron be initialied with nbInputs inputs with a random clamped - * value. - * - * @param {nbNeurons} Number of neurons. - * @param {nbInputs} Number of inputs. - * @return void - */ Layer.prototype.populate = function (nbNeurons, nbInputs) { this.neurons = []; for (var i = 0; i < nbNeurons; i++) { @@ -121,39 +49,18 @@ var Neuroevolution = function (options) { this.neurons.push(n); } } - - - /*NEURAL NETWORK**************************************************************/ - /** - * Neural Network class - * - * Composed of Neuron Layers. - * - * @constructor - */ var Network = function () { this.layers = []; } - - /** - * Generate the Network layers. - * - * @param {input} Number of Neurons in Input layer. - * @param {hidden} Number of Neurons per Hidden layer. - * @param {output} Number of Neurons in Output layer. - * @return void - */ Network.prototype.perceptronGeneration = function (input, hiddens, output) { var index = 0; var previousNeurons = 0; var layer = new Layer(index); - layer.populate(input, previousNeurons); // Number of Inputs will be set to - // 0 since it is an input layer. - previousNeurons = input; // number of input is size of previous layer. + layer.populate(input, previousNeurons); + previousNeurons = input; this.layers.push(layer); index++; for (var i in hiddens) { - // Repeat same process as first layer for each hidden layer. var layer = new Layer(index); layer.populate(hiddens[i], previousNeurons); previousNeurons = hiddens[i]; @@ -161,59 +68,37 @@ var Neuroevolution = function (options) { index++; } var layer = new Layer(index); - layer.populate(output, previousNeurons); // Number of input is equal to - // the size of the last hidden - // layer. + layer.populate(output, previousNeurons); this.layers.push(layer); } - - /** - * Create a copy of the Network (neurons and weights). - * - * Returns number of neurons per layer and a flat array of all weights. - * - * @return Network data. - */ Network.prototype.getSave = function () { var datas = { - neurons: [], // Number of Neurons per layer. - weights: [] // Weights of each Neuron's inputs. + neurons: [], + weights: [] }; for (var i in this.layers) { datas.neurons.push(this.layers[i].neurons.length); for (var j in this.layers[i].neurons) { for (var k in this.layers[i].neurons[j].weights) { - // push all input weights of each Neuron of each Layer into a flat - // array. datas.weights.push(this.layers[i].neurons[j].weights[k]); } } } return datas; } - - /** - * Apply network data (neurons and weights). - * - * @param {save} Copy of network data (neurons and weights). - * @return void - */ Network.prototype.setSave = function (save) { var previousNeurons = 0; var index = 0; var indexWeights = 0; this.layers = []; for (var i in save.neurons) { - // Create and populate layers. var layer = new Layer(index); layer.populate(save.neurons[i], previousNeurons); for (var j in layer.neurons) { for (var k in layer.neurons[j].weights) { - // Apply neurons weights to each Neuron. layer.neurons[j].weights[k] = save.weights[indexWeights]; - - indexWeights++; // Increment index of flat array. + indexWeights++; } } previousNeurons = save.neurons[i]; @@ -221,40 +106,25 @@ var Neuroevolution = function (options) { this.layers.push(layer); } } - - /** - * Compute the output of an input. - * - * @param {inputs} Set of inputs. - * @return Network output. - */ Network.prototype.compute = function (inputs) { - // Set the value of each Neuron in the input layer. for (var i in inputs) { if (this.layers[0] && this.layers[0].neurons[i]) { this.layers[0].neurons[i].value = inputs[i]; } } - var prevLayer = this.layers[0]; // Previous layer is input layer. + var prevLayer = this.layers[0]; for (var i = 1; i < this.layers.length; i++) { for (var j in this.layers[i].neurons) { - // For each Neuron in each layer. var sum = 0; for (var k in prevLayer.neurons) { - // Every Neuron in the previous layer is an input to each Neuron in - // the next layer. sum += prevLayer.neurons[k].value * this.layers[i].neurons[j].weights[k]; } - - // Compute the activation of the Neuron. this.layers[i].neurons[j].value = self.options.activation(sum); } prevLayer = this.layers[i]; } - - // All outputs of the Network. var out = []; var lastLayer = this.layers[this.layers.length - 1]; for (var i in lastLayer.neurons) { @@ -262,53 +132,19 @@ var Neuroevolution = function (options) { } return out; } - - - /*GENOME**********************************************************************/ - /** - * Genome class. - * - * Composed of a score and a Neural Network. - * - * @constructor - * - * @param {score} - * @param {network} - */ var Genome = function (score, network) { this.score = score || 0; this.network = network || null; } - - - /*GENERATION******************************************************************/ - /** - * Generation class. - * - * Composed of a set of Genomes. - * - * @constructor - */ var Generation = function () { this.genomes = []; } - - /** - * Add a genome to the generation. - * - * @param {genome} Genome to add. - * @return void. - */ Generation.prototype.addGenome = function (genome) { - // Locate position to insert Genome into. - // The gnomes should remain sorted. for (var i = 0; i < this.genomes.length; i++) { - // Sort in descending order. if (self.options.scoreSort < 0) { if (genome.score > this.genomes[i].score) { break; } - // Sort in ascending order. } else { if (genome.score < this.genomes[i].score) { break; @@ -316,33 +152,17 @@ var Neuroevolution = function (options) { } } - - // Insert genome into correct position. this.genomes.splice(i, 0, genome); } - - /** - * Breed to genomes to produce offspring(s). - * - * @param {g1} Genome 1. - * @param {g2} Genome 2. - * @param {nbChilds} Number of offspring (children). - */ Generation.prototype.breed = function (g1, g2, nbChilds) { var datas = []; for (var nb = 0; nb < nbChilds; nb++) { - // Deep clone of genome 1. var data = JSON.parse(JSON.stringify(g1)); for (var i in g2.network.weights) { - // Genetic crossover - // 0.5 is the crossover factor. - // FIXME Really should be a predefined constant. if (Math.random() <= 0.5) { data.network.weights[i] = g2.network.weights[i]; } } - - // Perform mutation on some weights. for (var i in data.network.weights) { if (Math.random() <= self.options.mutationRate) { data.network.weights[i] += Math.random() * @@ -356,19 +176,12 @@ var Neuroevolution = function (options) { return datas; } - - /** - * Generate the next generation. - * - * @return Next generation data array. - */ Generation.prototype.generateNextGeneration = function () { var nexts = []; for (var i = 0; i < Math.round(self.options.elitism * self.options.population); i++) { if (nexts.length < self.options.population) { - // Push a deep copy of ith Genome's Nethwork. nexts.push(JSON.parse(JSON.stringify(this.genomes[i].network))); } } @@ -387,14 +200,11 @@ var Neuroevolution = function (options) { var max = 0; while (true) { for (var i = 0; i < max; i++) { - // Create the children and push them to the nexts array. var childs = this.breed(this.genomes[i], this.genomes[max], (self.options.nbChild > 0 ? self.options.nbChild : 1)); for (var c in childs) { nexts.push(childs[c].network); if (nexts.length >= self.options.population) { - // Return once number of children is equal to the - // population by generatino value. return nexts; } } @@ -405,35 +215,13 @@ var Neuroevolution = function (options) { } } } - - - /*GENERATIONS*****************************************************************/ - /** - * Generations class. - * - * Hold's previous Generations and current Generation. - * - * @constructor - */ var Generations = function () { this.generations = []; var currentGeneration = new Generation(); } - - /** - * Create the first generation. - * - * @param {input} Input layer. - * @param {input} Hidden layer(s). - * @param {output} Output layer. - * @return First Generation. - */ Generations.prototype.firstGeneration = function (input, hiddens, output) { - // FIXME input, hiddens, output unused. - var out = []; for (var i = 0; i < self.options.population; i++) { - // Generate the Network and save it. var nn = new Network(); nn.perceptronGeneration(self.options.network[0], self.options.network[1], @@ -444,15 +232,8 @@ var Neuroevolution = function (options) { this.generations.push(new Generation()); return out; } - - /** - * Create the next Generation. - * - * @return Next Generation. - */ Generations.prototype.nextGeneration = function () { if (this.generations.length == 0) { - // Need to create first generation. return false; } @@ -461,51 +242,22 @@ var Neuroevolution = function (options) { this.generations.push(new Generation()); return gen; } - - /** - * Add a genome to the Generations. - * - * @param {genome} - * @return False if no Generations to add to. - */ Generations.prototype.addGenome = function (genome) { - // Can't add to a Generation if there are no Generations. if (this.generations.length == 0) return false; - - // FIXME addGenome returns void. return this.generations[this.generations.length - 1].addGenome(genome); } - - - /*SELF************************************************************************/ self.generations = new Generations(); - - /** - * Reset and create a new Generations object. - * - * @return void. - */ self.restart = function () { self.generations = new Generations(); } - - /** - * Create the next generation. - * - * @return Neural Network array for next Generation. - */ self.nextGeneration = function () { var networks = []; if (self.generations.generations.length == 0) { - // If no Generations, create first. networks = self.generations.firstGeneration(); } else { - // Otherwise, create next one. networks = self.generations.nextGeneration(); } - - // Create Networks from the current Generation. var nns = []; for (var i in networks) { var nn = new Network(); @@ -514,7 +266,6 @@ var Neuroevolution = function (options) { } if (self.options.lowHistoric) { - // Remove old Networks. if (self.generations.generations.length >= 2) { var genomes = self.generations @@ -527,7 +278,6 @@ var Neuroevolution = function (options) { } if (self.options.historic != -1) { - // Remove older generations. if (self.generations.generations.length > self.options.historic + 1) { self.generations.generations.splice(0, self.generations.generations.length - (self.options.historic + 1)); @@ -536,14 +286,6 @@ var Neuroevolution = function (options) { return nns; } - - /** - * Adds a new Genome with specified Neural Network and score. - * - * @param {network} Neural Network. - * @param {score} Score value. - * @return void. - */ self.networkScore = function (network, score) { self.generations.addGenome(new Genome(score, network.getSave())); } diff --git a/game.js b/game.js index 0864638..ba0d9de 100644 --- a/game.js +++ b/game.js @@ -137,13 +137,29 @@ var Game = function(){ this.backgroundSpeed = 0.5; this.backgroundx = 0; this.maxScore = 0; + this.myMaxScore=0; + this.myScore=0; } - Game.prototype.start = function(){ + if(this.generation!=0){ + var table = document.getElementById("genscore"); + var row = table.insertRow(this.generation); + var cell1 = row.insertCell(0); + var cell2 = row.insertCell(1); + var cell3 = row.insertCell(2); + cell1.style.textAlign = "center"; + cell2.style.textAlign = "center"; + cell3.style.textAlign = "center"; + cell1.innerHTML = this.generation; + cell2.innerHTML = this.myScore; + cell3.innerHTML = this.score; + } this.interval = 0; this.score = 0; + this.myScore=0; this.pipes = []; this.birds = []; + this.flag=false; this.gen = Neuvol.nextGeneration(); for(var i in this.gen){ @@ -211,10 +227,17 @@ Game.prototype.update = function(){ this.interval++; if(this.interval == this.spawnInterval){ this.interval = 0; + if(this.flag){ + this.myScore++; + } + else{ + this.flag=true; + } } this.score++; this.maxScore = (this.score > this.maxScore) ? this.score : this.maxScore; + this.myMaxScore = (this.myScore > this.myMaxScore) ? this.myScore : this.myMaxScore; var self = this; if(FPS == 0){ @@ -264,12 +287,23 @@ Game.prototype.display = function(){ } } - this.ctx.fillStyle = "white"; + this.ctx.fillStyle = "black"; this.ctx.font="20px Oswald, sans-serif"; - this.ctx.fillText("Score : "+ this.score, 10, 25); - this.ctx.fillText("Max Score : "+this.maxScore, 10, 50); - this.ctx.fillText("Generation : "+this.generation, 10, 75); - this.ctx.fillText("Alive : "+this.alives+" / "+Neuvol.options.population, 10, 100); + /* + this.ctx.fillText("Score : "+ this.myScore, 10, 25); + this.ctx.fillText("Max Score : "+this.myMaxScore, 10, 50); + this.ctx.fillText("Distance : "+ this.score, 10, 75); + this.ctx.fillText("Max Distance : "+this.maxScore, 10, 100); + this.ctx.fillText("Generation : "+this.generation, 10, 125); + this.ctx.fillText("Alive : "+this.alives+" / "+Neuvol.options.population, 10, 150); + */ + + document.getElementById("p_score").innerHTML="Score : "+this.myScore; + document.getElementById("p_maxscore").innerHTML="Max Score : "+this.myMaxScore; + document.getElementById("p_distance").innerHTML="Distance : "+this.score; + document.getElementById("p_maxdistance").innerHTML="Max Distance : "+this.maxScore; + document.getElementById("p_gen").innerHTML="Generation : "+this.generation; + document.getElementById("p_alive").innerHTML="Alive : "+this.alives+"/"+Neuvol.options.population; var self = this; requestAnimationFrame(function(){ @@ -278,6 +312,17 @@ Game.prototype.display = function(){ } window.onload = function(){ + var c = document.getElementById("flappy"); + var ctx = c.getContext("2d"); + ctx.font = "30px Arial"; + ctx.strokeText("Click here to start", 130, 256); + document.getElementById("Button").disabled = true; + document.getElementById("x1").disabled = true; + document.getElementById("x2").disabled = true; + document.getElementById("x3").disabled = true; + document.getElementById("x5").disabled = true; + document.getElementById("MAX").disabled = true; + document.getElementById("cui").disabled = true; var sprites = { bird:"./img/bird.png", background:"./img/background.png", @@ -299,7 +344,66 @@ window.onload = function(){ loadImages(sprites, function(imgs){ images = imgs; - start(); + //start(); + }) +} +var isStarted = false; +var mygamestart = function(){ + if(!isStarted){ + Neuvol = new Neuroevolution({ + population:50, + network:[2, [2], 1], + }); + game = new Game(); + game.start(); + game.update(); + game.display(); + isStarted = true; + document.getElementById("Button").disabled = false; + document.getElementById("x1").disabled = false; + document.getElementById("x2").disabled = false; + document.getElementById("x3").disabled = false; + document.getElementById("x5").disabled = false; + document.getElementById("MAX").disabled = false; + document.getElementById("cui").disabled = false; + } +} +var restart = function(){ + var table = document.getElementById("genscore"); + var rowCount = table.rows.length; + while(rowCount>1){ + rowCount--; + table.deleteRow(rowCount); + } + Neuvol = new Neuroevolution({ + population:50, + network:[2, [2], 1], + }); + game = new Game(); + game.start(); + game.update(); + game.display(); + speed(60); + isStarted = true; +} +var isBW = true; +var changeUI = function(){ + var ref; + if(isBW){ + isBW = false; + ref="img1" + } + else{ + isBW = true; + ref="img" + } + var sprites = { + bird:"./"+ref+"/bird.png", + background:"./"+ref+"/background.png", + pipetop:"./"+ref+"/pipetop.png", + pipebottom:"./"+ref+"/pipebottom.png" + } + loadImages(sprites, function(imgs){ + images = imgs; }) - } diff --git a/img/background.png b/img/background.png old mode 100644 new mode 100755 index 22b307092c207584a424fc9f7d851d9d6af56e4e..f5383319e972f68252939d0d96b9cc5d4c70a571 GIT binary patch literal 4399 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 literal 382 zcmeAS@N?(olHy`uVBq!ia0vp^NNn{1`ISV`@iy0XB4ude`@%$AjKtZVz*NBqf{Irtt#G+J&fW*wa5h zJyShHL-)^4=0H_%JzX3_JiM)yUF3!lt_)+21i0yUUqfzn#?$Z;p9cp zcqg{g_Z^a0PqMx!w%NCz--}aj`a7M8TzcnED+~CZUjIhpYjgbjxNBUy3Z+uwcI=bZ zkY!=`^;UlCi9I^%e8p+*Enj61V2;(j>Td>(^)9s1-nH!y> z=N1|tvif8!@a5hu%db-^f}gb38`|D_Wg<|+yyl+!T#0q+ol{=-m@bR+Rub^d&MOGZ zpTAuxo7I-#Mo!MX#LEtA*f%@Gb!WI=a{MCFIEOXa*y;bqm#mi^Og=u$_ljQBa-Z!d Z1J|R;Ez<7tM}Qt@@O1TaS?83{1OU^3k4OLj diff --git a/img/pipebottom.png b/img/pipebottom.png old mode 100644 new mode 100755 index 99d27b2679d6fab3955868760586e40d2095398e..6aa4892f99124d0738bc59ccc27ef218c25ccb1a GIT binary patch literal 3937 zcmeAS@N?(olHy`uVBq!ia0vp^3mF*L&vLK&UJchNOfK$;ih+WG0gVe%78T)#ZO922f7`0?p`{*)hOLW25AkD7HoioTs-OA%I7 zVi8nw5*QdtW9zm5^(&^l7yNiN=1gF=Zk*GcWD1p?QjDXZJW*Ow9omv|yIbJ71c_}pa00tHjk4AoycRoIrcQQq zYm=^v$2KK!)&*8WDz~FrERIvqc!O60RB3O}x4E=wuinxc5z11;0CrI*=#C({Iz#fI zF{n#W4XeCcX4YMa(s%8cPHH~|QHXYMEKF^=zD&vb&{{G(8o+h{u<7g)`xj|@<58QILO_JVcj{Imp~3nx}&cn z1H;CC?mvmFKt5-IM`SSr1K(i~W;~w1A_XWYRpJ^^5}cn_Ql40p$`Fv4nOCCc=Nh6= zW~^tbXK3jD*~uJel82{@V@O5Z+q>T0QxZkkKm249{K3c;wWAfEj&YlU6PfRrU!OHK~ z{P|PGhuZW1|2#{2>G8gC%fxWa{C6(#`MV4*&2(G-eaWk{%`gAWZ7hxK?l(8vyWXO1 zb?M8f@{RrflIO0>ug?$Mk<|S2+^ZPPy+3x@p8abvXWxIR_~~1JRIK0kR;TZN*4dgD z8s7>x1$q5FZY^#$qkh$&1A8|1+n@h$dE&8u>`~^d`P;>`@5jA2wFvfNzIv>%UEahj zy*Yd3Ca!f$qw0UhiH01kSy(2UQ`lHO>CfcJPu>KS?}~cuu|2+Q>B{HPUAHTHZEl8q zIj-$*E)FuLv})D8YIW)3w#CVrTT*vu+Us{SE9@%~D{p}a8gI4W{|8e*9_p|vMHd;!@JXBiS zbdh@t*Q$iiQZI}i8yUaXjJmt$vu)GvC1sgJW-r>*A^6l-Bkbc_&yQPcJ` zpBA>9cjBWGLf9c;|t<$_j5B^=pwr^u5Jz2ss%HT_#~+r zPM+a;MkP(ixO1k;Bu>>=9$(L>*iBja@!{60jd5$lUmu;Aw@BQ5)p8)Ks!)3UF=b^mU^R?VXW# zQFoW?_7`Gz7e$wJ=edHNAqV2D?p3^(dMx!AR5$NCw({oP2k$hS$XyQiUnMs9PLi9|5T8F(p)ov|IEVr{OAYSW- za1b(`QHv02z`_%KGy>gM9Y@tIxz$A%P3tR&Ba`7aKn#sqG!0E_sfw`Im5HwmL9Rep z?4}#&ji%KK5^WO5&IW3jwn7z6t1Ki$fnNLX@Ag-E@6Bm?5?+r%g%^XTtDnm{r-UW| D4<|^f diff --git a/img/pipetop.png b/img/pipetop.png old mode 100644 new mode 100755 index 1fcd52b3c4d210188e85e6e96bbd8f7ea6def600..54c46a987a1afce152753e138ecb1ea90f36a37c GIT binary patch literal 3848 zcmeAS@N?(olHy`uVBq!ia0vp^3mF*L&vLK+LngypTkZwuheuQ(RxW^*K0iZ%CHB%c;Y9TJJ#e4MskhQjJenuS~i( zyW~^3(fP&yO(tJ9e|~4nT-`}Eb7z)+Hj3f>|2aSH-@X?4OW)Fhqpg+J{MWZuJ~OR+ zeNWd_mkticmJWfDOU-d_@q7mih}j=9LM)CC>5z!2X>nhCNX?~1!O4X~i85;Wq;3B8 zUiZ$O;eVXezpZi`hrIKejYVA?bCYX?n>Yj&I|KwrsSyKSRALM++n9usBOBH zPqs;$Zb=Jnx;2%fu#fUOaYFa^+S7}--2HNW(iNYZi*s|F<|LDoIG$`$-6^WLI<{Nj zxdf%P^odB1+qwLcrNpi8ZrLlYczR23aZmn0Bw3KB#1vQG?V`+6J1f|M@x?CqNT!(z zRnmm3cHIKA$!ZFL970jvZ%OYqIC*hJ#9mItONU@vf5Owa^JmV_{Fx@AQ}mzV_0;s@{6?WLVAqzx M)78&qol`;+0Bb~Rh5!Hn literal 1191 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 Z1J|R;Ez<7tM}Qt@@O1TaS?83{1OU^3k4OLj literal 0 HcmV?d00001 diff --git a/img/flappy.png b/img1/flappy.png old mode 100644 new mode 100755 similarity index 100% rename from img/flappy.png rename to img1/flappy.png diff --git a/img1/pipebottom.png b/img1/pipebottom.png new file mode 100755 index 0000000000000000000000000000000000000000..99d27b2679d6fab3955868760586e40d2095398e GIT binary patch literal 1241 zcmeAS@N?(olHy`uVBq!ia0vp^8Vn3f3><7g)`xj|@<58QILO_JVcj{Imp~3nx}&cn z1H;CC?mvmFKt5-IM`SSr1K(i~W;~w1A_XWYRpJ^^5}cn_Ql40p$`Fv4nOCCc=Nh6= zW~^tbXK3jD*~uJel82{@V@O5Z+q>T0QxZkkKm249{K3c;wWAfEj&YlU6PfRrU!OHK~ z{P|PGhuZW1|2#{2>G8gC%fxWa{C6(#`MV4*&2(G-eaWk{%`gAWZ7hxK?l(8vyWXO1 zb?M8f@{RrflIO0>ug?$Mk<|S2+^ZPPy+3x@p8abvXWxIR_~~1JRIK0kR;TZN*4dgD z8s7>x1$q5FZY^#$qkh$&1A8|1+n@h$dE&8u>`~^d`P;>`@5jA2wFvfNzIv>%UEahj zy*Yd3Ca!f$qw0UhiH01kSy(2UQ`lHO>CfcJPu>KS?}~cuu|2+Q>B{HPUAHTHZEl8q zIj-$*E)FuLv})D8YIW)3w#CVrTT*vu+Us{SE9@%~D{p}a8gI4W{|8e*9_p|vMHd;!@JXBiS zbdh@t*Q$iiQZI}i8yUaXjJmt$vu)GvC1sgJW-r>*A^6l-Bkbc_&yQPcJ` zpBA>9cjBWGLf9c;|t<$_j5B^=pwr^u5Jz2ss%HT_#~+r zPM+a;MkP(ixO1k;Bu>>=9$(L>*iBja@!{60jd5$lUmu;Aw@BQ5)p8)Ks!)3UF=b^mU^R?VXW# zQFoW?_7`Gz7e$wJ=edHNAqV2D?p3^(dMx!AR5$NCw({oP2k$hS$XyQiUnMs9PLi9|5T8F(p)ov|IEVr{OAYSW- za1b(`QHv02z`_%KGy>gM9Y@tIxz$A%P3tR&Ba`7aKn#sqG!0E_sfw`Im5HwmL9Rep z?4}#&ji%KK5^WO5&IW3jwn7z6t1Ki$fnNLX@Ag-E@6Bm?5?+r%g%^XTtDnm{r-UW| D4<|^f literal 0 HcmV?d00001 diff --git a/img1/pipetop.png b/img1/pipetop.png new file mode 100755 index 0000000000000000000000000000000000000000..1fcd52b3c4d210188e85e6e96bbd8f7ea6def600 GIT binary patch literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^8Vn3f3><7g)`xj|@<58QILO_JVcj{Imp~3nx}&cn z1H;CC?mvmFKt5-IM`SSr1K(i~W;~w1A_XWYRpJ^^5}cn_Ql40p$`Fv4nOCCc=Nh6= zW~^tbXK3jD*~uJel9;E9V@O5Z+dJOgQvxN}KYW*VdB84_@$gXZx>Ji@Y|>cKs&s>O zleU`M>K+qbH%Da+on1RJ5)He%mbh-^{=>93YIlL)(_^WpAAFf->>FnwYaygp$IQQH z@wtMEFMHnq|9Q5@HSpdpYyUes+I#-)vp=I>-nuk?apJq<2TS$`Zavy(U>M(Wvvu2t zU$^g-%n44O-&L?Ls^#H#*A;c^?bA z&ASiYeUMjRzC-#B?>n~g=4wB+DJzP1r9(yTb-v3zWta2dvOsj*HJ3X=+Zogju*_f( zPGItB;51-NgQ#XVU_=)F>#f)swJ7aM&#XY>EVr{OBuy8mU9p@Qu;|=NIUox~SZJ#Y zQ~^x5*l3~4+{?BweK29wtWIR(fWlD!z!ZRO0dl;r2!ZTF5v=ypw z*FjXzoy@8FYR)@DG+|w!nMlR~&9+h(_PSzuHyKShYSA=^XW^<#Apvw8O?az|E>h5d zRKJ?zqY?PLq#sQ!~4nVRb8e)!~2y(2c8lmGtK)|2MdmkQS`oWA?egUw2JsIwC%q7T^zN-Ur(pSq&(i9 z(Rj8jntk@$EBovG*h>xOt(ePhF8L^T(*I4K>v*>=%TC-QcV54IY4)eGE3cl - NeuroEvolution : Flappy Bird + + FlapAI Bird + -
- - - - - -
- GitHub Repository +
+

FlapAI Bird

+ + + + + + +
+

+

+

+

+

+

+
+
+ + + + + + + +
+ + + + + + +
Generation NumberScoreDistance
+
+
+

+
- \ No newline at end of file + From a0190cfad2f72f6ef321c9f4b24e6a3e985223b5 Mon Sep 17 00:00:00 2001 From: atmandarji Date: Thu, 7 Mar 2019 12:06:07 +0530 Subject: [PATCH 2/2] Undo some changes --- Neuroevolution.js | 297 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 278 insertions(+), 19 deletions(-) diff --git a/Neuroevolution.js b/Neuroevolution.js index 7108bfb..82b910d 100644 --- a/Neuroevolution.js +++ b/Neuroevolution.js @@ -1,46 +1,118 @@ +/** + * Provides a set of classes and methods for handling Neuroevolution and + * genetic algorithms. + * + * @param {options} An object of options for Neuroevolution. + */ var Neuroevolution = function (options) { - var self = this; + var self = this; // reference to the top scope of this module + + // Declaration of module parameters (options) and default values self.options = { + /** + * Logistic activation function. + * + * @param {a} Input value. + * @return Logistic function output. + */ activation: function (a) { ap = (-a) / 1; return (1 / (1 + Math.exp(ap))) }, + + /** + * Returns a random value between -1 and 1. + * + * @return Random value. + */ randomClamped: function () { return Math.random() * 2 - 1; }, - network: [1, [1], 1], - population: 50, - elitism: 0.2, - randomBehaviour: 0.2, - mutationRate: 0.1, - mutationRange: 0.5, - historic: 0, - lowHistoric: false, - scoreSort: -1, - nbChild: 1 + + // various factors and parameters (along with default values). + network: [1, [1], 1], // Perceptron network structure (1 hidden + // layer). + population: 50, // Population by generation. + elitism: 0.2, // Best networks kepts unchanged for the next + // generation (rate). + randomBehaviour: 0.2, // New random networks for the next generation + // (rate). + mutationRate: 0.1, // Mutation rate on the weights of synapses. + mutationRange: 0.5, // Interval of the mutation changes on the + // synapse weight. + historic: 0, // Latest generations saved. + lowHistoric: false, // Only save score (not the network). + scoreSort: -1, // Sort order (-1 = desc, 1 = asc). + nbChild: 1 // Number of children by breeding. + } + + /** + * Override default options. + * + * @param {options} An object of Neuroevolution options. + * @return void + */ self.set = function (options) { for (var i in options) { - if (this.options[i] != undefined) { + if (this.options[i] != undefined) { // Only override if the passed in value + // is actually defined. self.options[i] = options[i]; } } } + + // Overriding default options with the pass in options self.set(options); + + + /*NEURON**********************************************************************/ + /** + * Artificial Neuron class + * + * @constructor + */ var Neuron = function () { this.value = 0; this.weights = []; } + + /** + * Initialize number of neuron weights to random clamped values. + * + * @param {nb} Number of neuron weights (number of inputs). + * @return void + */ Neuron.prototype.populate = function (nb) { this.weights = []; for (var i = 0; i < nb; i++) { this.weights.push(self.options.randomClamped()); } } + + + /*LAYER***********************************************************************/ + /** + * Neural Network Layer class. + * + * @constructor + * @param {index} Index of this Layer in the Network. + */ var Layer = function (index) { this.id = index || 0; this.neurons = []; } + + /** + * Populate the Layer with a set of randomly weighted Neurons. + * + * Each Neuron be initialied with nbInputs inputs with a random clamped + * value. + * + * @param {nbNeurons} Number of neurons. + * @param {nbInputs} Number of inputs. + * @return void + */ Layer.prototype.populate = function (nbNeurons, nbInputs) { this.neurons = []; for (var i = 0; i < nbNeurons; i++) { @@ -49,18 +121,39 @@ var Neuroevolution = function (options) { this.neurons.push(n); } } + + + /*NEURAL NETWORK**************************************************************/ + /** + * Neural Network class + * + * Composed of Neuron Layers. + * + * @constructor + */ var Network = function () { this.layers = []; } + + /** + * Generate the Network layers. + * + * @param {input} Number of Neurons in Input layer. + * @param {hidden} Number of Neurons per Hidden layer. + * @param {output} Number of Neurons in Output layer. + * @return void + */ Network.prototype.perceptronGeneration = function (input, hiddens, output) { var index = 0; var previousNeurons = 0; var layer = new Layer(index); - layer.populate(input, previousNeurons); - previousNeurons = input; + layer.populate(input, previousNeurons); // Number of Inputs will be set to + // 0 since it is an input layer. + previousNeurons = input; // number of input is size of previous layer. this.layers.push(layer); index++; for (var i in hiddens) { + // Repeat same process as first layer for each hidden layer. var layer = new Layer(index); layer.populate(hiddens[i], previousNeurons); previousNeurons = hiddens[i]; @@ -68,37 +161,59 @@ var Neuroevolution = function (options) { index++; } var layer = new Layer(index); - layer.populate(output, previousNeurons); + layer.populate(output, previousNeurons); // Number of input is equal to + // the size of the last hidden + // layer. this.layers.push(layer); } + + /** + * Create a copy of the Network (neurons and weights). + * + * Returns number of neurons per layer and a flat array of all weights. + * + * @return Network data. + */ Network.prototype.getSave = function () { var datas = { - neurons: [], - weights: [] + neurons: [], // Number of Neurons per layer. + weights: [] // Weights of each Neuron's inputs. }; for (var i in this.layers) { datas.neurons.push(this.layers[i].neurons.length); for (var j in this.layers[i].neurons) { for (var k in this.layers[i].neurons[j].weights) { + // push all input weights of each Neuron of each Layer into a flat + // array. datas.weights.push(this.layers[i].neurons[j].weights[k]); } } } return datas; } + + /** + * Apply network data (neurons and weights). + * + * @param {save} Copy of network data (neurons and weights). + * @return void + */ Network.prototype.setSave = function (save) { var previousNeurons = 0; var index = 0; var indexWeights = 0; this.layers = []; for (var i in save.neurons) { + // Create and populate layers. var layer = new Layer(index); layer.populate(save.neurons[i], previousNeurons); for (var j in layer.neurons) { for (var k in layer.neurons[j].weights) { + // Apply neurons weights to each Neuron. layer.neurons[j].weights[k] = save.weights[indexWeights]; - indexWeights++; + + indexWeights++; // Increment index of flat array. } } previousNeurons = save.neurons[i]; @@ -106,25 +221,40 @@ var Neuroevolution = function (options) { this.layers.push(layer); } } + + /** + * Compute the output of an input. + * + * @param {inputs} Set of inputs. + * @return Network output. + */ Network.prototype.compute = function (inputs) { + // Set the value of each Neuron in the input layer. for (var i in inputs) { if (this.layers[0] && this.layers[0].neurons[i]) { this.layers[0].neurons[i].value = inputs[i]; } } - var prevLayer = this.layers[0]; + var prevLayer = this.layers[0]; // Previous layer is input layer. for (var i = 1; i < this.layers.length; i++) { for (var j in this.layers[i].neurons) { + // For each Neuron in each layer. var sum = 0; for (var k in prevLayer.neurons) { + // Every Neuron in the previous layer is an input to each Neuron in + // the next layer. sum += prevLayer.neurons[k].value * this.layers[i].neurons[j].weights[k]; } + + // Compute the activation of the Neuron. this.layers[i].neurons[j].value = self.options.activation(sum); } prevLayer = this.layers[i]; } + + // All outputs of the Network. var out = []; var lastLayer = this.layers[this.layers.length - 1]; for (var i in lastLayer.neurons) { @@ -132,19 +262,53 @@ var Neuroevolution = function (options) { } return out; } + + + /*GENOME**********************************************************************/ + /** + * Genome class. + * + * Composed of a score and a Neural Network. + * + * @constructor + * + * @param {score} + * @param {network} + */ var Genome = function (score, network) { this.score = score || 0; this.network = network || null; } + + + /*GENERATION******************************************************************/ + /** + * Generation class. + * + * Composed of a set of Genomes. + * + * @constructor + */ var Generation = function () { this.genomes = []; } + + /** + * Add a genome to the generation. + * + * @param {genome} Genome to add. + * @return void. + */ Generation.prototype.addGenome = function (genome) { + // Locate position to insert Genome into. + // The gnomes should remain sorted. for (var i = 0; i < this.genomes.length; i++) { + // Sort in descending order. if (self.options.scoreSort < 0) { if (genome.score > this.genomes[i].score) { break; } + // Sort in ascending order. } else { if (genome.score < this.genomes[i].score) { break; @@ -152,17 +316,33 @@ var Neuroevolution = function (options) { } } + + // Insert genome into correct position. this.genomes.splice(i, 0, genome); } + + /** + * Breed to genomes to produce offspring(s). + * + * @param {g1} Genome 1. + * @param {g2} Genome 2. + * @param {nbChilds} Number of offspring (children). + */ Generation.prototype.breed = function (g1, g2, nbChilds) { var datas = []; for (var nb = 0; nb < nbChilds; nb++) { + // Deep clone of genome 1. var data = JSON.parse(JSON.stringify(g1)); for (var i in g2.network.weights) { + // Genetic crossover + // 0.5 is the crossover factor. + // FIXME Really should be a predefined constant. if (Math.random() <= 0.5) { data.network.weights[i] = g2.network.weights[i]; } } + + // Perform mutation on some weights. for (var i in data.network.weights) { if (Math.random() <= self.options.mutationRate) { data.network.weights[i] += Math.random() * @@ -176,12 +356,19 @@ var Neuroevolution = function (options) { return datas; } + + /** + * Generate the next generation. + * + * @return Next generation data array. + */ Generation.prototype.generateNextGeneration = function () { var nexts = []; for (var i = 0; i < Math.round(self.options.elitism * self.options.population); i++) { if (nexts.length < self.options.population) { + // Push a deep copy of ith Genome's Nethwork. nexts.push(JSON.parse(JSON.stringify(this.genomes[i].network))); } } @@ -200,11 +387,14 @@ var Neuroevolution = function (options) { var max = 0; while (true) { for (var i = 0; i < max; i++) { + // Create the children and push them to the nexts array. var childs = this.breed(this.genomes[i], this.genomes[max], (self.options.nbChild > 0 ? self.options.nbChild : 1)); for (var c in childs) { nexts.push(childs[c].network); if (nexts.length >= self.options.population) { + // Return once number of children is equal to the + // population by generatino value. return nexts; } } @@ -215,13 +405,35 @@ var Neuroevolution = function (options) { } } } + + + /*GENERATIONS*****************************************************************/ + /** + * Generations class. + * + * Hold's previous Generations and current Generation. + * + * @constructor + */ var Generations = function () { this.generations = []; var currentGeneration = new Generation(); } + + /** + * Create the first generation. + * + * @param {input} Input layer. + * @param {input} Hidden layer(s). + * @param {output} Output layer. + * @return First Generation. + */ Generations.prototype.firstGeneration = function (input, hiddens, output) { + // FIXME input, hiddens, output unused. + var out = []; for (var i = 0; i < self.options.population; i++) { + // Generate the Network and save it. var nn = new Network(); nn.perceptronGeneration(self.options.network[0], self.options.network[1], @@ -232,8 +444,15 @@ var Neuroevolution = function (options) { this.generations.push(new Generation()); return out; } + + /** + * Create the next Generation. + * + * @return Next Generation. + */ Generations.prototype.nextGeneration = function () { if (this.generations.length == 0) { + // Need to create first generation. return false; } @@ -242,22 +461,51 @@ var Neuroevolution = function (options) { this.generations.push(new Generation()); return gen; } + + /** + * Add a genome to the Generations. + * + * @param {genome} + * @return False if no Generations to add to. + */ Generations.prototype.addGenome = function (genome) { + // Can't add to a Generation if there are no Generations. if (this.generations.length == 0) return false; + + // FIXME addGenome returns void. return this.generations[this.generations.length - 1].addGenome(genome); } + + + /*SELF************************************************************************/ self.generations = new Generations(); + + /** + * Reset and create a new Generations object. + * + * @return void. + */ self.restart = function () { self.generations = new Generations(); } + + /** + * Create the next generation. + * + * @return Neural Network array for next Generation. + */ self.nextGeneration = function () { var networks = []; if (self.generations.generations.length == 0) { + // If no Generations, create first. networks = self.generations.firstGeneration(); } else { + // Otherwise, create next one. networks = self.generations.nextGeneration(); } + + // Create Networks from the current Generation. var nns = []; for (var i in networks) { var nn = new Network(); @@ -266,6 +514,7 @@ var Neuroevolution = function (options) { } if (self.options.lowHistoric) { + // Remove old Networks. if (self.generations.generations.length >= 2) { var genomes = self.generations @@ -278,6 +527,7 @@ var Neuroevolution = function (options) { } if (self.options.historic != -1) { + // Remove older generations. if (self.generations.generations.length > self.options.historic + 1) { self.generations.generations.splice(0, self.generations.generations.length - (self.options.historic + 1)); @@ -286,7 +536,16 @@ var Neuroevolution = function (options) { return nns; } + + /** + * Adds a new Genome with specified Neural Network and score. + * + * @param {network} Neural Network. + * @param {score} Score value. + * @return void. + */ self.networkScore = function (network, score) { self.generations.addGenome(new Genome(score, network.getSave())); } } +