Search code examples
pythonlistloopsturtle-graphicspython-turtle

How to use turtle.distance(x) over members of a list of turtles?


I'm trying to do collision detection over the members of a list of turtles, so that a variable returns a Boolean to break a while true loop. It works with ONE of the turtles, but not the others. It also only prints the distance (for debug) for the one that collision works with. What's confusing is the exact same syntax used in the collision function works just fine in the ball/wall collision detection loops.

import turtle
from turtle import Screen
import random
from random import randrange

screen = turtle.Screen()
screen.setup(500, 500)
screen.title('Balls Bounce')

friend = turtle.Turtle()
friend.shape('turtle')
friend.color('green')
friend.turtlesize(3)

def friendUp():
    friend.setheading(90)
    friend.forward(45)

def friendDown():
    friend.setheading(270)
    friend.forward(45)

def friendRight():
    friend.setheading(0)
    friend.forward(45)

def friendLeft():
    friend.setheading(180)
    friend.forward(45)

screen.onkey(friendUp, "Up")
screen.onkey(friendLeft, "Left")
screen.onkey(friendRight, "Right")
screen.onkey(friendDown, "Down")
screen.listen()

def createBall():
    ball = turtle.Turtle(shape="circle")
    ball.penup()
    ball.color("Red")
    ball.turtlesize(3)
    ball.setheading(randrange(361))
    ball.forward(5)
    return ball

balls = []
for i in range(5):
    b = createBall()
    balls.append(b)

bodySize = 80
halfBodySize = bodySize / 2
collisionDistance = 5
isCollision = False

def collision(balls, friend):
    distance = 0
    for b in balls:
        distance = b.distance(friend)
        print(distance // 1)
        if distance < halfBodySize:
            print('Collision!')
            global isCollision
            isCollision = True
            break
        return isCollision

while True:
    collision(balls, friend)
    if isCollision == True:
        break
    for b in balls:
        b.forward(5)
        xPos = b.position()[0]
        yPos = b.position()[1]
        if xPos > 250 or xPos < -250:
            b.forward(-5)
            b.setheading(randrange(361))
        if yPos > 250 or yPos < -250:
            b.forward(-5)
            b.setheading(randrange(361))

screen.mainloop()

I've tried using map and enumerate, but they keep returning issues that I can't seem to figure out. For instance, using enumerate returns tuple issues, and I'm not sure I set up the syntax for it correctly. Map has similar issues.


Solution

  • return isCollision needs to be indented one level further, inside the if that detects collision:

    def collision(balls, friend):
        for b in balls:
            distance = b.distance(friend)
            print(distance // 1)
    
            if distance < halfBodySize:
                print('Collision!')
                return True
    
        return False
    

    If it's outside the if, it'll always return whatever the first ball-friend collision result was and never make it past the first iteration of the loop.

    No need for global--just return the value and use it directly in the caller:

    if collision(balls, friend):
        # there was a collision between a ball and friend
    

    You can also remove your print() calls, at which point consider using any:

    def collision(balls, friend):
        return any(b.distance(friend) < halfBodySize for b in balls)
    

    Here's a minimal proof of correctness:

    from random import randint
    from turtle import Screen, Turtle
    
    
    def check_collision(ball, balls):
        return any(b.distance(ball) < HALF_BODY_SIZE for b in balls)
    
    
    def reposition():
        for t in turtles:
            t.goto(randint(-w, w), randint(-h, h))
    
        friend.color("green")
    
        if check_collision(friend, balls):
            friend.color("red")
    
        screen.update()
    
    
    HALF_BODY_SIZE = 60
    screen = Screen()
    w, h = screen.screensize()
    screen.tracer(0)
    turtles = []
    
    for _ in range(35):
        t = Turtle(shape="circle")
        turtles.append(t)
        t.shapesize(3)
        t.penup()
    
    friend, balls = turtles[-1], turtles[:-1]
    reposition()
    screen.onclick(lambda *_: reposition())
    screen.mainloop()
    

    As an aside, use snake_case for functions and variables, not camelCase, per PEP-8 convention.

    Also prefer from turtle import Screen, Turtle to avoid confusing the functional and OOP interfaces. Your current code never uses from turtle import Screen, which can be caught with a linter like flake 8.