i’m trying to make an ai version of flappy birds, but when i go to save the bird it cant find the saved score. it keeps saying undefined when i console.log.
sketch.js
var bird = [];
var saved = [];
var paused = false;
var space = 200;
var moveSpeed = 1;
var pipe = [];
var pipeX = 100;
var total = 500;
var highScore = 0;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
if (bird.length == 0){
nextGen();
}
if (paused == false){
for (var i = bird.length-1; i >= 0; i--){
bird[i].move();
bird[i].hit();
bird[i].think();
if (bird[i].alive == false){
saved.push(bird.splice(i,1),[0]);
}
}
for (var i = pipe.length-1; i >= 0; i--){
pipe[i].move();
if (pipe[i].destroy){
for (var j = bird.length-1; j >= 0; j--){
bird[j].score += 1;
}
pipe.splice(i,1);
}
}
}
for (var p of pipe){
p.show();
}
for (var b of bird){
b.show();
}
fill(0);
textSize(20);
text(highScore,5,20);
if (bird.length > 0){
text(bird[0].score,5,40);
}
}
function keyPressed(){
if (key == 'p'){
if (paused == true){paused = false;}
else if (paused == false){paused = true;}
}
}
bird.js
class Bird{
constructor(brain){
this.x = 32;
this.y = height/2;
this.grav = 0.7;
this.lift = -12;
this.vel = 0;
this.alive = true;
this.score = 0;
this.r = 8;
if (brain){
this.brain = brain.copy();
} else {
this.brain = new NeuralNetwork(4, 6, 2);
}
}
mutate(){
this.brain.mutate(0.1);
}
think(){
let closest = null;
let closestD = Infinity;
for (let i = 0; i < pipe.length; i++){
let d = pipe[i].x - this.x;
if (d < closestD && d > 0){
closest = pipe[i];
closestD = d;
}
}
let input = [];
input[0] = this.y/height;
input[1] = this.x/width;
input[2] = closest.x/width;
input[3] = (closest.top + (space/2))/height;
let output = this.brain.predict(input);
if (output[0] > output[1]){
this.up();
}
}
show(){
if (this.alive == false){fill(255,0,0);}
if(this.alive){fill(255,255,0);}
ellipse(this.x,this.y,this.r*2,this.r*2);
}
hit(){
this.alive = true;
for (var i = pipe.length-1; i >= 0; i--){
if (this.x > pipe[i].x && this.x < pipe[i].x + pipe[i].w){
if (this.y < pipe[i].top || this.y > height - pipe[i].bottom){
this.alive = false;
}
}
}
}
up(){
if (this.vel >= 0){
this.vel += this.lift;
}
}
move(){
this.vel += this.grav;
this.y += this.vel;
if (this.y > height || this.y < 0){
this.alive = false;
}
}
}
pipe.js
class Pipe{
constructor(){
this.x = 410;
this.w = 50;
this.top = random(25,height - 25 - space);
this.bottom = height - this.top - space;
this.destroy = false;
this.create = false;
}
show(){
fill(0,255,0);
rect(this.x,0,this.w,this.top);
rect(this.x,height - this.bottom,this.w,this.bottom);
}
move(){
this.x -= moveSpeed;
if (this.create == false && this.x < pipeX){
pipe.push(new Pipe());
this.create = true;
}
if (this.x < -this.w){
this.destroy = true;
}
}
}
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 {
// TODO: document what a, b, c are
constructor(a, b, c) {
if (a instanceof NeuralNetwork) {
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 = a;
this.hidden_nodes = b;
this.output_nodes = c;
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);
}
mutate(rate) {
function mutate(val) {
if (Math.random() < rate) {
// return 2 * Math.random() - 1;
return val + randomGaussian(0, 0.1);
} else {
return val;
}
}
this.weights_ih.map(mutate);
this.weights_ho.map(mutate);
this.bias_h.map(mutate);
this.bias_o.map(mutate);
}
}
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;
}
ga.js
function nextGen(){
var bb = [5];// 5 best saved birds
var bs = [5];// 5 best scores
for (let i = 0; i < bb.length; i++){// reset all score and birds to 0
bb[i] = 0;
bs[i] = 0;
}
pipe = [];// clear all pipes
findBest();// find the top best 5
for (let i = 0; i < total; i++){
bird[i] = PickOne();// pick a bird surprisely works fine
}
pipe.push(new Pipe());// add pipe
saved = [];// clear saved
}
function PickOne(){
let index = random(4);// pick random from best
let bird = saved[index];// get saved bird
let child = new Bird(bird.brain);// new bird with that bird brain
child.mutate();// mutate the new bird
return child;// return the bird
}
function findBest(){
for (var i = 0; i < saved.length; i++){// loop through all saved bird
var sc = saved[i].score;// get score from saved
console.log(sc);// console log score
if (sc > bs[0]){// check if score > 0
bb[4] = bb[3];
bb[3] = bb[2];
bb[2] = bb[1];
bb[1] = bb[0];
bb[0] = i;
bs[4] = bs[3];
bs[3] = bs[2];
bs[2] = bs[1];
bs[1] = bs[0];
bs[0] = sc;
if (bs[0] > highScore){highScore = bs[0];}// set highScore
}
else if (sc > bs[1]){
bb[4] = bb[3];
bb[3] = bb[2];
bb[2] = bb[1];
bb[1] = i;
bs[4] = bs[3];
bs[3] = bs[2];
bs[2] = bs[1];
bs[1] = sc;
}
else if (sc > bs[2]){
bb[4] = bb[3];
bb[3] = bb[2];
bb[2] = i;
bs[4] = bs[3];
bs[3] = bs[2];
bs[2] = sc;
}
else if (sc > bs[3]){
bb[4] = bb[3];
bb[3] = i;
bs[4] = bs[3];
bs[3] = sc;
}
else if (sc > bs[4]){
bb[4] = i;
bs[4] = sc;
}
}
}