Created
June 27, 2017 15:34
-
-
Save flavioamieiro/30c4450f9545ce38f7519c2fa42d2e3b to your computer and use it in GitHub Desktop.
Coding Challenge #29 - Smart Rockets (in Python)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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