Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
simple AI
-
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
-
I changed my code up a bit, so here’s the main script:
from scene import * import sound import random import math import extension A = Action history = [] 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): extension.createEntity(self, []) 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): extension.createEntity(self, parent.moves) extension.mutate(self, self.entities[i]) print(self.entities[i].moves, '\n') print('------------------------------------------------------------------------\n') #entity.moves are different for every entity up to here for i in range(len(self.entities)-1): #checks if all entities have the same moves print(self.entities[i+1].moves == self.entities[i].moves) print(self.entities[i].moves) else: for entity in self.entities: if entity.done == False: extension.move(self, entity) extension.checkCollision(self, entity) pass if __name__ == '__main__': run(MyScene(), show_fps=False)
And here’s the extension script:
from scene import * import sound import random import math import time maxVel = 200 accIncrease = 15 mutationPercentage = 100 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.moves.append(sprite.acc) 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.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): 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 = Vector2(0,0) self.entities[len(self.entities)-1].acc = Vector2(0,0) 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
I really have no idea why this happens,but this happened before. Although the mutate() function does actually change the path of the squares (verified by printing out the path of every square and comparing them manually), all of these paths change to the path of the last entity in the self.entities array. I check this with 3 lines of code just after printing out all of the paths, and somehow they all are the same without some command to do that. There has to be some statement that causes this, but I have absolutely no clue how/ where/ why this occurs.
-
you have a few problems. The first one is, your calculateFitness runction has an error, which is being hidden because you are using try-except instead of catching a specific exception (i think you misplaced your colon, then forgot what you wanted to do in case of divide by zero)
Next, you have to remember that lists are objects. So
>>> a=[1,2,3] >>> b=a >>> b.append(4) >>> a [1, 2, 3, 4]
so, when you copy moves to the new entities, you are copying the object, thus mutations all happen on the same copy. assign moves.copy() instead, or [m for m in moves]
finally, I'd suggest you add a shapenode representing the selected parent, so you can see what is happening. Also you might want to label the fitness of each node. your fitness computation should pribably depend on screen size... as is, on my ipad, half the time the fitness is just under 1 ror all entities, meaning that nobody gets a preference!
finally, as an aside,
abs(entitity.position - target.position) is a convienece method to get distance.
-
@JonB great, thanks a lot!
-
@JonB said:
a=[1,2,3]
b=a
b.append(4)
a
[1, 2, 3, 4]I had a similar problem so I change the a to a tuple and the copied it to b. I needed to pop() from b but keep a unchanged so I can reset it to b at start over in my script
a=(1,2,3)
b=list(a)
b.append(4)
a
[1, 2, 3]