Trying to add turtles to random locations using a loop in Python, but an extra turtle keeps appearing?

Question:

First off, I apologize for the poor wording of this question. I’m not great with the vocabulary and it doesn’t help that I have no idea where I’m going wrong with this code. Basically, I was given code that runs the classic Snake Game and was instructed to add ‘portals’. These portals must appear at random locations on the screen that are not locations where a ‘food’ piece or snake segment are already present. My issue lies somewhere in this portion of the assignment– I can get the two portals to appear, but for some reason a third turtle with no assigned shape or color is also appearing at 0,0. The turtle only appears when I add in the portal code, so I know I’ve messed up somewhere with my logic. I guess I’ve been staring at it for too long, because I just cannot for the life of me figure out the issue.

Here is my portal code. Some of the lines may be overly convoluted as I’ve been messing around with it for a while, but I’m mostly worried about the extra turtle. This is also my first time working with classes. Any help would be greatly appreciated.

class Portal(turtle.Turtle):
    def __init__(self,snake):
        super().__init__()
        self.portals = []
        self.count = 0

        while self.count != 2:
            portal = turtle.Turtle()
            portal.shape('triangle')
            portal.speed(0)
            portal.color('blue')
            portal.penup()
            self.portals.append(portal)
            self.create_portal(snake)
            self.count += 1

         
    def create_portal(self,snake):

        portal_created = False
        while not portal_created:
            portal_x = random.randint(x_min, x_max)//20*20
            portal_y = random.randint(y_min, y_max)//20*20

            for segment in snake.segments:
                if segment.distance(portal_x, portal_y) < 10 or food.distance(portal_x, portal_y) < 10:
                    break

            else:
                portal_created = True
        self.portals[self.count].goto(portal_x, portal_y)

Some of the Snake code for added context if necessary:

class Snake:
    def __init__(self):
        self.segments = []
        
        for i in range(6):
            self.grow()
        self.head=self.segments[0]
    def grow(self):
        body = turtle.Turtle()
        body.speed(0)
        body.shape("square")
        body.penup()
        self.segments.append(body)

    def move(self):
        for i in range(len(self.segments)-1,0,-1):
            self.segments[i].goto(self.segments[i-1].xcor(), self.segments[i-1].ycor())
        self.head.forward(20)

Please let me know if I’m missing any explanation/code that could be of importance. This is class assignment so I’m not looking for complete answers, just a step in the right direction!

EDIT: It does seem I was missing some important information, so here is my code all put together. Everything but the portal-related code was given for the assignment. I haven’t attempted the teleportation yet.

import random
import turtle
import time

WINDOW_WIDTH = 500  
WINDOW_HEIGHT = 500 
x_min = -220 
y_min = -220
x_max = 220
y_max = 220

# window size
turtle.setup(WINDOW_WIDTH, WINDOW_HEIGHT) 
turtle.tracer(False)


#============= snake ===============

class Snake:
    def __init__(self):
        self.segments = []
        
        for i in range(6):
            self.grow()
        self.head=self.segments[0]
    def grow(self):
        body = turtle.Turtle()
        body.speed(0)
        body.shape("square")
        body.penup()
        self.segments.append(body)

    def move(self):
        for i in range(len(self.segments)-1,0,-1):
            self.segments[i].goto(self.segments[i-1].xcor(), self.segments[i-1].ycor())
        self.head.forward(20)

    def up(self):
        if int(self.head.heading()) != 270:
            self.head.setheading(90)

    def down(self):
        if int(self.head.heading()) != 90:
            self.head.setheading(270)

    def left(self):
        if int(self.head.heading()) != 0:
            self.head.setheading(180)

    def right(self):
        if int(self.head.heading()) != 180:
            self.head.setheading(0)

#============= food ===============

            
class Food(turtle.Turtle):
    def __init__(self,snake):
        super().__init__()
        self.color("red")
        self.shape("circle")
        self.turtlesize(0.8)
        self.speed(0)
        self.penup()
        self.create_food(snake)

    def create_food(self,snake):
        food_created = False
        while not food_created:
            food_x = random.randint(x_min, x_max)//20*20
            food_y = random.randint(y_min, y_max)//20*20

            for segment in snake.segments:
                
                if segment.distance(food_x,food_y) < 10:
                    break
            else:
                food_created = True
        self.goto(food_x, food_y)

#============= portal ===============

class Portal(turtle.Turtle):
    def __init__(self,snake):
        super().__init__()
        self.portals = []
        self.count = 0

        while self.count != 2:
            portal = turtle.Turtle()
            portal.shape('triangle')
            portal.speed(0)
            portal.color('blue')
            portal.penup()
            self.portals.append(portal)
            self.create_portal(snake)
            self.count += 1


         
    def create_portal(self,snake):

        portal_created = False
        while not portal_created:
            portal_x = random.randint(x_min, x_max)//20*20
            portal_y = random.randint(y_min, y_max)//20*20

            for segment in snake.segments:
                if segment.distance(portal_x, portal_y) < 10 or food.distance(portal_x, portal_y) < 10:
                    break

            else:
                portal_created = True
        self.portals[self.count].goto(portal_x, portal_y)


#============= message ===============
        
class Message(turtle.Turtle):
    def __init__(self):
        super().__init__()
        self.score = 0
    
        self.speed(0)
        self.hideturtle()
        self.penup()
        self.color("gray")
        self.goto(-20,220)
        self.write('score: ' + str(self.score), font=("Arial", 16, 'normal'))

#============= snake game ===============
        
snake = Snake()
food = Food(snake)
message = Message()
portal = Portal(snake)

turtle.onkeypress(snake.up, 'Up')  
turtle.onkeypress(snake.down, 'Down') 
turtle.onkeypress(snake.left, 'Left')
turtle.onkeypress(snake.right, 'Right')

turtle.listen()
is_over = False
while not is_over:
    turtle.update() 
    snake.move()


    if snake.head.xcor() < -250 or snake.head.xcor() > 250 or snake.head.ycor() < -250 or snake.head.ycor() > 250:
        message.goto(-40,100)
        message.write('Game Over', font=("Arial", 16, 'normal'))
        is_over = True
    
    for i in range(1,len(snake.segments)):
        if snake.head.distance(snake.segments[i]) < 10:
            message.goto(-40,100)
            message.write('Game Over', font=("Arial", 16, 'normal'))
            is_over = True
            break


    if snake.head.distance(food) < 10:    
        food.create_food(snake)
        message.score += 1      
        snake.grow()
        snake.segments[-1].goto(snake.segments[-2].xcor(),snake.segments[-2].ycor())
        message.clear()
        message.write('score: ' + str(message.score), font=("Arial", 16, 'normal'))

    time.sleep(0.1)


turtle.done()
Asked By: kaidoz

||

Answers:

You didn’t include the portion of your code where you actually call the Portal class constructor, but I am assuming that you do somewhere in the code.

The issue is that you are creating 3 turtles every time you create a portal. In portal’s constructor you have a while loop that executes twice each time creating a Turtle instance and then setting it’s shape, speed, color etc… So that is the first two turles. The third turtle is the portal itself. Portal is a subclass of turtle.Turtle, so calling simply by calling it’s constructor you have created an instance of a Turtle.

for example:

portal = Portal(snake)

In the example above, portal is the third extra turtle that has no shape color, or and is never moved from (0,0). This one line of code will create 3 turtles, the 2 inside of the while loop and the portal object itself.

A solution to this could be to turn create_portal into a function and simply move the while loop inside of the function.

def create_portal(snake):
    portals = []
    count = 0
    while self.count != 2:
        portal = turtle.Turtle()
        portal.shape('triangle')
        portal.speed(0)
        portal.color('blue')
        portal.penup()
        portals.append(portal)
        count += 1
    portal_created = False
    while not portal_created:
        portal_x = random.randint(x_min, x_max)//20*20
        portal_y = random.randint(y_min, y_max)//20*20
        for segment in snake.segments:
            if segment.distance(portal_x, portal_y) < 10 or food.distance(portal_x, portal_y) < 10:
                break
        else:
            portal_created = True
    portals[count].goto(portal_x, portal_y)

Alternatively you could keep the portal as a class, but don’t subclass the Turtle, and instead have to instance attributes represent each end of the portal. THis is likely the better solution.

For example:

class Portal:

    def __init__(self):
        self.portal1 = self.create_portal()
        self.portal2 = self.create_portal()
        
    def create_portal(self):
        portal = turtle.Turtle()
        portal.shape('triangle')
        portal.speed(0)
        portal.color('blue')
        portal.penup()
        portal_x = random.randint(x_min, x_max)//20*20
        portal_y = random.randint(y_min, y_max)//20*20
        portal.goto(portal_x, portal_y)
        return portal
Answered By: Alexander