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