Evolsim: A science fair project

Last year, my school had their annual science fair, and I had to make a project. I chose to do what I do best, which was programming. I had to make it somewhat related to science, so I decided to program an evolution simulator, which I abbreviated to evolsim. That code is now about 10 months old, and I probably haven't looked at it since last year. Despite the fact that it was clearly rushed and the way it's implemented is terrible (each step is a seperate html page with no css, and data is stored in localstorage or urls), I'm still pretty amazed that I made a physics engine that can sort creatures.

A typical creature simulation url looks like this:

http://localhost/viewsim.html#%7B%22points%22%3A%5B%7B%22offsetX%22%3A4.634100356245945%2C%22offsetY%22%3A-0.32996455997966745%2C%22size%22%3A2.823225167066776%7D%2C%7B%22offsetX%22%3A-3.014571789129471%2C%22offsetY%22%3A-2.006523552259558%2C%22size%22%3A4.180568799223131%7D%2C%7B%22offsetX%22%3A-3.517528138540158%2C%22offsetY%22%3A3.4953254775113898%2C%22size%22%3A1.7961150843333393%7D%2C%7B%22offsetX%22%3A-1.0865555159193931%2C%22offsetY%22%3A3.055693552738253%2C%22size%22%3A3.9135172497396895%7D%2C%7B%22offsetX%22%3A-3.5276337650124026%2C%22offsetY%22%3A2.0574024653702105%2C%22size%22%3A5.268852416801503%7D%2C%7B%22offsetX%22%3A-3.028235472794388%2C%22offsetY%22%3A3.9978123215066983%2C%22size%22%3A4.341020298675987%7D%2C%7B%22offsetX%22%3A0.034561177730266124%2C%22offsetY%22%3A4.944052669289315%2C%22size%22%3A3.42276505029798%7D%5D%2C%22joints%22%3A%7B%220-1%22%3A%7B%22stretch%22%3A2.265814594047074%7D%2C%220-2%22%3A%7B%22stretch%22%3A4.591976990407103%7D%2C%220-3%22%3A%7B%22stretch%22%3A3.133386444705021%7D%2C%220-6%22%3A%7B%22stretch%22%3A2.5886185048692947%7D%2C%221-2%22%3A%7B%22stretch%22%3A2.4175891726803282%7D%2C%221-3%22%3A%7B%22stretch%22%3A2.8072735508184494%7D%2C%221-4%22%3A%7B%22stretch%22%3A1.3782108598750444%7D%2C%221-5%22%3A%7B%22stretch%22%3A4.989344072389161%7D%2C%221-6%22%3A%7B%22stretch%22%3A5.1791979724918935%7D%2C%222-3%22%3A%7B%22stretch%22%3A4.620452828438965%7D%2C%222-4%22%3A%7B%22stretch%22%3A4.321571615827075%7D%2C%222-5%22%3A%7B%22stretch%22%3A2.7380367900920275%7D%2C%222-6%22%3A%7B%22stretch%22%3A4.607275643798817%7D%2C%223-4%22%3A%7B%22stretch%22%3A4.967867375137104%7D%2C%223-5%22%3A%7B%22stretch%22%3A1.2041736921182729%7D%2C%223-6%22%3A%7B%22stretch%22%3A1.8059044699063822%7D%2C%220-4%22%3A%7B%22stretch%22%3A5.224366684412989%7D%2C%224-6%22%3A%7B%22stretch%22%3A5.496922258068487%7D%2C%220-5%22%3A%7B%22stretch%22%3A1.1637581525779814%7D%2C%224-5%22%3A%7B%22stretch%22%3A5.323588123749544%7D%2C%225-6%22%3A%7B%22stretch%22%3A1.3623579129409178%7D%7D%7D

Wow. That's long. But when decoded, that's:

{
  "points": [
    {
      "offsetX": 4.634100356245945,
      "offsetY": -0.32996455997966745,
      "size": 2.823225167066776
    },
    {
      "offsetX": -3.014571789129471,
      "offsetY": -2.006523552259558,
      "size": 4.180568799223131
    },
    {
      "offsetX": -3.517528138540158,
      "offsetY": 3.4953254775113898,
      "size": 1.7961150843333393
    },
    {
      "offsetX": -1.0865555159193931,
      "offsetY": 3.055693552738253,
      "size": 3.9135172497396895
    },
    {
      "offsetX": -3.5276337650124026,
      "offsetY": 2.0574024653702105,
      "size": 5.268852416801503
    },
    {
      "offsetX": -3.028235472794388,
      "offsetY": 3.9978123215066983,
      "size": 4.341020298675987
    },
    {
      "offsetX": 0.034561177730266124,
      "offsetY": 4.944052669289315,
      "size": 3.42276505029798
    }
  ],
  "joints": {
    "0-1": {
      "stretch": 2.265814594047074
    },
    "0-2": {
      "stretch": 4.591976990407103
    },
    "0-3": {
      "stretch": 3.133386444705021
    },
    "0-6": {
      "stretch": 2.5886185048692947
    },
    "1-2": {
      "stretch": 2.4175891726803282
    },
    "1-3": {
      "stretch": 2.8072735508184494
    },
    "1-4": {
      "stretch": 1.3782108598750444
    },
    "1-5": {
      "stretch": 4.989344072389161
    },
    "1-6": {
      "stretch": 5.1791979724918935
    },
    "2-3": {
      "stretch": 4.620452828438965
    },
    "2-4": {
      "stretch": 4.321571615827075
    },
    "2-5": {
      "stretch": 2.7380367900920275
    },
    "2-6": {
      "stretch": 4.607275643798817
    },
    "3-4": {
      "stretch": 4.967867375137104
    },
    "3-5": {
      "stretch": 1.2041736921182729
    },
    "3-6": {
      "stretch": 1.8059044699063822
    },
    "0-4": {
      "stretch": 5.224366684412989
    },
    "4-6": {
      "stretch": 5.496922258068487
    },
    "0-5": {
      "stretch": 1.1637581525779814
    },
    "4-5": {
      "stretch": 5.323588123749544
    },
    "5-6": {
      "stretch": 1.3623579129409178
    }
  }
}

A creature's structure is pretty simple. It has points, which have variables weights (size) and joints, which connect the points, and they have variable amounts they can stretch. This can all be rendered onto a canvas, as you can see in this post's image.

The Simulation

The simulator has a function to move a creator, which just simulates things like gravity and has a thing to prevent the creature from collapsing, so it bounces around, and then there's a parameter for the "floor", where the creature won't move below. To be honest, I don't really know how it works. But in a live simulation, it does 500 movements a second, and displays the creatures distance, which is just the average x position of all its points. This is negative if it goes towards the left of the screen. Once we've finished playing around with the creatures, we then sort them. All 20 creatures are simulated for what would be 10 seconds, 5000 movements, so 100000 creature movements occur. We record the distance of each of them, sort them, and then, to simulate natural selection, the slower half are removed. The user can then see the remaining 10 creators, which are ordered by distance traveled. In the next phase, we create a new generation. Each creature has 2 offspring, which are mutated slightly. Points may be in different starting positions, joints may have different strengths, and these things will affect how far the creature can travel in 5000 movements. Since we're now back at 20 creatures, the cycle can repeat.

Observations

Creatures that are close relatives to each other often have similar speeds, causing the fastest ones to quickly dominate as they multiply and push out the slower ones. By generation 5 or so, there are only about one or two groups of creatures with the same common ancestor from generation 1. Small changes can make the difference between whether a creature lives or dies. The most interesting creatures are the ones that look like they're going to fail, but manage to push themselves up before collapsing and continue.

Limitations

The main limitation is probably processing power. To prevent the simulation using up too many resources, I've set an arbitrarily limit on how many points a creature can have.

The future

This simulation actually has some pretty cool things that could be salvaged. The physics engine and the creature mutation system is cool. So here's an idea. What if I turned this into a game? You sign in (with your AllesID, of course!) and create your planet. Between 12 and 48 hours later, dust will have settled and life will begin to form. When you next sign in, they'll be 50 different creatures, which were randomly generated. You can click on any creature to view a live simulation of them. Because there is no randomness involved in the actual movement, they do the same thing every time. Every 6 hours, you can make a new generation, with the faster half of the previous generation having 2 offspring each, with slight variations. There could be a leaderboard, with the fastest creatures. If you think you started off with a bad set of creatures, you can nuke your planet and make a new one.

The technical stuff

I'd want the engine to remain closed source, so all processing would be done on the server. This also means that clients with low specs can also play. When a new creature is generated, it would be stored in the database. We'd only simulate it when we need to. I was entertaining the idea of storing each frame in a database, but that would use a lot of storage (250,000 records per generation), so we'll just simulate them when necessary. For the live simulation, we'd use socket.io to broadcast the creature's movements.

Funding

This is going to cost money. Not initially, but if a lot of people want to use it, it's going to need servers with good specs. Additionally, storage could become a problem. 50 creatures being generated every 6 hours for each user could grow fast, and the live simulations would need a lot of processing power if multiple creatures were being simulated simultaneously. There are a few ways this game could be monetized: You could pay a cheap price to export a video of your entire generation - all your creatures being simulated at once. I don't know, maybe people would want that after getting to a high generation, watching your creatures race each other might be interesting. Alternatively, we could make a fee when you create a planet. Or we could just run on donations, though I'm not sure how sustainable this would be. Let me know what you think on twitter!