-1

I was following a video for this little project but i encountered this error

Traceback (most recent call last):
File "C:/Users/Dell/Desktop/Pygame/games/pong.py", line 92, in <module>
ball.setx(ball.xcor()+ball.dx)
File "C:\Users\Dell\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 1808, in setx
self._goto(Vec2D(x, self._position[1]))
File "C:\Users\Dell\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 3158, in _goto
screen._pointlist(self.currentLineItem),
File "C:\Users\Dell\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 755, in _pointlist
cl = self.cv.coords(item)
File "<string>", line 1, in coords
File "C:\Users\Dell\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 2469, in coords
self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"

This happens when I press the exit button to close the window. How to solve this?

Here's the code:

import turtle

wn = turtle.Screen()
wn.title("Pong")
wn.bgcolor('black')
wn.setup(width=800, height=600)
wn.tracer(0)
#Score
score_a = 0
score_b = 0


#paddle A
paddle_a = turtle.Turtle()
paddle_a.speed(0)
paddle_a.shape("square")
paddle_a.color("white")
paddle_a.shapesize(stretch_wid=5, stretch_len = 1)
paddle_a.penup()
paddle_a.goto(-350,0)
#paddle B
paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.shape("square")
paddle_b.color("white")
paddle_b.shapesize(stretch_wid=5, stretch_len = 1)
paddle_b.penup()
paddle_b.goto(350,0)


#Ball

ball = turtle.Turtle()
ball.speed(0)
ball.shape("circle")
ball.color("white")
ball.penup()
ball.goto(0,0)
ball.dx = 0.5
ball.dy = 0.5

#pen

pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle()
pen.goto(0,260)
pen.write("Player A: 0 Player B: 0", align="center", font=('Courier', 24, "normal"))

#Function
def paddle_a_up():
    y = paddle_a.ycor()
    y+=20
    paddle_a.sety(y)

def paddle_a_down():
    y = paddle_a.ycor()
    y-=20
    paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor()
    y+=20
    paddle_b.sety(y)


def paddle_b_down():
    y = paddle_b.ycor()
    y-=20
    paddle_b.sety(y)
#Keyboard binding
wn.listen()
wn.onkeypress(paddle_a_up, "w")
wn.onkeypress(paddle_a_down, "s")
wn.onkeypress(paddle_b_up, "Up")
wn.onkeypress(paddle_b_down, "Down")

running = True
#main game loop
while running:
    wn.update()

    #move the ball
    ball.setx(ball.xcor()+ball.dx)
    ball.sety(ball.ycor()+ball.dy)

    #border
    if ball.ycor() > 280:
        ball.sety(280)
        ball.dy*=-1

    if ball.ycor() < -280:
        ball.sety(-280)
        ball.dy*=-1

    if ball.xcor() > 380:
        ball.goto(0,0)
        ball.dx*=-1
        score_a+=1
        pen.clear()
        pen.write(f"Player A: {score_a} Player B: {score_b}", align="center", font=('Courier', 24, "normal"))

    if ball.xcor() < -380:
        ball.goto(0,0)
        ball.dx*=-1
        score_b += 1
        pen.clear()
        pen.write(f"Player A: {score_a} Player B: {score_b}", align="center", font=('Courier', 24, "normal"))

    #colliosion
    if (ball.xcor() > 330 and ball.xcor() < 340) and (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() -50):
        ball.setx(330)
        ball.dx*=-1
    if (ball.xcor() < -330 and ball.xcor() > -340) and (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() -50):
        ball.setx(-330)
        ball.dx*=-1


i read about putting the .mainloop() method but i couldn't figure out where do i put it. I understand this much that the while loop runs forever and has no condition to stop here so how do i make the loop stop? How do i tell python to stop the loop and quit the window after pressing the exit button?

cdlane
  • 40,441
  • 5
  • 32
  • 81

2 Answers2

1

It looks like turtle uses tkinter. In Tk there's a method named protocol which you can use to control what happens when certain events happen, one of those being hitting the red 'X'.

In order to invoke that we need to get the root level window.

Placing

canvas = wn.getcanvas()
root = canvas.winfo_toplevel()

will set root as your root window.

From here you can use root.protocol("WM_DELETE_WINDOW", on_close) to invoke a function named on_close or whatever you want to name it.

Your function will probably look something like this.

def on_close():
    global running
    running = False

This will break you out of your loop and close your program.

Full Code.

import turtle

wn = turtle.Screen()

canvas = wn.getcanvas()
root = canvas.winfo_toplevel()

wn.title("Pong")
wn.bgcolor('black')
wn.setup(width=800, height=600)
wn.tracer(0)
#Score
score_a = 0
score_b = 0


#paddle A
paddle_a = turtle.Turtle()
paddle_a.speed(0)
paddle_a.shape("square")
paddle_a.color("white")
paddle_a.shapesize(stretch_wid=5, stretch_len = 1)
paddle_a.penup()
paddle_a.goto(-350,0)
#paddle B
paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.shape("square")
paddle_b.color("white")
paddle_b.shapesize(stretch_wid=5, stretch_len = 1)
paddle_b.penup()
paddle_b.goto(350,0)


#Ball

ball = turtle.Turtle()
ball.speed(0)
ball.shape("circle")
ball.color("white")
ball.penup()
ball.goto(0,0)
ball.dx = 0.5
ball.dy = 0.5

#pen

pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle()
pen.goto(0,260)
pen.write("Player A: 0 Player B: 0", align="center", font=('Courier', 24, "normal"))

#Function
def paddle_a_up():
    y = paddle_a.ycor()
    y+=20
    paddle_a.sety(y)

def paddle_a_down():
    y = paddle_a.ycor()
    y-=20
    paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor()
    y+=20
    paddle_b.sety(y)


def paddle_b_down():
    y = paddle_b.ycor()
    y-=20
    paddle_b.sety(y)
#Keyboard binding
wn.listen()
wn.onkeypress(paddle_a_up, "w")
wn.onkeypress(paddle_a_down, "s")
wn.onkeypress(paddle_b_up, "Up")
wn.onkeypress(paddle_b_down, "Down")

def on_close():
    global running
    running = False

root.protocol("WM_DELETE_WINDOW", on_close)

running = True
#main game loop
while running:
    wn.update()



    #move the ball
    ball.setx(ball.xcor()+ball.dx)
    ball.sety(ball.ycor()+ball.dy)

    #border
    if ball.ycor() > 280:
        ball.sety(280)
        ball.dy*=-1

    if ball.ycor() < -280:
        ball.sety(-280)
        ball.dy*=-1

    if ball.xcor() > 380:
        ball.goto(0,0)
        ball.dx*=-1
        score_a+=1
        pen.clear()
        pen.write(f"Player A: {score_a} Player B: {score_b}", align="center", font=('Courier', 24, "normal"))

    if ball.xcor() < -380:
        ball.goto(0,0)
        ball.dx*=-1
        score_b += 1
        pen.clear()
        pen.write(f"Player A: {score_a} Player B: {score_b}", align="center", font=('Courier', 24, "normal"))

    #colliosion
    if (ball.xcor() > 330 and ball.xcor() < 340) and (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() -50):
        ball.setx(330)
        ball.dx*=-1
    if (ball.xcor() < -330 and ball.xcor() > -340) and (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() -50):
        ball.setx(-330)
        ball.dx*=-1
Axe319
  • 4,255
  • 3
  • 15
  • 31
0

We can solve this problem from within turtle, no need to drop down to tkinter underpinnings. Generally, you need to play by the rules. Specifically, no while running: aka while True:. Turtle is an event-based environment and you're not letting events get handled properly and thus the error messages.

Instead of the while loop, we need an ontimer() event:

from turtle import Screen, Turtle

FONT = ('Courier', 24, 'normal')

# Score
score_a = 0
score_b = 0

# Function
def paddle_a_up():
    paddle_a.sety(paddle_a.ycor() + 20)

def paddle_a_down():
    paddle_a.sety(paddle_a.ycor() - 20)

def paddle_b_up():
    paddle_b.sety(paddle_b.ycor() + 20)

def paddle_b_down():
    paddle_b.sety(paddle_b.ycor() - 20)

def single_step():
    global score_a, score_b

    # move the ball
    ball.setposition(ball.xcor() + ball.dx, ball.ycor() + ball.dy)

    # border
    if ball.ycor() > 280:
        ball.sety(280)
        ball.dy *= -1
    elif ball.ycor() < -280:
        ball.sety(-280)
        ball.dy *= -1

    if ball.xcor() > 380:
        ball.goto(0, 0)
        ball.dx *= -1
        score_a += 1
        pen.clear()
        pen.write(f"Player A: {score_a} Player B: {score_b}", align='center', font=FONT)

    if ball.xcor() < -380:
        ball.goto(0, 0)
        ball.dx *= -1
        score_b += 1
        pen.clear()
        pen.write(f"Player A: {score_a} Player B: {score_b}", align='center', font=FONT)

    # collision
    if 330 < ball.xcor() < 340 and paddle_b.ycor() - 50 < ball.ycor() < paddle_b.ycor() + 50:
        ball.setx(330)
        ball.dx *= -1
    elif -340 < ball.xcor() < -330 and paddle_a.ycor() - 50 < ball.ycor() < paddle_a.ycor() + 50:
        ball.setx(-330)
        ball.dx *= -1

    screen.update()

    screen.ontimer(single_step)

screen = Screen()
screen.title('Pong')
screen.bgcolor('black')
screen.setup(width=800, height=600)
screen.tracer(0)

# paddle A
paddle_a = Turtle()
paddle_a.shape('square')
paddle_a.color('white')
paddle_a.shapesize(stretch_wid=5, stretch_len=1)
paddle_a.penup()
paddle_a.setx(-350)

# paddle B
paddle_b = Turtle()
paddle_b.shape('square')
paddle_b.color('white')
paddle_b.shapesize(stretch_wid=5, stretch_len=1)
paddle_b.penup()
paddle_b.setx(350)

# Ball
ball = Turtle()
ball.shape('circle')
ball.color('white')
ball.penup()

ball.dx = 1
ball.dy = 1

# pen
pen = Turtle()
pen.hideturtle()
pen.color('white')
pen.penup()

pen.sety(260)
pen.write("Player A: 0 Player B: 0", align='center', font=FONT)

# Keyboard binding
screen.onkeypress(paddle_a_up, 'w')
screen.onkeypress(paddle_a_down, 's')
screen.onkeypress(paddle_b_up, 'Up')
screen.onkeypress(paddle_b_down, 'Down')
screen.listen()

single_step()

screen.mainloop()

Now when you close the window, that event will be handled by the same mainloop event handler that's running your game.

cdlane
  • 40,441
  • 5
  • 32
  • 81