What's wrong with my Smart Rockets code?

I was watching a YouTube video on simulating evolution with a program called Smart Rockets. I got to a point in the video, 32:47, where I was unable to continue despite being certain I had done everything right. I asked if someone could help in the comments if I posted my code and was instructed to come here with my question.

For anyone who hasn't seen the video, the program is supposed to iterate through multiple populations of rectangular "rockets" which are guided randomly, and frame by frame, by an array of vectors generated at the rockets creation time by a constructor called BuildDNA. On each iteration, a BuildPopulation attribute called Selection imposes probabilities on each rockets chance to reproduce based on laudability of their attempt to reach a target. The end game is that after several iterations, only the strongest DNA from the strongest rockets will be included in the population, and most or all rockets will be reaching the intended goal.

Since I was following a tutorial, I had to keep many variables and function faithful to tutorial creator. But I attempt to keep things intuitive and distinct in many places too. So my code won't look exactly like the code in the video. However I do not believe my error originates from this. That said, the code I have below is able to run the simulation twice and then it throws an error which I'll include at the bottom.

var population;
var maxLongevity = 200;
var target;
var lifeStage = 0;

// --------------------------------------

function setup() {
  createCanvas(400, 400);
  population = new BuildPopulation(25);
  target = createVector(width / 2, 50);
}

function draw() {
  background(44);
  fill(255, 255, 255, 255);
  noStroke();
  ellipse(target.x, target.y, 16, 16);
  population.run();
  text(lifeStage, 2, height - 2);

  lifeStage++;
  if (lifeStage == maxLongevity) {
    population.evaluate();
    population.selection();
    //population = new BuildPopulation(25);
    lifeStage = 0;
  }
}

//----------------------------------------

function BuildDNA(param1) {
  if (param1) {
    this.genes = param1;
  } else {
    this.genes = [];

    this.crossover = function(param1) {
      var newGenes = [];
      var mid = floor(random(this.genes.length));
      for (var i = 0; i < this.genes.length; i++) {
        if (i > mid) {
          newGenes[i] = this.genes[i];
        } else {
          newGenes[i] = param1.genes[i];
        }
      }
      return new BuildDNA(newGenes);
    }

    for (var i = 0; i < maxLongevity; i++) {
      this.genes[i] = p5.Vector.random2D();
      this.genes[i].setMag(.35);
    }
  }
}

function BuildPopulation(mass) {
  this.populants = [];
  this.fittest = []
  this.mass = mass;
  this.evaluate = function() {
    var topdog = 0;
    for (var i = 0; i < this.mass; i++) {
      this.populants[i].calcFitness();
      if (this.populants[i].fit > topdog) {
        topdog = this.populants[i].fit;
      }
    }    
    for (var i = 0; i < this.mass; i++) {
      this.populants[i].fit /= topdog;
    }

    this.fittest = [];

    for (var i = 0; i < this.mass; i++) {
      var suitability = this.populants[i].fit * 100;
      for (var j = 0; j < suitability; j++) {
        this.fittest.push(this.populants[i]);
      }
    }
  }

  this.run = function() {
    for (var i = 0; i < this.mass; i++) {
      this.populants[i].update();
      this.populants[i].show();
    }
  }

  this.selection = function() {
    var newPopulants = [];
    for (var i = 0; i < this.populants.length; i++) {
      var parentA = random(this.fittest).dna;
      var parentB = random(this.fittest).dna;
      var child = parentA.crossover(parentB);
      newPopulants[i] = new BuildRocket(child);
    }
    this.populants = newPopulants;
  }

  for (var i = 0; i < this.mass; i++) {
    this.populants[i] = new BuildRocket();
  }
}

function BuildRocket(param1) {                       // a for loop in BuildPopulation invokes this x times to create a population.
  this.acc = createVector();                         // acceleration. I don't really know much about vectors. I'm following a tutorial.
  this.pos = createVector(width / 2, height - 3.5);  // an initial position. still don't get the vector part.
  this.vel = createVector();                         // velocity... vectors...
  this.fit = 0;                                      // fitness score. augmented by populant.calcFitness() from population.evaluate()
  if (param1) {
    this.dna = param1;
  } else {
    this.dna = new BuildDNA();
  }

  this.applyForce = function(param1) {
    this.acc.add(param1);
  }

  this.calcFitness = function() {
    var d = dist(this.pos.x, this.pos.y, target.x, target.y);
    this.fit = map(d, 0, width, width, 0);
  }

  this.show = function() {
    push();
    noStroke();
    fill(255, 220, 18, 255)
    translate(this.pos.x, this.pos.y);
    rotate(this.vel.heading());
    rectMode(CENTER);
    rect(0, 0, 7, 2);
    pop();
  }

  this.update = function() {
    this.applyForce(this.dna.genes[lifeStage]);

    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.acc.mult(0);
  }
}

Error \/ \/ \/

Uncaught TypeError: parentA.crossover is not a function
    at BuildPopulation.selection (sketch.js:100)
    at draw (sketch.js:25)
    at p5.redraw (p5.js:16560)
    at p5.<anonymous> (p5.js:11593)`

Answers

  • What is the purpose of your code? You should add a link to the video here. Also, you could provide a description of your program. What do you mean about evolution? What does it do? A layout description will be also helpful.

    I notice you use param1 in several places? I am not sure if this variable has the same functionality but they seem to hold different type of information.

    Kf

  • edited April 2017

    Your code is still mal-indented and it's hard to study! [-(
    Go to https://JSFiddle.net and paste your code at the JavaScript section.
    Then hit the 3rd button called Tidy. *-:)

    In your class BuildDNA, the method crossover() is only created when there's no param1 parameter passed! #-o

    In fact, the logic in your class BuildDNA is a mess and it's hard to reason about. ^#(^

    You should also consider renaming your many param1 parameters w/ a customized name reflecting their role for each class constructor(). >-)

  • GoToLoop

    Thanks for your response. I worked hard to indent my code manually face palm. I wish I'd known about the jsfiddle feature before now.

    I was following a tutorial. I tried to keep things intuitive and distinct, but in the end, this isn't my original code and I'm trying to learn. I could probably have cleaned it up beautifully if I already understood the lesson. But about the crossover() method... That sounds very much like something that could stop the simulation the second time around. I'll investigate.

    The use of param in functions is a technique I picked up early while attempting to learn to code so that I could avoid making mental errors involving scope. If I had gotten any farther in the tutorial I would have assigned each parameter to variable inside the functions.

  • If you want to format your code ever again.... no need to do it manually. Do you use proceesing? Copy the code in the IDE and press ctrl+t. Done. If you don't have processing, try this next link:

    http://jsbeautifier.org/

    Share the link to the videos.

    When writing code, try to keep in mind the functionality of each part of its member at all the time. Not only when somebody gets to see your code, but when you look your code in a non-distant future, you might find it very confusing and you will see it will be very hard to maintain (and debug). In your case, a loosely type programming language does make things a bit worse. I could debug it if I knew I was dealing with objects or primitive data types in your different functions.

    Kf

  • Kfrajer

    I updated the post to include a link to the video. Just click the word 'video' or follow this link:

    What do you mean when you say "keep in mind the functionality of each part of its member"?

    As for the datatypes, I think all the function that start with the word Build are constructors.

    Start with BuildRocket Then checkout BuildPopulation

    That much should be easy to make sense of.

    The problem is when the tutorial started adding d.n.a. things got VERY chaotic. I caught most of the errors in his code myself and the ones I didn't catch I fixed when he pointed them out. But like I say, his runs fine and mine runs twice and throws an exception.

  • I didn't know you updated your first post. That is why I asked the second time.

    functionality of each part of its member at all times

    I was specifically referring to param1. The other part of the code is clear. While at this, there is nothing wrong with adding comments. It is not typical in the forum to see commented code. However, for algorithms like yours, it can get very convoluted very easily and the only way to properly follow them is to understand the underlying concept which is only know by you for now, and probably by anybody that watches the video series. I am just saying this in other to help. The more info you provide about your question, the more people will contribute to it.

    I can see you have edited your first post and provided some additional details. Great!

    Kf

  • edited April 2017

    Actually, I've just added some comments which I think, if you just observe the behavior of the rockets in the tutorial (no need to watch the series, skip ahead to the time stamp I mentioned in the original post), and then start reading my code from those comments, you'll understand very well

Sign In or Register to comment.