3

I have coded a small game using python turtle, and it seems like whenever I close the turtle window manually it gives me an error, but if the game finished running and then I close it's fine. I think it has something to do with the ontimer part of the code, but I'm not sure how to fix it.

import turtle
from random import randint


wn = turtle.Screen()
circle1 = turtle.Turtle(shape = 'circle')
bullet = turtle.Turtle(shape = "circle")
bullet.ht()
bullet.speed(0)
circle1.speed(-1)
circle1.penup()
circle1.ht()
circle1.sety(-270)
circle1.st()

wn.setup(300, 600)

enemies = []
score = 0
prevscore = 1
speed = 10


for i in range(10):
    p = turtle.Turtle(shape='square',visible=False)
    p.speed(0)
    p.penup()
    p.color('blue')
    x = randint(-240,240)
    y = randint(180,360)
    p.goto(x,y)
    p.showturtle()
    enemies.append(p)

def enemy_move():
    global game_on
    global speed
    global score
    global prevscore

    for p in enemies:

        y = p.ycor()
        p.sety(y-speed)

        if p.ycor() < -300  or p.distance(bullet.pos())<30:
            if p.distance(bullet.pos())<30:
                score += 1
            p.hideturtle()
            y = randint(180,360)
            p.sety(y)
            p.showturtle()

        if circle1.isvisible() and p.distance(circle1.pos())<20:
            p.hideturtle()
            circle1.hideturtle()
            game_on = False
            wn.clear()
            circle1.goto(0, 0)
            circle1.write(f"Your final score is {score}", align ="center", font = ("Arial", 26, "normal"))


    if game_on == True:

        if score%10 == 0:

            if score != prevscore:
                speed += 0.5
            prevscore = score

        wn.ontimer(enemy_move,50)

    else:
        return

game_on = True
enemy_move()

def goright():
    if(circle1.xcor() < 130):
        circle1.seth(0)
        circle1.fd(10)
def goleft():
    if(circle1.xcor() > -130):
        circle1.seth(180)
        circle1.fd(10)

def shoot():
    bullet.penup()
    bullet.goto(circle1.pos())
    bullet.seth(90)
    bullet_move()
    bullet.showturtle()

def bullet_move():
    if bullet.ycor() <= 300:
        bullet.sety(bullet.ycor() + 10)
        wn.ontimer(bullet_move, 50)
    else:
        bullet.hideturtle()


wn.listen()

wn.onkeypress(goright, "Right")
wn.onkeypress(goleft, "Left")
wn.onkeypress(shoot, "Up")

wn.mainloop()

The error I get when I exit the code manually is this:

Traceback (most recent call last):
  File "/Users/luke/PycharmProjects/Class Teaching/Game interface example.py", line 1, in <module>
    import game
  File "/Users/luke/PycharmProjects/Class Teaching/game.py", line 31, in <module>
    p.goto(x,y)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/turtle.py", line 1777, in goto
    self._goto(Vec2D(x, y))
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/turtle.py", line 3159, in _goto
    screen._pointlist(self.currentLineItem),
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/turtle.py", line 756, in _pointlist
    cl = self.cv.coords(item)
  File "<string>", line 1, in coords
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/tkinter/__init__.py", line 2762, in coords
    self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"
2
  • Related: 1, 2, 3 Commented Apr 9, 2023 at 16:32
  • Another one that seems a bit clearer: 1 Commented Apr 25, 2023 at 20:23

1 Answer 1

4

It is caused by the for loop inside enemy_move(). If you try to exit the application by destroying the window, the for loop may still be running and so accessing the already destroyed canvas raises the exception.

You can check game_on at the beginning of each iteration of the for loop:

def enemy_move():
    ...
    for p in enemies:
        # exit the function is game_on is False
        if not game_on:
            return
        ...
    ...

Then you need to set game_on to False before destroying the window. It can be done using tkinter.protocol() (as turtle is built on tkinter):

...
def on_quit():
    global game_on
    game_on = False
    # give some time to stop the for loop before destroying window
    wn._root.after(100, wn._root.destroy)

wn._root.protocol("WM_DELETE_WINDOW", on_quit)

wn.mainloop()
Sign up to request clarification or add additional context in comments.

4 Comments

could you explain how I could implement the second part of your answer a bit more? I'm not sure how it would work
wn._root.protocol("WM_DELETE_WINDOW", on_quit) binds a callback which will be triggered by windows manager before destroying window. In the callback, you can determine whether to destroy the window or not and do whatever you want before destroying the window. You can refer the tk doc for details.
I got it! Thanks a lot for the answer
thanks for the answer! but is it a normal way to close app window? we should use private _root variable. does not Turtle provide public methods to do the same?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.