Flappy bird javascript

OK, I’m following a tutorial on youtube, but ran into a problem. I copied his code from the video, but when I get to where he mutates the ai i get errors. like hundreds of errors. my program don’t wanna mute no matter how I try to do it.


the console error is

Uncaught TypeError: func is not a function (matrix: line 49)

here is index.html

<html>
<head>
  <meta charset="UTF-8">
  <script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/npm/p5@1.1.4/lib/p5.min.js"></script>
  
  <script language="javascript" type="text/javascript" src="nn.js"></script>
  <script language="javascript" type="text/javascript" src="matrix.js"></script>
  <script language="javascript" type="text/javascript" src="sketch.js"></script>
  <script language="javascript" type="text/javascript" src="ga.js"></script>
  <script language="javascript" type="text/javascript" src="bird.js"></script>
  <script language="javascript" type="text/javascript" src="pipe.js"></script>

</head>

<body>
</body>
</html>

here is sketch.js

let birdImage = [];
let birds = [];
let saveBirds = [];
let pipes = [];
let hscore = [];
let population = 200;
let gen = 0;

/*
function preload() {
  birdImage[0] = loadImage('bird/bird0.png');
  birdImage[1] = loadImage('bird/bird1.png');
  birdImage[2] = loadImage('bird/bird2.png');
  birdImage[3] = loadImage('bird/bird3.png');
  birdImage[4] = loadImage('bird/bird4.png');
  birdImage[5] = loadImage('bird/bird5.png');
  birdImage[6] = loadImage('bird/bird6.png');
  birdImage[7] = loadImage('bird/bird7.png');
  birdImage[8] = loadImage('bird/bird8.png');
  birdImage[9] = loadImage('bird/bird9.png');
  birdImage[10] = loadImage('bird/bird10.png');
  birdImage[11] = loadImage('bird/bird11.png');
  birdImage[12] = loadImage('bird/bird12.png');
  birdImage[13] = loadImage('bird/bird13.png');
  birdImage[14] = loadImage('bird/bird14.png');
  birdImage[15] = loadImage('bird/bird15.png');
  birdImage[16] = loadImage('bird/bird16.png');
  birdImage[17] = loadImage('bird/bird17.png');
  birdImage[18] = loadImage('bird/bird18.png');
  birdImage[19] = loadImage('bird/bird19.png');
  birdImage[20] = loadImage('bird/bird20.png');
  birdImage[21] = loadImage('bird/bird21.png');
  birdImage[22] = loadImage('bird/bird22.png');
  birdImage[23] = loadImage('bird/bird23.png');
  birdImage[24] = loadImage('bird/bird24.png');
}
*/

function setup() {
  createCanvas(400, 400);
  for (var i = 0; i < population; i++){
    birds[i] = new Bird();
  }
  pipes.push(new Pipe());
}

function draw() {
  background(0);
  fill(255);
  for (var i = birds.length - 1; i >= 0; i--){
    birds[i].think(pipes);
    birds[i].show();
    birds[i].update();
    if (birds[i].death == true){
      saveBirds.push(birds.splice(i,1)[0]);
    }
  }
  for (var i = pipes.length - 1; i >= 0; i--){
    pipes[i].show();
    pipes[i].move();
    for (var j = birds.length - 1; j >= 0; j--){
      pipes[i].hit(birds[j]);
    }
    if(pipes[i].destroy){
      pipes.splice(i,1);
    }
  }// pipes
  if (birds.length == 0){
    nextGeneration();
  }
  fill(255);
  text("P: " + birds.length, 10, 32);
  text("G: " + gen, 10, 64);
  text("BS: " + birds[0].score, 10, 96);
}

here nn.js

class ActivationFunction {
  constructor(func, dfunc) {
    this.func = func;
    this.dfunc = dfunc;
  }
}

let sigmoid = new ActivationFunction(
  x => 1 / (1 + Math.exp(-x)),
  y => y * (1 - y)
);

let tanh = new ActivationFunction(
  x => Math.tanh(x),
  y => 1 - (y * y)
);


class NeuralNetwork {
  /*
  * if first argument is a NeuralNetwork the constructor clones it
  * USAGE: cloned_nn = new NeuralNetwork(to_clone_nn);
  */
  constructor(in_nodes, hid_nodes, out_nodes) {
    if (in_nodes instanceof NeuralNetwork) {
      let a = in_nodes;
      this.input_nodes = a.input_nodes;
      this.hidden_nodes = a.hidden_nodes;
      this.output_nodes = a.output_nodes;

      this.weights_ih = a.weights_ih.copy();
      this.weights_ho = a.weights_ho.copy();

      this.bias_h = a.bias_h.copy();
      this.bias_o = a.bias_o.copy();
    } else {
      this.input_nodes = in_nodes;
      this.hidden_nodes = hid_nodes;
      this.output_nodes = out_nodes;

      this.weights_ih = new Matrix(this.hidden_nodes, this.input_nodes);
      this.weights_ho = new Matrix(this.output_nodes, this.hidden_nodes);
      this.weights_ih.randomize();
      this.weights_ho.randomize();

      this.bias_h = new Matrix(this.hidden_nodes, 1);
      this.bias_o = new Matrix(this.output_nodes, 1);
      this.bias_h.randomize();
      this.bias_o.randomize();
    }

    // TODO: copy these as well
    this.setLearningRate();
    this.setActivationFunction();


  }

  predict(input_array) {

    // Generating the Hidden Outputs
    let inputs = Matrix.fromArray(input_array);
    let hidden = Matrix.multiply(this.weights_ih, inputs);
    hidden.add(this.bias_h);
    // activation function!
    hidden.map(this.activation_function.func);

    // Generating the output's output!
    let output = Matrix.multiply(this.weights_ho, hidden);
    output.add(this.bias_o);
    output.map(this.activation_function.func);

    // Sending back to the caller!
    return output.toArray();
  }

  setLearningRate(learning_rate = 0.1) {
    this.learning_rate = learning_rate;
  }

  setActivationFunction(func = sigmoid) {
    this.activation_function = func;
  }

  train(input_array, target_array) {
    // Generating the Hidden Outputs
    let inputs = Matrix.fromArray(input_array);
    let hidden = Matrix.multiply(this.weights_ih, inputs);
    hidden.add(this.bias_h);
    // activation function!
    hidden.map(this.activation_function.func);

    // Generating the output's output!
    let outputs = Matrix.multiply(this.weights_ho, hidden);
    outputs.add(this.bias_o);
    outputs.map(this.activation_function.func);

    // Convert array to matrix object
    let targets = Matrix.fromArray(target_array);

    // Calculate the error
    // ERROR = TARGETS - OUTPUTS
    let output_errors = Matrix.subtract(targets, outputs);

    // let gradient = outputs * (1 - outputs);
    // Calculate gradient
    let gradients = Matrix.map(outputs, this.activation_function.dfunc);
    gradients.multiply(output_errors);
    gradients.multiply(this.learning_rate);


    // Calculate deltas
    let hidden_T = Matrix.transpose(hidden);
    let weight_ho_deltas = Matrix.multiply(gradients, hidden_T);

    // Adjust the weights by deltas
    this.weights_ho.add(weight_ho_deltas);
    // Adjust the bias by its deltas (which is just the gradients)
    this.bias_o.add(gradients);

    // Calculate the hidden layer errors
    let who_t = Matrix.transpose(this.weights_ho);
    let hidden_errors = Matrix.multiply(who_t, output_errors);

    // Calculate hidden gradient
    let hidden_gradient = Matrix.map(hidden, this.activation_function.dfunc);
    hidden_gradient.multiply(hidden_errors);
    hidden_gradient.multiply(this.learning_rate);

    // Calcuate input->hidden deltas
    let inputs_T = Matrix.transpose(inputs);
    let weight_ih_deltas = Matrix.multiply(hidden_gradient, inputs_T);

    this.weights_ih.add(weight_ih_deltas);
    // Adjust the bias by its deltas (which is just the gradients)
    this.bias_h.add(hidden_gradient);

    // outputs.print();
    // targets.print();
    // error.print();
  }

  serialize() {
    return JSON.stringify(this);
  }

  static deserialize(data) {
    if (typeof data == 'string') {
      data = JSON.parse(data);
    }
    let nn = new NeuralNetwork(data.input_nodes, data.hidden_nodes, data.output_nodes);
    nn.weights_ih = Matrix.deserialize(data.weights_ih);
    nn.weights_ho = Matrix.deserialize(data.weights_ho);
    nn.bias_h = Matrix.deserialize(data.bias_h);
    nn.bias_o = Matrix.deserialize(data.bias_o);
    nn.learning_rate = data.learning_rate;
    return nn;
  }


  // Adding function for neuro-evolution
  copy() {
    return new NeuralNetwork(this);
  }

  // Accept an arbitrary function for mutation
  mutate(func) {
    this.weights_ih.map(func);
    this.weights_ho.map(func);
    this.bias_h.map(func);
    this.bias_o.map(func);
  }
}

here matrix.js

class Matrix {
  constructor(rows, cols) {
    this.rows = rows;
    this.cols = cols;
    this.data = Array(this.rows).fill().map(() => Array(this.cols).fill(0));
  }

  copy() {
    let m = new Matrix(this.rows, this.cols);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.cols; j++) {
        m.data[i][j] = this.data[i][j];
      }
    }
    return m;
  }

  static fromArray(arr) {
    return new Matrix(arr.length, 1).map((e, i) => arr[i]);
  }

  static subtract(a, b) {
    if (a.rows !== b.rows || a.cols !== b.cols) {
      console.log('Columns and Rows of A must match Columns and Rows of B.');
      return;
    }

    // Return a new Matrix a-b
    return new Matrix(a.rows, a.cols)
      .map((_, i, j) => a.data[i][j] - b.data[i][j]);
  }

  toArray() {
    let arr = [];
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.cols; j++) {
        arr.push(this.data[i][j]);
      }
    }
    return arr;
  }

  randomize() {
    return this.map(e => Math.random() * 2 - 1);
  }

  add(n) {
    if (n instanceof Matrix) {
      if (this.rows !== n.rows || this.cols !== n.cols) {
        console.log('Columns and Rows of A must match Columns and Rows of B.');
        return;
      }
      return this.map((e, i, j) => e + n.data[i][j]);
    } else {
      return this.map(e => e + n);
    }
  }

  static transpose(matrix) {
    return new Matrix(matrix.cols, matrix.rows)
      .map((_, i, j) => matrix.data[j][i]);
  }

  static multiply(a, b) {
    // Matrix product
    if (a.cols !== b.rows) {
      console.log('Columns of A must match rows of B.');
      return;
    }

    return new Matrix(a.rows, b.cols)
      .map((e, i, j) => {
        // Dot product of values in col
        let sum = 0;
        for (let k = 0; k < a.cols; k++) {
          sum += a.data[i][k] * b.data[k][j];
        }
        return sum;
      });
  }

  multiply(n) {
    if (n instanceof Matrix) {
      if (this.rows !== n.rows || this.cols !== n.cols) {
        console.log('Columns and Rows of A must match Columns and Rows of B.');
        return;
      }

      // hadamard product
      return this.map((e, i, j) => e * n.data[i][j]);
    } else {
      // Scalar product
      return this.map(e => e * n);
    }
  }

  map(func) {
    // Apply a function to every element of matrix
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.cols; j++) {
        let val = this.data[i][j];
        this.data[i][j] = func(val, i, j);
      }
    }
    return this;
  }

  static map(matrix, func) {
    // Apply a function to every element of matrix
    return new Matrix(matrix.rows, matrix.cols)
      .map((e, i, j) => func(matrix.data[i][j], i, j));
  }

  print() {
    console.table(this.data);
    return this;
  }

  serialize() {
    return JSON.stringify(this);
  }

  static deserialize(data) {
    if (typeof data == 'string') {
      data = JSON.parse(data);
    }
    let matrix = new Matrix(data.rows, data.cols);
    matrix.data = data.data;
    return matrix;
  }
}

if (typeof module !== 'undefined') {
  module.exports = Matrix;
}

here bird.js


class Bird {
  constructor(brain) {
    this.y = height/2;
    this.x = 64;
    this.w = 16;
    this.score = 0;
    this.grav = 0.7;
    this.jump = -12;
    this.vel = 0;
    this.type = random(birdImage.length - 1);
    this.type = round(this.type);
    this.death = false;
    if (brain){
      this.brain = brain.copy();
      this.brain.mutate(0.1);
    } else {
      this.brain = new NeuralNetwork(6,42,2);
    }
  }
  
  think(pipes) {
    let inputs = [];
    inputs[0] = this.y/height;
    inputs[1] = this.x/width;
    inputs[2] = this.vel/height;
    inputs[3] = pipes[0].x/width;
    inputs[4] = pipes[0].top/height;
    inputs[5] = (height - pipes[0].bottom)/height;
    let output = this.brain.predict(inputs);
    if (output[0] > output[1]){
      this.up();
    }
  }

  up() {
    this.vel += this.jump;
  }
  
  update() {
    this.score += 1;
    this.vel += this.grav;
    this.vel *= 0.9;
    this.y += this.vel;
    if (this.y > height || this.y < 0){
      this.death = true;
    }
  }
  
  show() {
    fill(255,0,0);
    ellipse(this.x,this.y,this.w*2,this.w*2);
  }
}

here pipe.js

function Pipe() {
  this.x = width + 50;
  this.space = 200;
  this.top = random(5,height - this.space);
  this.bottom = height - (this.space + this.top);
  this.w = 50;
  this.speed = 2;
  this.destroy = false;
  
  this.show = function() {
    fill(255);
    rect(this.x,0,this.w,this.top);
    rect(this.x,height - this.bottom,this.w,this.bottom);
  }
  
  this.move = function() {
    this.x -= this.speed;
    if (this.x < -50){
      this.destroy = true;
      pipes.push(new Pipe());
    }
  }
  
  this.hit = function(bird) {
    if (bird.x + bird.w > this.x && bird.x + bird.w < this.x + this.w){
      if (bird.y - bird.w < this.top || bird.y + bird.w > height - this.bottom){
        bird.death = true;
      }
    }
  }
}

here ga.js

var bestScore = [0,0,0,0,0];
var bestNum = [0,0,0,0,0];
function nextGeneration(){
  findBest();
  gen += 1;
  pipes[0].x = width + 50;
  for (let i = 0; i < population; i++){
    birds[i] = pickOne();
  }
  for (let i = 0; i < 5; i++){
    birds[i] = new Bird();
  }
  saveBirds = [];
}

function pickOne(){
  let bird = random(saveBirds);
  let child = new Bird(bird.brain);
  return child;
}

function findBest(){
  for (var i = 0; i < saveBirds.length; i++){
    if (saveBirds[i].score > bestScore[0]){
      bestScore[4] = bestScore[3];
      bestScore[3] = bestScore[2];
      bestScore[2] = bestScore[1];
      bestScore[1] = bestScore[0];
      bestScore[0] = saveBirds[i].score;
      bestNum[4] = bestNum[3];
      bestNum[3] = bestNum[2];
      bestNum[2] = bestNum[1];
      bestNum[1] = bestNum[0];
      bestNum[0] = i;
    }
    else if (saveBirds[i].score > bestScore[1]){
      bestScore[4] = bestScore[3];
      bestScore[3] = bestScore[2];
      bestScore[2] = bestScore[1];
      bestScore[1] = saveBirds[i].score;
      bestNum[4] = bestNum[3];
      bestNum[3] = bestNum[2];
      bestNum[2] = bestNum[1];
      bestNum[1] = i;
    }
    else if (saveBirds[i].score > bestScore[2]){
      bestScore[4] = bestScore[3];
      bestScore[3] = bestScore[2];
      bestScore[2] = saveBirds[i].score;
      bestNum[4] = bestNum[3];
      bestNum[3] = bestNum[2];
      bestNum[2] = i;
    }
    else if (saveBirds[i].score > bestScore[3]){
      bestScore[4] = bestScore[3];
      bestScore[3] = saveBirds[i].score;
      bestNum[4] = bestNum[3];
      bestNum[3] = i;
    }
    else if (saveBirds[i].score > bestScore[4]){
      bestScore[4] = saveBirds[i].score;
      bestNum[4] = i;
    }
  }
}

Hey Cody,

it would be awesome to see a (working) example of your project on codesandbox, so that we can fiddle around with it.

It’s very hard to give you advice just by reading your code.

Looking forward to seeing it!

How do I use codesandbox? I took a quick glance at it & it looks like i need my work on github. I think I got my prodject on github now. I never done it before.