如何在javascript

时间:2019-07-14 21:24:58

标签: javascript

我正在尝试为游戏蛇编写一个进化神经网络。我已经编码了神经网络部分,现在我想输出每一代最优秀的个人的游戏。为此,我使用的是图形库p5.js(https://p5js.org/)。

在我的代码中,我正在运行一个循环,在其中创建基于上一代的新一代。每一代人都必须玩游戏,这就是他们的评分方式。现在,我希望每个人都玩一次之后再输出最好的人。

在每个输出的最佳个人回合之间,我希望代码等待500毫秒。我该如何实现?

这是我已经尝试过的代码,但在这里,它仅在每一代的最后一轮之后才输出板子:

async function start() {
    for (let i = 0; i < 50; i++) {
        population.createNewGeneration();

        let bestGameTurns = population.bestIndividual.game.turns; //Array of boards      
        for (let turn = 0; turn < bestGameTurns.length; turn++) {
            let board = bestGameTurns[turn];
            drawBoard(board);
            let p = new Promise(resolve => setTimeout(resolve, 500));
            await p;

            function drawBoard(board) {
                //Draw the board using p5.js rect()'s
            }
        }
    }    
}

另一个版本,但是等待在这里无效:

let i = 0;
setInterval(async () => {
    population.createNewGeneration();
    console.log(i, population.avgFitness);

    let bestGameTurns = population.bestIndividual.game.turns; //Array of boards  
    for (let turn = 0; turn < bestGameTurns.length; turn++) {
        let board = bestGameTurns[turn];
        drawBoard(board);
        let p = new Promise(resolve => setTimeout(resolve, 500));
        await p;

        function drawBoard(board) {
            //Draw the board using p5.js rect()'s
        }
    }
    i++;
}, 1);

4 个答案:

答案 0 :(得分:0)

原始想法(已废弃)

您可以这样创建一个简短函数:

function pause(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

然后在任何async函数中,您都可以这样称呼它:

async function () {}
    // something happening
    await pause(500);
    // continue
}

另一个想法

现在,您问题中的代码尚不完整,因此是一种盲目编码。

因此,首先setInterval将每1毫秒(实际上是4毫秒,因为这是JS中的最小值)运行整个函数。这意味着它将运行这些循环。我决定专注于您标记的部分。

我要求循环而不是尝试暂停它,而不是尝试暂停它,而不是尝试循环?

另外,我将drawBoard移到

setInterval(async () => {
// ^^^^^^^^ <-- this should probably go
    population.createNewGeneration();
    console.log(i, population.avgFitness);

    let bestGameTurns = population.bestIndividual.game.turns; //Array of boards  

    function tick(turn = 0) {
        let board = bestGameTurns[turn];

        function drawBoard(board) {
            //Draw the board using p5.js rect()'s
        }

        drawBoard(board);

        // here is "setTimeouted" loop
        if (turn < bestGameTurns.length) {
            setTimeout(tick, 500, turn + 1);
        }
    }

    tick();
}, 1);

答案 1 :(得分:0)

您提供的代码应该可以满足您的要求,我只能为您整理一些部分。更好地说明您面临的问题。

// The function should be defined only once.
function drawBoard(board) { }

async function start() {
    for (let i = 0; i < 50; i++) {
        population.createNewGeneration();

        const bestGameTurns = population.bestIndividual.game.turns; //Array of boards      
        for (let turn = 0; turn < bestGameTurns.length; turn++) {
            // Don't wait on first iteration
            await new Promise(resolve => setTimeout(resolve, 500 * (turn ? 0 : 1 )));

            drawBoard(bestGameTurns[turn]);
        }
    }    
}

答案 2 :(得分:0)

感谢大家,您的建议使我有了一个主意。我发现问题出在其他地方。因为javascript只在一个线程上运行(我想这就是它的调用方式),所以在运行了一代之后,我们必须停止该函数,以让运行每帧的另一个draw函数绘制电路板。绘制后,主要功能可以继续。看起来是这样:

let isDrawn = false;
let currentBoard;

async function setup() {    
    for (let i = 0; i < 50; i++) {
        population.createNewGeneration();

        const bestGameTurns = population.bestIndividual.game.turns;
        for (let turn = 0; turn < bestGameTurns.length; turn++) {
            await step(bestGameTurns[turn], turn);
        }
    }
}

function step(board, turn) {
    currentBoard = board;
    isDrawn = false;
    return new Promise(resolve => setTimeout(() => {
        if (isDrawn) resolve();
    }, 500));
}

setTimeout(() => {
    if (currentBoard) {
        drawBoard(currentBoard);
        isDrawn = true;
        currentBoard = undefined;
    }
}, 1);

const nrOfCols = 10;
const nrOfRows = 10;
const fieldWidth = 20;
const nodeNrs = [24, 8, 8, 4];
const populationSize = 200;
const mutationRate = 0.01;

let population;
let game;

let isDrawn = false;
let currentBoard;

async function setup() {
    createCanvas(500, 500);
    population = new PopulationManager(populationSize);
    
    for (let i = 0; i < 50; i++) {
        population.createNewGeneration();

        const bestGameTurns = population.bestIndividual.game.turns;
        for (let turn = 0; turn < bestGameTurns.length; turn++) {
            await step(bestGameTurns[turn]);
        }
    }
}

function step(board) {
    currentBoard = board;
    isDrawn = false;
    return new Promise(resolve => setTimeout(() => {
        if (isDrawn) resolve();
    }, 500));
}

function draw() {
    if (currentBoard) {
        drawBoard(currentBoard);
        isDrawn = true;
        currentBoard = undefined;
    }
}

function drawBoard(board) {
    board.forEach((col, colNr) => {
        col.forEach((field, rowNr) => {
            fill(field.isSnake ? "green" : field.isFruit ? "red" : "black");
            stroke(255);
            rect(colNr*fieldWidth, rowNr*fieldWidth, fieldWidth, fieldWidth);
        });
    });
}

function play(game) {    
    setInterval(() => {
        if (!game.lost) {
            game.nextTurn();
            drawBoard(game.board);
        } else {
            clearInterval(1);
        }
    }, 500);
}
class PopulationManager {
    constructor(populationSize) {
        this.population = createPopulation();

        function createPopulation() {
            let population = [];

            for (let i = 0; i < populationSize; i++) {
                let chromosomes = createRandomChromosomes();
                let i = new Individual(chromosomes);
                population.push(i);
            }

            return population;

            function createRandomChromosomes() {
                let arr = [];
                let nrOfChromosomes = calcNrOfChromosomes();

                for (let i = 0; i < nrOfChromosomes; i++)
                    arr.push(Math.random()*2-1);

                return arr;

                function calcNrOfChromosomes() {
                    let nr = 0;

                    for (let i = 0; i < nodeNrs.length - 1; i++)
                        nr += (nodeNrs[i] + 1)*nodeNrs[i + 1];

                    return nr;
                }
            }
        };
    }

    createNewGeneration() {
        let that = this;
        
        getFitnessOfPop();

        this.calcAvgFitness();
        this.findBestIndividual();

        let parents = selection();
        breed(parents);
        
        function getFitnessOfPop() {
            that.population.forEach(iv => {
                iv.fitness = iv.playGame();
            });

            that.population.sort((a, b) => a.fitness - b.fitness);
        }

        function selection() {
            let totalFitness = that.population.map(iv => iv.fitness/*  + 1 */).reduce((a,b) => a + b);
            
            let allParents = [];
            
            for (let i = 0; i < that.population.length/2; i++) { 
                allParents.push(selectRandomParents());  
            }

            return allParents;
            
            function selectRandomParents() {
                let p1, p2;
                do {
                    p1 = selectRandomParent();
                    p2 = selectRandomParent();
                } while (p1 == p2); 
                return [p1, p2];
                
                function selectRandomParent() {
                    let rdm = Math.random()*totalFitness;
                    return that.population.find((iv, i) => {
                        let sum = that.population.filter((iv2, i2) => i2 <= i).map(iv => iv.fitness /* + 1 */).reduce((a,b) => a + b);
                        return rdm <= sum;
                    });
                }
            }
        }

        function breed(allParents) {
            that.population = [];

            allParents.forEach(ps => {
                let childChromosomes = crossOver(ps[0].chromosome, ps[1].chromosome);
                childChromosomes = [mutate(childChromosomes[0]), mutate(childChromosomes[1])];
                
                let child1 = new Individual(childChromosomes[0]);
                let child2 = new Individual(childChromosomes[1]);

                that.population.push(child1);
                that.population.push(child2);
            });

            function crossOver(parent1Chromosome, parent2Chromosome) {
                let crossingPoint = Math.round(Math.random()*parent1Chromosome.length);
                
                let divided1 = divideChromosome(parent1Chromosome);
                let divided2 = divideChromosome(parent2Chromosome);

                let child1Chromosome = divided1[0].concat(divided2[1]);
                let child2Chromosome = divided2[0].concat(divided1[1]);

                return [child1Chromosome, child2Chromosome];
                
                function divideChromosome(chromosome) {
                    let part1 = chromosome.filter((g, i) => i <= crossingPoint);
                    let part2 = chromosome.filter((g, i) => i > crossingPoint);
                    return [part1, part2];
                }
            }

            function mutate(chromosome) {
                chromosome = chromosome.map(g => {
                    if (Math.random() < mutationRate) 
                        return Math.random()*2-1;
                    return g;
                });
                return chromosome;
            }
        }    
    }

    calcAvgFitness() {
        this.avgFitness = this.population.map(iv => iv.fitness).reduce((a, b) => a + b) / this.population.length;
    }

    findBestIndividual() {
        let bestFitness = -1, bestIndividual;
        
        this.population.forEach(i => {
            if (i.fitness > bestFitness) {
                bestFitness = i.fitness;
                bestIndividual = i;
            }   
        });
        
        this.bestIndividual = bestIndividual;
    }
}
class Individual {
    constructor(chromosome) {
        this.chromosome = chromosome;
        this.fitness = 0;
        
        this.game = createGame();

        function createGame() {
            let weights = convertChromosomeToWeights();
            
            let game = new Game(weights);
            
            return game;
            
            function convertChromosomeToWeights() {
                let weights = [];
                for (let i = 0; i < nodeNrs.length - 1; i++) {
                    let lArr = [];
                    
                    for (let j = 0; j < nodeNrs[i] + 1; j++) {
                        let nArr = [];
                        lArr.push(nArr);
                    } 
                    
                    weights.push(lArr);
                }

                chromosome.forEach((gene, geneIdx) => {
                    let lIdx = -1, minIdx, maxIdx = 0;
                    for (let i = 0; i < nodeNrs.length - 1; i++) {
                        let nr = 0;
                        for (let j = 0; j <= i; j++)
                            nr += (nodeNrs[j] + 1)*nodeNrs[j + 1];

                        if (geneIdx < nr) {
                            lIdx = i;
                            break;
                        }
                        
                        maxIdx = nr;
                        minIdx = maxIdx;
                    }

                    minIdx = maxIdx;

                    let nIdx = -1;
                    for (let i = 0; i < nodeNrs[lIdx] + 1; i++) {
                        let nr = minIdx + nodeNrs[lIdx + 1];;
                        
                        if (geneIdx < nr) {
                            nIdx = i;
                            break;
                        }
                        
                        maxIdx = nr;
                        minIdx = maxIdx;
                    }

                    minIdx = maxIdx;
                    
                    let wIdx = -1;
                    for (let i = 0; i < nodeNrs[lIdx + 1]; i++) {
                        let nr = minIdx + 1;
                        
                        if (geneIdx < nr) {
                            wIdx = i;
                            break;
                        }

                        maxIdx = nr;
                        minIdx = maxIdx;
                    }

                    weights[lIdx][nIdx][wIdx] = gene;
                });

                return weights;
            }
        }
    }

    playGame() {
        while (!this.game.lost) {
            this.game.nextTurn();
        }

        return this.game.score;
    }
}

class Game {
    constructor(weights) {
        let that = this;

        this.chromosome = flattenArray(weights);
        this.nn = new NeuralNetwork(weights);
        
        this.turnNr = 0;
        this.score = 0;
        this.lost = false;

        this.board = createBoard();

        this.snake = new Snake();
        setupSnake();

        this.createFruit();

        this.turns = [JSON.parse(JSON.stringify(this.board))];

        function createBoard() {
            let board = [];

            for (let colNr = 0; colNr < nrOfCols; colNr++) {
                board[colNr] = [];
                for (let rowNr = 0; rowNr < nrOfRows; rowNr++) {
                    let field = new Field(colNr, rowNr);
                    board[colNr][rowNr] = field;
                }
            }    

            return board;
        }

        function setupSnake() {
            for (let i = 0; i < 4; i++)
                that.addToTail([floor(nrOfCols/2) - i, floor(nrOfRows/2)]);

            that.length = that.snake.body.length;
        }

        function flattenArray(arr) {
            let flattened = [];
            flatten(arr);
            return flattened;
            
            function flatten(arr) {
                arr.forEach(e => {
                    if (Array.isArray(e))
                        flatten(e);
                    else
                        flattened.push(e);
                });
            }
        }
    }    

    addToTail(pos) {
        this.snake.body.push(pos);
        this.board[pos[0]][pos[1]].setSnake(true);
    }

    nextTurn() {
        let that = this;

        let direction = findDirection();
        this.moveSnake(direction);

        this.turns.push(JSON.parse(JSON.stringify(this.board)));
        this.turnNr++;

        checkEat();

        
        function findDirection() {
            let inputValues = [];
            
            for (let i = 0; i < 8; i++) {
                let distances = that.snake.look(i, that.board);
                inputValues.push(distances.distToFruit);
                inputValues.push(distances.distToWall);
                inputValues.push(distances.distToBody);
            } 

            let output = that.nn.getOutput(inputValues);

            let probability = -1;
            let direction = -1;
            output.forEach((v, vIdx) => {
                if (v > probability) {
                    probability = v;
                    direction = vIdx;
                }
            });

            return direction;
        }

        function checkEat() {
            let head = that.snake.body[0];
            let headField = that.board[head[0]][head[1]];
            if (headField.isFruit) {
                that.snake.eat();
                that.score++;
                headField.setFruit(false);
                that.createFruit();
            }
        } 
    }

    createFruit() {
        let field;

        do {
            let colNr = floor(random()*nrOfCols);
            let rowNr = floor(random()*nrOfRows);
            field = this.board[colNr][rowNr];
        } while(field.isSnake);

        field.setFruit(true);
    }

    moveSnake(newDirection) {
        let that = this;
        let oldBody = JSON.parse(JSON.stringify(that.snake.body));

        moveTail();
        makeSnakeLonger();
        moveHead();

        function moveTail() {        
            for (let i = oldBody.length - 1; i > 0; i--)
                that.snake.body[i] = oldBody[i - 1];
        }

        function moveHead() {    
            let newHeadPosition = findNewHeadPos();
            
            if (
                newHeadPosition[0] >= nrOfCols || newHeadPosition[0] < 0 || 
                newHeadPosition[1] >= nrOfRows || newHeadPosition[1] < 0
            ) {
                that.lose();
                return;    
            }
                
            
            let newHeadField = that.board[newHeadPosition[0]][newHeadPosition[1]];
            if (newHeadField.isSnake) {
                that.lose();
                return;    
            }

            addNewHeadPos(newHeadPosition);
        }

        function findNewHeadPos() {      
            if (newDirection == 0) { //up
                return [oldBody[0][0], oldBody[0][1] - 1];
            } else if (newDirection == 1) { //right
                return [oldBody[0][0] + 1, oldBody[0][1]];
            } else if (newDirection == 2) { //down
                return [oldBody[0][0], oldBody[0][1] + 1];
            } else if (newDirection == 3) { //left
                return [oldBody[0][0] - 1, oldBody[0][1]];
            }
        }

        function makeSnakeLonger() {            
            if (that.snake.length > that.snake.body.length) {
                that.addToTail(oldBody[oldBody.length - 1]);
            } else {
                removeFromTail(oldBody[oldBody.length - 1]);
            }
        }

        function removeFromTail(pos) {
            that.board[pos[0]][pos[1]].setSnake(false);
        }

        function addNewHeadPos(pos) {
            that.snake.body[0] = pos;
            that.board[pos[0]][pos[1]].setSnake(true);
        }
    }

    lose() {
        this.lost = true;
    }
}
class Field {
    constructor(col, row) {
        this.col = col;
        this.row = row;

        this.isFruit = false;
        this.isSnake = false;
    }

    setFruit(bool) {
        this.isFruit = bool;
    }

    setSnake(bool) {
        this.isSnake = bool;
    }
}

class Snake {
    constructor() {
        this.body = [];
        this.length = 4;
    }

    eat() {
        this.length++;
    }

    look(direction, board) {
        let distances = {distToFruit: 0, distToWall: 0, distToBody: 0};
        let xDiff = getXDiff(direction), yDiff = getYDiff(direction);

        let pos = [this.body[0][0] + xDiff, this.body[0][1] + yDiff];
        let dist = 1;
        
        while (pos[0] > 0 && pos[0] < nrOfRows && pos[1] > 0 && pos[1] < nrOfCols) {
            if (board[pos[0]][pos[1]].isFruit && distances.distToFruit == 0) distances.distToFruit = dist;
            if (board[pos[0]][pos[1]].isSnake && distances.distToBody == 0) distances.distToBody = dist;
            pos[0] += xDiff, pos[1] += yDiff;
            dist++;
        }

        distances.distToWall = dist;
        
        return distances;

        function getXDiff(direction) {
            if (direction == 5 || direction == 6 || direction == 7) return -1;
            else if (direction == 1 || direction == 2 || direction == 3) return 1;
            return 0;
        }
        
        function getYDiff(direction) {
            if (direction == 7 || direction == 0 || direction == 1) return -1;
            else if (direction == 3 || direction == 4 || direction == 5) return 1;
            return 0;
        }
    }
}
class NeuralNetwork {
    constructor(weights) {
        this.layers = createLayers();
        this.layers = addWeights(this.layers, weights);
     
        function createLayers() {
            let layers = [];
            
            let nrOfNodesGlobal;
            nodeNrs.forEach((nrOfNodes, lNr) => {
                nrOfNodesGlobal = nrOfNodes;
                layers[lNr] = [];
                
                for (let i = 0; i < nrOfNodes; i++) {
                    let node = createNode(lNr);
                    layers[lNr][i] = node;
                }   

                if (lNr != nodeNrs.length - 1)
                    layers[lNr].push(new Bias());                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
            });

            return layers;

            function createNode(lNr) {
                if (lNr == 0) return new InputLayerNode();
                else if (lNr == nrOfNodesGlobal - 1) return new OutputLayerNode();
                else return new HiddenLayerNode();
            }
        }

        function addWeights(layers, weights) {
            for (let lNr = 0; lNr < layers.length - 1; lNr++) {
                let l = layers[lNr];
                l.forEach((n1, nNr) => {
                    for (let n2Nr = 0; n2Nr < layers[lNr+1].length - 1; n2Nr++) { //not including bias of next layer
                        let n2 = layers[lNr+1][n2Nr];
                        let weight = weights[lNr][nNr][n2Nr];
                        let w = new Weight(n1, n2, weight);

                        n1.addWeight(w);
                    }
                });
            }

            return layers;
        }
    }

    getOutput(inputValues) {
        let output = [];

        this.layers[0].forEach((inputNeuron, nNr) => {
            if (nNr != this.layers[0].length - 1)
                inputNeuron.addToInput(inputValues[nNr]);
        });

        this.layers.forEach((l, lNr) => {
            calcOutputs(l);  
            if (lNr != this.layers.length - 1) {
                l.forEach(n => {
                    n.feedForward();
                });
            } else {
                output = l.map(n => n.output);
            }
        });

        return output;

        function calcOutputs(layer) {
            layer.forEach(n => n.output = n.activationFunction(n.summedInput, layer.map(N => N.summedInput)));
        }
    }

    log() {
        console.log(this.weights, this.nodes);
    }
}

class Node {
    constructor() {
        this.weights = [];
        this.summedInput = 0;
    }

    addWeight(w) {
        this.weights.push(w);
    }

    addToInput(input) {
        if (input == NaN)
            console.log("A");
        this.summedInput += input;
    }

    feedForward() {
        this.weights.forEach((w, wNr) => {
            let input = w.weight*this.output;
            w.to.addToInput(input);
        });
    }
}

class Bias extends Node {
    constructor() {
        super();
        this.output = 1;
    }

    activationFunction(x, allXs) {
        return x;
    }
}

class InputLayerNode extends Node {
    constructor() {
        super();
    }

    activationFunction(x, allXs) {
        return x;
    }
}

class HiddenLayerNode extends Node {
    constructor() {
        super();
    }

    activationFunction(x, allXs) {
        return leakyReLU(x);
    }
}

class OutputLayerNode extends Node {
    constructor() {
        super();
    }

    activationFunction(x, allXs) {
        return softmax(x, allXs);
    }
}

class Weight {
    constructor(from, to, weight) {
        this.from = from;
        this.to = to;
        this.weight = weight;
    }

    setWeight(newWeight) {
        this.weight = weight;
    }
}

function leakyReLU(x) {
    if (x >= 0) return x;
    else return 0.01*x;
}

function softmax(x, allXs) {
    return Math.exp(x) / allXs.map(X => Math.exp(X)).reduce((a, b) => a+b);
}
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <body>
    <script src="sketch.js"></script>
  </body>
</html>

效果不佳,但应该进行一些改进才能使它变得更好...

如果您对代码的改进有任何建议,请告诉我!

答案 3 :(得分:-1)

我尝试按照评论中的说明将其修复为步骤,希望我没有错:

     let i = 0;
    async function step(bestGameTurns, turn)
    {
      if (turn == bestGameTurns.length)
          return;
        let board = bestGameTurns[turn];
        drawBoard(board);
       let p = new Promise(resolve => setTimeout(() => step(bestGameTurns, turn+1), 500));
            await p;
    }

    function drawBoard(board) {
                //Draw the board using p5.js rect()'s
       }

    setInterval(async () => {
        population.createNewGeneration();
        console.log(i, population.avgFitness);

        let bestGameTurns = population.bestIndividual.game.turns; //Array of boards  
        step(bestGameTurns, 0);

        i++;
    }, 1);