Error when opening different windows in turtle using tkinter

帅比萌擦擦* 提交于 2021-01-29 15:17:40

问题


I have a problem using turtle with tkinter in Python 3.8. Still new to programming so thanks in advance!

I have a tkinter window where you can chose to play either level one or two, every time you start the program either level will work but once you have finished the level and try another level, including the same level I get an error.

error message:

"Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "C:/Users/Kev/IdeaProjects/HelloWorld/Games/Maze.py", line 49, in load_level_2
    set_up_maze(levels[2])  # choose what level to load
  File "C:/Users/Kev/IdeaProjects/HelloWorld/Games/Maze.py", line 254, in set_up_maze
    walls.goto(screen_x, screen_y)  # make the  * characters into walls
  File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\turtle.py", line 1776, in goto
    self._goto(Vec2D(x, y))
  File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\turtle.py", line 3158, in _goto
    screen._pointlist(self.currentLineItem),
  File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\turtle.py", line 755, in _pointlist
    cl = self.cv.coords(item)
  File "<string>", line 1, in coords
  File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 2761, in coords
    self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"

code:

import turtle
import math
from time import monotonic as my_timer
import tkinter as tk

# turtle variables
bg_color = "black"
wall_shape = "square"
wall_color = "red"
player_shape = "classic"
player_color = "white"


def load_level_1():
    hide_root()
    main_level1 = turtle.Screen()
    main_level1.bgcolor(bg_color)
    main_level1.title("Level 1")
    main_level1.setup(700, 700)
    main_level1.tracer(0)
    print("try to set level up")
    set_up_maze(levels[1])
    print("level set up")
    start_time = my_timer()

    level_finished = False
    while not level_finished:
        for treasure in treasures:
            if player.has_collided(treasure):
                player.gold += treasure.gold
                treasure.destroy()
                treasures.remove(treasure)

        for end in end_points:
            if player.has_collided(end):
                print("Finish reached")
                level_finished = True

        main_level1.update()
    main_level1.clear()
    main_level1.bye()
    show_root()

    end_time = my_timer()
    total_time = end_time - start_time
    player.print_score()
    print("Total time was {:.2f} seconds".format(total_time))


def load_level_2():
    hide_root()
    main_level2 = turtle.Screen()
    main_level2.bgcolor(bg_color)
    main_level2.title("Level 2")
    main_level2.setup(700, 700)
    main_level2.tracer(0)
    print("try to set level up")
    set_up_maze(levels[2])  # choose what level to load
    print("level set up")

    start_time = my_timer()
    level_finished = False

    while not level_finished:
        for treasure in treasures:
            if player.has_collided(treasure):
                player.gold += treasure.gold
                treasure.destroy()
                treasures.remove(treasure)

        for end in end_points:
            if player.has_collided(end):
                print("Finish reached")
                level_finished = True

        main_level2.update()

    main_level2.clear()
    main_level2.bye()
    show_root()

    print("finished")
    end_time = my_timer()
    total_time = end_time - start_time
    player.print_score()
    print("Total time was {:.2f} seconds".format(total_time))


# create the pen
class Walls(turtle.Turtle):
    def __init__(self):
        turtle.Turtle.__init__(self)
        self.shape(wall_shape)
        self.color(wall_color)
        self.penup()
        self.speed(0)


class Player(turtle.Turtle):
    def __init__(self):
        turtle.Turtle.__init__(self)
        self.shape(player_shape)
        self.color(player_color)
        self.penup()
        self.speed(0)
        self.gold = 0
        self.settiltangle(-90)

    def move_up(self):
        self.settiltangle(90)
        if (self.xcor(), self.ycor() + 24) not in wall_coordinates:
            self.goto(self.xcor(), self.ycor() + 24)

    def move_down(self):
        self.settiltangle(-90)
        if (self.xcor(), self.ycor() - 24) not in wall_coordinates:
            self.goto(self.xcor(), self.ycor() - 24)

    def move_left(self):
        self.settiltangle(180)
        if (self.xcor() - 24, self.ycor()) not in wall_coordinates:
            self.goto(self.xcor() - 24, self.ycor())

    def move_right(self):
        self.settiltangle(0)
        if (self.xcor() + 24, self.ycor()) not in wall_coordinates:
            self.goto(self.xcor() + 24, self.ycor())

    def has_collided(self, other):
        a = self.xcor() - other.xcor()
        b = self.ycor() - other.ycor()
        distance = math.sqrt((a ** 2) + (b ** 2))

        if distance < 5:
            return True
        else:
            return False

    def print_score(self):
        print("Your total score is: {} ".format(self.gold))


class Treasure(turtle.Turtle):
    def __init__(self, x, y):
        turtle.Turtle.__init__(self)
        self.shape("circle")
        self.color("yellow")
        self.penup()
        self.speed(0.5)
        self.gold = 100
        self.goto(x, y)

    def destroy(self):
        self.goto(2000, 2000)
        self.hideturtle()


class Finish(turtle.Turtle):
    def __init__(self, x, y):
        turtle.Turtle.__init__(self)
        self.shape("square")
        self.color("green")
        self.penup()
        self.speed(0.5)
        self.goto(x, y)


# lists
levels = [""]
wall_coordinates = []
treasures = []
end_points = []

level_template = ["*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************",
                  "*************************"]

level_1 = ['*************************',
           '*S*****          ********',
           '*  E*******  *** ***    *',
           '** T        **** *** ** *',
           '**** ****** **** *** ** *',
           '**** **   ****   *** ** *',
           '***  ** * **         ** *',
           '**** *  * T  ******* *  *',
           '***  ***   ** T   ** * **',
           '*T *     ******** **   **',
           '** ***** ****        ** *',
           '**   ***T************** *',
           '* ** ***  ****    **  T *',
           '*T * ***       ***** ****',
           '** * **  ****        *  *',
           '** * **  * **  ***** * **',
           '**      ** **  ***     **',
           '****** ***    T   **** **',
           '**  ** ** * ** ** **** **',
           '*E* ** ** * ***** **** **',
           '* * ** **   *         ***',
           '* *       ***  **** *****',
           '* ***********  *T**    **',
           '*              *    **  *',
           '*************************']

level_2 = ['*************************',
           '*******S        E********',
           '**T *******  *** ***    *',
           '***        ****  *** ** *',
           '**** ****** **** *** ** *',
           '**** **   ****   *** ** *',
           '***  ** * **         ** *',
           '**** *  * T  ******* *  *',
           '***  ***   ** T   ** * **',
           '*T *     ******** **   **',
           '** ***** ****        ** *',
           '**   ***T************** *',
           '* ** ***  ****    **  T *',
           '*T * ***       ***** ****',
           '** * **  ****        *  *',
           '** * **  * **  ***** * **',
           '**      ** **  ***     **',
           '****** ***    T   **** **',
           '**  ** ** * ** ** **** **',
           '** ** ** * *** ** **** **',
           '*T* ** **   *         ***',
           '* *       ***  **** *****',
           '* ***********  *T**    **',
           '*              *    ** E*',
           '*************************']

levels.append(level_1)
levels.append(level_2)


def set_up_maze(level):
    for y in range(len(level)):  # get the character co ordinates
        for x in range(len(level[y])):
            character = level[y][x]  # save the character coordinates
            screen_x = -288 + (x * 24)  # calculate the screen co ordinates
            screen_y = 288 - (y * 24)

            if character == "*":
                walls.goto(screen_x, screen_y)  # make the  * characters into walls
                walls.stamp()
                wall_coordinates.append((screen_x, screen_y))

            if character == "S":        # make the player start point
                player.goto(screen_x, screen_y)

            if character == "T":    # make the treasure spawn points
                treasures.append(Treasure(screen_x, screen_y))

            if character == "E":    # make the end point
                end_points.append(Finish(screen_x, screen_y))


walls = Walls()
player = Player()

# key bindings
turtle.listen()
turtle.onkey(player.move_up, "Up")
turtle.onkey(player.move_down, "Down")
turtle.onkey(player.move_left, "Left")
turtle.onkey(player.move_right, "Right")

# tk variables
tk_bg_color = "light green"
font = ("comic sans ms", 20)
btn_height = 2
btn_width = 10
pad_x = 40
pad_y = 25


def hide_root():
    root.withdraw()


def show_root():
    root.deiconify()
    root.update()


root = tk.Tk()
root.title("Maze game")
root.config(bg=tk_bg_color)
root.geometry("250x600+600+100")
root.resizable(width=False, height=False)

title_label = tk.Label(root, text="Maze Game", font=font, bg=tk_bg_color)
title_label.grid(row=0, column=0, padx=pad_x, pady=pad_y)
level_1_btn = tk.Button(root, text="Level 1", font=font, height=btn_height, width=btn_width, bg=tk_bg_color,
                        command=load_level_1)
level_1_btn.grid(row=1, column=0, padx=pad_x, pady=pad_y)
level_2_btn = tk.Button(root, text="Level 2", font=font, height=btn_height, width=btn_width, bg=tk_bg_color,
                        command=load_level_2)
level_2_btn.grid(row=2, column=0, padx=pad_x, pady=pad_y)
close_btn = tk.Button(root, text="Exit", font=font, height=btn_height, width=btn_width, bg=tk_bg_color,
                      command=quit)
close_btn.grid(row=3, column=0, padx=pad_x, pady=pad_y)

root.mainloop()

回答1:


A number of changes need to be made to get a design that works:

First, when you use turtle within tkinter, you need to use embedded turtle (i.e. TurtleScreen, RawTurtle) not standalone turtle (Screen, Turtle).

Since TurtleScreen doesn't want to be a Toplevel instance, I swapped the maze and menu windows.

The turtle screen.clear() method is highly destructive -- besides clearing the screen, it undoes bindings, background colors, tracer setings, etc. and kills all the turtles. So we have to program accordingly.

Don't call screen.bye() if you plan to use the window again. Turtle has a distance() method, you don't have to reinvent it.

Finally, turtles wander a floating point plan. If you save the coordinates of your walls, they won't match turtle positions as turtles accumulate error. You need to coerce the comparison to integer.

Below is my attempt to rework your code to address the above issues:

from turtle import TurtleScreen, RawTurtle
from time import monotonic as my_timer
import tkinter as tk

# tk constants
TK_BG_COLOR = "light green"
FONT = ("comic sans ms", 20)
BUTTON_HEIGHT = 2
BUTTON_WIDTH = 10
PAD_X = 40
PAD_Y = 25

# turtle contants
BG_COLOR = 'black'
WALL_SHAPE = 'square'
WALL_COLOR = 'red'
WALL_SIZE = 24
PLAYER_SHAPE = 'classic'
PLAYER_COLOR = 'white'
CURSOR_SIZE = 20

level_1 = [
    '*************************',
    '*S*****          ********',
    '*   *******  *** ***    *',
    '** T        **** *** ** *',
    '**** ****** **** *** ** *',
    '**** **   ****   *** ** *',
    '***  ** * **         ** *',
    '**** *  * T  ******* *  *',
    '***  ***   ** T   ** * **',
    '*T *     ******** **   **',
    '** ***** ****        ** *',
    '**   ***T************** *',
    '* ** ***  ****    **  T *',
    '*T * ***       ***** ****',
    '** * **  ****        *  *',
    '** * **  * **  ***** * **',
    '**      ** **  ***     **',
    '****** ***    T   **** **',
    '**  ** ** * ** ** **** **',
    '*E* ** ** * ***** **** **',
    '* * ** **   *         ***',
    '* *       ***  **** *****',
    '* ***********  *T**    **',
    '*              *    **  *',
    '*************************'
]

level_2 = [
    '*************************',
    '*******S         ********',
    '**T *******  *** ***    *',
    '***        ****  *** ** *',
    '**** ****** **** *** ** *',
    '**** **   ****   *** ** *',
    '***  ** * **         ** *',
    '**** *  * T  ******* *  *',
    '***  ***   ** T   ** * **',
    '*T *     ******** **   **',
    '** ***** ****        ** *',
    '**   ***T************** *',
    '* ** ***  ****    **  T *',
    '*T * ***       ***** ****',
    '** * **  ****        *  *',
    '** * **  * **  ***** * **',
    '**      ** **  ***     **',
    '****** ***    T   **** **',
    '**  ** ** * ** ** **** **',
    '** ** ** * *** ** **** **',
    '*T* ** **   *         ***',
    '* *       ***  **** *****',
    '* ***********  *T**    **',
    '*              *    ** E*',
    '*************************'
]

levels = [("", None), ("Level 2", level_1), ("Level 2", level_2)]

class Walls(RawTurtle):
    def __init__(self, canvas):
        super().__init__(canvas)
        self.shape(WALL_SHAPE)
        self.color(WALL_COLOR)
        self.penup()

class Player(RawTurtle):
    def __init__(self, canvas):
        super().__init__(canvas)
        self.shape(PLAYER_SHAPE)
        self.color(PLAYER_COLOR)
        self.penup()
        self.setheading(270)

        self.gold = 0

    def move_up(self):
        self.setheading(90)
        if (int(self.xcor()), int(self.ycor()) + WALL_SIZE) not in wall_coordinates:
            self.sety(self.ycor() + WALL_SIZE)

    def move_down(self):
        self.setheading(270)
        if (int(self.xcor()), int(self.ycor()) - WALL_SIZE) not in wall_coordinates:
            self.sety(self.ycor() - WALL_SIZE)

    def move_left(self):
        self.setheading(180)
        if (int(self.xcor()) - WALL_SIZE, int(self.ycor())) not in wall_coordinates:
            self.setx(self.xcor() - WALL_SIZE)

    def move_right(self):
        self.setheading(0)
        if (int(self.xcor()) + WALL_SIZE, int(self.ycor())) not in wall_coordinates:
            self.setx(self.xcor() + WALL_SIZE)

    def has_collided(self, other):
        return self.distance(other) < 5

    def print_score(self):
        print("Your total score is: {} ".format(self.gold))

class Treasure(RawTurtle):
    def __init__(self, canvas, x, y):
        super().__init__(canvas)
        self.shape('circle')
        self.color('yellow')
        self.penup()
        self.goto(x, y)

        self.gold = 100

    def destroy(self):
        self.hideturtle()

class Finish(RawTurtle):
    def __init__(self, canvas, x, y):
        super().__init__(canvas)
        self.shape('square')
        self.color('green')
        self.penup()
        self.goto(x, y)

def load_level(level):
    global player

    hide_menu()
    title, maze = levels[level]
    root.title(title)
    player = Player(screen)  # recreate as it's destroyed by screen.clear()
    set_up_maze(maze)

    # rebind turtle key bindings as they're unbound by screen.clear()
    screen.onkey(player.move_up, 'Up')
    screen.onkey(player.move_down, 'Down')
    screen.onkey(player.move_left, 'Left')
    screen.onkey(player.move_right, 'Right')
    screen.listen()

    level_finished = False
    start_time = my_timer()

    while not level_finished:
        for treasure in treasures:
            if player.has_collided(treasure):
                player.gold += treasure.gold
                treasure.destroy()
                treasures.remove(treasure)

        for end in end_points:
            if player.has_collided(end):
                level_finished = True

        screen.update()

    screen.clear()
    screen.bgcolor(BG_COLOR)  # redo as it's undone by clear()
    screen.tracer(0)  # redo as it's undone by clear()

    show_menu()

    end_time = my_timer()
    total_time = end_time - start_time
    player.print_score()
    print("Total time was {:.2f} seconds".format(total_time))

def set_up_maze(maze):
    walls = Walls(screen)

    for y, row in enumerate(maze):  # get the character co ordinates
        for x, character in enumerate(row):
            screen_x = -288 + (x * WALL_SIZE)  # calculate the screen co ordinates
            screen_y = 288 - (y * WALL_SIZE)

            if character == '*':
                walls.goto(screen_x, screen_y)  # make the  * characters into walls
                walls.stamp()
                wall_coordinates.append((screen_x, screen_y))
            elif character == 'S':  # make the player start point
                player.goto(screen_x, screen_y)
            elif character == 'T':  # make the treasure spawn points
                treasures.append(Treasure(screen, screen_x, screen_y))
            elif character == 'E':  # make the end point
                end_points.append(Finish(screen, screen_x, screen_y))

def hide_menu():
    menu.withdraw()

def show_menu():
    menu.deiconify()
    menu.update()

# lists
wall_coordinates = []
treasures = []
end_points = []

root = tk.Tk()
root.title("Maze game")
root.resizable(width=False, height=False)

canvas = tk.Canvas(root, width=700, height=700)
canvas.pack()

screen = TurtleScreen(canvas)
screen.bgcolor(BG_COLOR)
screen.tracer(0)

player = None

menu = tk.Toplevel(root)
menu.title("Maze game")
menu.config(bg=TK_BG_COLOR)
menu.geometry("250x600+600+100")
menu.resizable(width=False, height=False)

title_label = tk.Label(menu, text="Maze Game", font=FONT, bg=TK_BG_COLOR)
title_label.grid(row=0, column=0, padx=PAD_X, pady=PAD_Y)
level_1_btn = tk.Button(menu, text="Level 1", font=FONT, height=BUTTON_HEIGHT, width=BUTTON_WIDTH, bg=TK_BG_COLOR, command=lambda: load_level(1))
level_1_btn.grid(row=1, column=0, padx=PAD_X, pady=PAD_Y)
level_2_btn = tk.Button(menu, text="Level 2", font=FONT, height=BUTTON_HEIGHT, width=BUTTON_WIDTH, bg=TK_BG_COLOR, command=lambda: load_level(2))
level_2_btn.grid(row=2, column=0, padx=PAD_X, pady=PAD_Y)
close_btn = tk.Button(menu, text="Exit", font=FONT, height=BUTTON_HEIGHT, width=BUTTON_WIDTH, bg=TK_BG_COLOR, command=quit)
close_btn.grid(row=3, column=0, padx=PAD_X, pady=PAD_Y)

screen.mainloop()


来源:https://stackoverflow.com/questions/62191210/error-when-opening-different-windows-in-turtle-using-tkinter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!