Skip to content

Instantly share code, notes, and snippets.

@flavioamieiro
Created June 27, 2017 15:34
Show Gist options
  • Save flavioamieiro/30c4450f9545ce38f7519c2fa42d2e3b to your computer and use it in GitHub Desktop.
Save flavioamieiro/30c4450f9545ce38f7519c2fa42d2e3b to your computer and use it in GitHub Desktop.
Coding Challenge #29 - Smart Rockets (in Python)
#https://www.youtube.com/watch?v=bGz7mv2vD6g
lifespan = 300
population_size = 25
mutation_rate = 0.01
max_force = 0.2
max_vel = 10
SAVE_FRAMES = False
def setup():
size(800, 600)
global world
world = World()
def draw():
global world
background(0)
fill(255)
ellipse(world.target.x, world.target.y, 50, 50)
text("generation: {}".format(world.generation), 50, height-20)
text("tick: {}/{}".format(world.tick, lifespan), 50, height-5)
world.population.update()
world.population.show()
world.tick += 1
if world.tick >= lifespan:
world.population.evaluate()
world.population = world.population.select()
world.tick = 0
world.generation += 1
if SAVE_FRAMES:
saveFrame("frames/####.png")
if world.generation >= 11:
noLoop()
class World(object):
def __init__(self):
self.generation = 0
self.tick = 0
self.population = Population()
self.target = PVector(width/2, 100)
class DNA(object):
def __init__(self, genes=None):
if genes is None:
self.genes = [PVector.random2D().setMag(max_force) for i in range(lifespan)]
else:
self.genes = genes
def crossover(self, other):
midpoint = int(random(0, len(self.genes)))
new_genes = self.genes[:midpoint] + other.genes[midpoint:]
# mutation
new_genes = map(lambda g: PVector.random2D().setMag(max_force) if (random(1) < mutation_rate) else g, new_genes)
return DNA(new_genes)
class Population(object):
def __init__(self, rockets=None):
self.mating_pool = []
if rockets is None:
self.rockets = [Rocket() for i in range(population_size)]
else:
self.rockets = rockets
def update(self):
for r in self.rockets:
r.update()
def show(self):
for r in self.rockets:
r.show()
def evaluate(self):
best_fit = max(self.rockets, key=lambda r: r.fitness)
max_fitness = best_fit.fitness
for r in self.rockets:
r.fitness = r.fitness / max_fitness
self.mating_pool.extend([r] * floor(r.fitness * 100))
def select(self):
new_rockets = []
for i in range(population_size):
parent_a = self.mating_pool[int(random(len(self.mating_pool)))]
parent_b = self.mating_pool[int(random(len(self.mating_pool)))]
new_dna = parent_a.dna.crossover(parent_b.dna)
new_rockets.append(Rocket(new_dna))
return Population(new_rockets)
class Rocket(object):
def __init__(self, dna=None):
if dna is None:
self.dna = DNA()
else:
self.dna = dna
self.pos = PVector(width/2, height)
self.vel = PVector()
self.acc = PVector()
self.fitness = 0
self.completed = False
self.crashed = False
self.completed_age = lifespan + 1
self.crashed_age = lifespan + 1
self._fitness = None
@property
def fitness(self):
if self._fitness is None:
self._fitness = self.calculate_fitness()
return self._fitness
@fitness.setter
def fitness(self, value):
self._fitness = value
def calculate_fitness(self):
d = dist(self.pos.x, self.pos.y, world.target.x, world.target.y)
fitness = map(d, 0, width, width, 0)
if self.crashed:
fitness = fitness / 10.0
if self.completed:
fitness = fitness * 10.0
fitness = fitness * (10 * (lifespan - self.completed_age))
return fitness
def apply_force(self, force):
self.acc.add(force)
def update(self):
if dist(self.pos.x, self.pos.y, world.target.x, world.target.y) < 20:
self.completed = True
self.completed_age = world.tick
if self.pos.x < 0 or self.pos.x > width or self.pos.y < 0 or self.pos.y > height:
self.crashed = True
self.crashed_age = world.tick
if not (self.crashed or self.completed):
self.apply_force(self.dna.genes[world.tick])
self.vel.add(self.acc)
self.pos.add(self.vel)
self.acc.mult(0)
self.vel.limit(max_vel)
def show(self):
with pushMatrix():
noStroke()
fill(255, 100)
translate(self.pos.x, self.pos.y)
rotate(self.vel.heading())
rectMode(CENTER)
rect(0, 0, 25, 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment