omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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

    Pythonista
    scene
    3
    5
    3763
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Drizzel
      Drizzel last edited by

      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

      1 Reply Last reply Reply Quote 0
      • Drizzel
        Drizzel last edited by

        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.

        1 Reply Last reply Reply Quote 0
        • JonB
          JonB last edited by

          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.

          Drizzel 1 Reply Last reply Reply Quote 1
          • Drizzel
            Drizzel @JonB last edited by

            @JonB great, thanks a lot!

            1 Reply Last reply Reply Quote 0
            • Nitrous9123
              Nitrous9123 last edited by

              @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]

              1 Reply Last reply Reply Quote 0
              • First post
                Last post
              Powered by NodeBB Forums | Contributors