I just watched a video of codeBullet yesterday where he explained the basics of coding a simple “intelligent” algorithm that I wanted to recreate. The idea is that the black squares (I don’t know how to make dots) try to find their way to the blue target in the top left corner. Here’s the code of my main script:
from scene import *
import sound
import random
import math
import extension
A = Action
entitiesPerGeneration = 5
class MyScene (Scene):
def setup(self):
self.position = self.size/2
self.background_color = 'white'
self.target = ShapeNode(rect(0,0,10,10))
self.target.color = 'blue'
self.target.size = (10, 10)
self.target.position = self.size/2-(self.size.x-10,10)
self.add_child(self.target)
self.obstacles = []
self.entities = []
for entity in range(entitiesPerGeneration):
self.entities.append(ShapeNode(rect(0,0,10,10)))
self.entities[entity].color = 'black'
self.entities[entity].size = (10,10)
self.entities[entity].position = (0,0)
self.entities[entity].vel = Vector2(0, 0)
self.entities[entity].acc = Vector2(0, 0)
self.entities[entity].moves = []
self.entities[entity].currentMove = 0
self.entities[entity].fitness = 0
self.entities[entity].done = False
self.add_child(self.entities[entity])
pass
def did_change_size(self):
pass
def update(self):
if extension.allDone(self):
fitness = extension.calculateFitness(self)
parent = extension.chooseParent(self)
extension.killEntities(self)
for i in range(entitiesPerGeneration-1):
extension.createEntity(self, parent.moves, parent.vel, parent.acc)
for entity in self.entities:
extension.mutate(self, entity)
extension.createEntity(self, parent.moves, parent.vel, parent.acc)
self.entities[len(self.entities)-1].color = 'pink'
else:
for entity in self.entities:
if entity.done == False:
extension.move(self, entity)
extension.checkCollision(self, entity)
pass
def touch_began(self, touch):
pass
def touch_moved(self, touch):
pass
def touch_ended(self, touch):
pass
if __name__ == '__main__':
run(MyScene(), show_fps=False)
and here’s extension.py
from scene import *
import sound
import random
import math
import time
maxVel = 100
accIncrease = 15
mutationPercentage = 5
def calculateDistance(a, b):
x1 = a[0]
x2 = b[0]
y1 = a[1]
y2 = b[1]
dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
def move(self, sprite):
if sprite.currentMove <= len(sprite.moves)-1:
sprite.acc = sprite.moves[sprite.currentMove]
else:
angle = random.randint(0, 360)
changeX = round(math.sin(math.radians(angle)), 4)
changeY = round(math.cos(math.radians(angle)), 4)
sprite.acc = Vector2(changeX, changeY) * accIncrease
sprite.vel += sprite.acc
if sprite.vel[0] <= -maxVel or sprite.vel[0] >= maxVel: #checks that velocity won't exceed maxVel
if sprite.vel[0] < 0: sprite.vel = Vector2(-maxVel, sprite.vel[1])
else: sprite.vel = Vector2(maxVel, sprite.vel[1])
if sprite.vel[1] <= -maxVel or sprite.vel[1] >= maxVel:
if sprite.vel[1] < 0: sprite.vel = Vector2(sprite.vel[0], -maxVel)
else: sprite.vel = Vector2(sprite.vel[0], maxVel)
sprite.moves.append(sprite.acc)
sprite.currentMove = sprite.currentMove + 1
sprite.run_action(Action.move_by(sprite.vel[0]*self.dt, sprite.vel[1]*self.dt,self.dt))
def allDone(self):
allDone = True
for entity in self.entities:
if entity.done == False:
allDone = False
break
return allDone
def calculateFitness(self):
for entity in self.entities:
try:
distance = calculateDistance(entity.position, self.target.position)
sprite.fitness = 1000/distance
except: ZeroDivisionError
def checkCollision(self, entity):
if entity.frame.intersects(self.target.frame):
entity.done = True
entity.color = '#00ff00'
if entity.position.x < -self.size.x/2 + entity.size.x/2 or entity.position.x > self.size.x/2 - entity.size.x/2 or entity.position.y < -self.size.y/2 + entity.size.y/2 or entity.position.y > self.size.y/2 - entity.size.y/2:
entity.done = True
entity.color = 'red'
for obstacle in self.obstacles:
if entity.frame.intersects(obstacle.frame):
entity.done = True
entity.color = 'red'
def chooseParent(self):
parents = []
for i in range(len(self.entities)):
for a in range(round(self.entities[i].fitness)+1):
parents.append(i)
parent = random.choice(parents)
return self.entities[parent]
def killEntities(self):
for sprite in self.entities:
sprite.remove_from_parent()
self.entities = []
def createEntity(self, moves, vel, acc):
self.entities.append(ShapeNode(rect(0,0,10,10)))
self.entities[len(self.entities)-1].color = 'black'
self.entities[len(self.entities)-1].size = (10,10)
self.entities[len(self.entities)-1].position = (0,0)
self.entities[len(self.entities)-1].vel = vel
self.entities[len(self.entities)-1].acc = acc
self.entities[len(self.entities)-1].moves = moves
self.entities[len(self.entities)-1].currentMove = 0
self.entities[len(self.entities)-1].fitness = 0
self.entities[len(self.entities)-1].done = False
self.add_child(self.entities[len(self.entities)-1])
def mutate(self, entity):
for i in range(len(entity.moves)):
if random.randint(1, 100) <= mutationPercentage:
angle = random.randint(0, 360)
changeX = round(math.sin(math.radians(angle)), 4)
changeY = round(math.cos(math.radians(angle)), 4)
move = Vector2(changeX, changeY) * accIncrease
entity.moves[i] = move
print('mutated')
I know it’s not the shortest and most beautiful code out there, but I hope the smart ones here can solve my issue :)
In theory, if the whole generation is dead, a new generation is created from one of the many squares. This new generation then mutates randomly. Therefore all squares have a slightly different path, even though they have the same parent. Somehow the whole generation follows the same path (which didn’t even change from the parent’s path). I tried to set the mutationPercentage to 100, so that 100% of all directions in the path are changed, but nothing changed. And afterwards pythonista crashes, again no idea why :D