qianfanyimeng 2022-01-14 16:45 采纳率: 100%
浏览 15
已结题

解释以Flappy Bird为例的进化算法

git链接如下 https://github.com/xviniette/FlappyLearn
是一个教电脑自己玩游戏的算法
因为是跨考来的所以包括基础语法在内的很多东西都不懂,请尽可能多的注释、解释一下,或者给出可以参考的材料,谢谢!

/** 以下是Neuroevolution.js
 * 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
    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.

    }

    /**
     * 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.
                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++) {
            var n = new Neuron();
            n.populate(nbInputs);
            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.
        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];
            this.layers.push(layer);
            index++;
        }
        var layer = new Layer(index);
        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: [], // 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++; // Increment index of flat array.
                }
            }
            previousNeurons = save.neurons[i];
            index++;
            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.
        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) {
            out.push(lastLayer.neurons[i].value);
        }
        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;
                }
            }

        }

        // 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() *
                        self.options.mutationRange *
                        2 -
                        self.options.mutationRange;
                }
            }
            datas.push(data);
        }

        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)));
            }
        }

        for (var i = 0; i < Math.round(self.options.randomBehaviour *
                self.options.population); i++) {
            var n = JSON.parse(JSON.stringify(this.genomes[0].network));
            for (var k in n.weights) {
                n.weights[k] = self.options.randomClamped();
            }
            if (nexts.length < self.options.population) {
                nexts.push(n);
            }
        }

        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;
                    }
                }
            }
            max++;
            if (max >= this.genomes.length - 1) {
                max = 0;
            }
        }
    }


    /*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],
                self.options.network[2]);
            out.push(nn.getSave());
        }

        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;
        }

        var gen = this.generations[this.generations.length - 1]
            .generateNextGeneration();
        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();
            nn.setSave(networks[i]);
            nns.push(nn);
        }

        if (self.options.lowHistoric) {
            // Remove old Networks.
            if (self.generations.generations.length >= 2) {
                var genomes =
                    self.generations
                    .generations[self.generations.generations.length - 2]
                    .genomes;
                for (var i in genomes) {
                    delete genomes[i].network;
                }
            }
        }

        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));
            }
        }

        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()));
    }
}

/*以下是game.js*/
(function() {
    var timeouts = [];
    var messageName = "zero-timeout-message";

    function setZeroTimeout(fn) {
        timeouts.push(fn);
        window.postMessage(messageName, "*");
    }

    function handleMessage(event) {
        if (event.source == window && event.data == messageName) {
            event.stopPropagation();
            if (timeouts.length > 0) {
                var fn = timeouts.shift();
                fn();
            }
        }
    }

    window.addEventListener("message", handleMessage, true);

    window.setZeroTimeout = setZeroTimeout;
})();

var Neuvol;
var game;
var FPS = 60;
var maxScore=0;

var images = {};

var speed = function(fps){
    FPS = parseInt(fps);
}

var loadImages = function(sources, callback){
    var nb = 0;
    var loaded = 0;
    var imgs = {};
    for(var i in sources){
        nb++;
        imgs[i] = new Image();
        imgs[i].src = sources[i];
        imgs[i].onload = function(){
            loaded++;
            if(loaded == nb){
                callback(imgs);
            }
        }
    }
}

var Bird = function(json){
    this.x = 80;
    this.y = 250;
    this.width = 40;
    this.height = 30;

    this.alive = true;
    this.gravity = 0;
    this.velocity = 0.3;
    this.jump = -6;

    this.init(json);
}

Bird.prototype.init = function(json){
    for(var i in json){
        this[i] = json[i];
    }
}

Bird.prototype.flap = function(){
    this.gravity = this.jump;
}

Bird.prototype.update = function(){
    this.gravity += this.velocity;
    this.y += this.gravity;
}

Bird.prototype.isDead = function(height, pipes){
    if(this.y >= height || this.y + this.height <= 0){
        return true;
    }
    for(var i in pipes){
        if(!(
            this.x > pipes[i].x + pipes[i].width ||
            this.x + this.width < pipes[i].x || 
            this.y > pipes[i].y + pipes[i].height ||
            this.y + this.height < pipes[i].y
            )){
            return true;
    }
}
}

var Pipe = function(json){
    this.x = 0;
    this.y = 0;
    this.width = 50;
    this.height = 40;
    this.speed = 3;

    this.init(json);
}

Pipe.prototype.init = function(json){
    for(var i in json){
        this[i] = json[i];
    }
}

Pipe.prototype.update = function(){
    this.x -= this.speed;
}

Pipe.prototype.isOut = function(){
    if(this.x + this.width < 0){
        return true;
    }
}

var Game = function(){
    this.pipes = [];
    this.birds = [];
    this.score = 0;
    this.canvas = document.querySelector("#flappy");
    this.ctx = this.canvas.getContext("2d");
    this.width = this.canvas.width;
    this.height = this.canvas.height;
    this.spawnInterval = 90;
    this.interval = 0;
    this.gen = [];
    this.alives = 0;
    this.generation = 0;
    this.backgroundSpeed = 0.5;
    this.backgroundx = 0;
    this.maxScore = 0;
}

Game.prototype.start = function(){
    this.interval = 0;
    this.score = 0;
    this.pipes = [];
    this.birds = [];

    this.gen = Neuvol.nextGeneration();
    for(var i in this.gen){
        var b = new Bird();
        this.birds.push(b)
    }
    this.generation++;
    this.alives = this.birds.length;
}

Game.prototype.update = function(){
    this.backgroundx += this.backgroundSpeed;
    var nextHoll = 0;
    if(this.birds.length > 0){
        for(var i = 0; i < this.pipes.length; i+=2){
            if(this.pipes[i].x + this.pipes[i].width > this.birds[0].x){
                nextHoll = this.pipes[i].height/this.height;
                break;
            }
        }
    }

    for(var i in this.birds){
        if(this.birds[i].alive){

            var inputs = [
            this.birds[i].y / this.height,
            nextHoll
            ];

            var res = this.gen[i].compute(inputs);
            if(res > 0.5){
                this.birds[i].flap();
            }

            this.birds[i].update();
            if(this.birds[i].isDead(this.height, this.pipes)){
                this.birds[i].alive = false;
                this.alives--;
                //console.log(this.alives);
                Neuvol.networkScore(this.gen[i], this.score);
                if(this.isItEnd()){
                    this.start();
                }
            }
        }
    }

    for(var i = 0; i < this.pipes.length; i++){
        this.pipes[i].update();
        if(this.pipes[i].isOut()){
            this.pipes.splice(i, 1);
            i--;
        }
    }

    if(this.interval == 0){
        var deltaBord = 50;
        var pipeHoll = 120;
        var hollPosition = Math.round(Math.random() * (this.height - deltaBord * 2 - pipeHoll)) +  deltaBord;
        this.pipes.push(new Pipe({x:this.width, y:0, height:hollPosition}));
        this.pipes.push(new Pipe({x:this.width, y:hollPosition+pipeHoll, height:this.height}));
    }

    this.interval++;
    if(this.interval == this.spawnInterval){
        this.interval = 0;
    }

    this.score++;
    this.maxScore = (this.score > this.maxScore) ? this.score : this.maxScore;
    var self = this;

    if(FPS == 0){
        setZeroTimeout(function(){
            self.update();
        });
    }else{
        setTimeout(function(){
            self.update();
        }, 1000/FPS);
    }
}


Game.prototype.isItEnd = function(){
    for(var i in this.birds){
        if(this.birds[i].alive){
            return false;
        }
    }
    return true;
}

Game.prototype.display = function(){
    this.ctx.clearRect(0, 0, this.width, this.height);
    for(var i = 0; i < Math.ceil(this.width / images.background.width) + 1; i++){
        this.ctx.drawImage(images.background, i * images.background.width - Math.floor(this.backgroundx%images.background.width), 0)
    }

    for(var i in this.pipes){
        if(i%2 == 0){
            this.ctx.drawImage(images.pipetop, this.pipes[i].x, this.pipes[i].y + this.pipes[i].height - images.pipetop.height, this.pipes[i].width, images.pipetop.height);
        }else{
            this.ctx.drawImage(images.pipebottom, this.pipes[i].x, this.pipes[i].y, this.pipes[i].width, images.pipetop.height);
        }
    }

    this.ctx.fillStyle = "#FFC600";
    this.ctx.strokeStyle = "#CE9E00";
    for(var i in this.birds){
        if(this.birds[i].alive){
            this.ctx.save(); 
            this.ctx.translate(this.birds[i].x + this.birds[i].width/2, this.birds[i].y + this.birds[i].height/2);
            this.ctx.rotate(Math.PI/2 * this.birds[i].gravity/20);
            this.ctx.drawImage(images.bird, -this.birds[i].width/2, -this.birds[i].height/2, this.birds[i].width, this.birds[i].height);
            this.ctx.restore();
        }
    }

    this.ctx.fillStyle = "white";
    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);

    var self = this;
    requestAnimationFrame(function(){
        self.display();
    });
}

window.onload = function(){
    var sprites = {
        bird:"./img/bird.png",
        background:"./img/background.png",
        pipetop:"./img/pipetop.png",
        pipebottom:"./img/pipebottom.png"
    }

    var start = function(){
        Neuvol = new Neuroevolution({
            population:50,
            network:[2, [2], 1],
        });
        game = new Game();
        game.start();
        game.update();
        game.display();
    }


    loadImages(sprites, function(imgs){
        images = imgs;
        start();
    })

}

<!DOCTYPE html>
<html>
<head>
    <title>NeuroEvolution : Flappy Bird</title>
    <link href='https://fonts.googleapis.com/css?family=Oswald' rel='stylesheet' type='text/css'>
</head>
<body>
    <canvas id="flappy" width="500" height="512"></canvas> <br/>
    <button onclick="speed(60)">x1</button> 
    <button onclick="speed(120)">x2</button> 
    <button onclick="speed(180)">x3</button> 
    <button onclick="speed(300)">x5</button> 
    <button onclick="speed(0)">MAX</button> 
    <br/>
    <a href="http://github.com/xviniette/FlappyLearning">GitHub Repository</a>
    <script src = "Neuroevolution.js"></script>
    <script src = "game.js"></script>
</body>
</html>

  • 写回答

1条回答 默认 最新

  • 有问必答小助手 2022-01-17 10:06
    关注

    你好,我是有问必答小助手,非常抱歉,本次您提出的有问必答问题,技术专家团超时未为您做出解答


    本次提问扣除的有问必答次数,已经为您补发到账户,我们后续会持续优化,扩大我们的服务范围,为您带来更好地服务。

    评论

报告相同问题?

问题事件

  • 系统已结题 1月22日
  • 创建了问题 1月14日

悬赏问题

  • ¥35 平滑拟合曲线该如何生成
  • ¥100 c语言,请帮蒟蒻写一个题的范例作参考
  • ¥15 名为“Product”的列已属于此 DataTable
  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 自己瞎改改,结果现在又运行不了了
  • ¥15 链式存储应该如何解决
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站