[英]Tkinter canvas doesn't show the window
I've been making the snake game, but when I run it, it performs like it should, but withown the tkinter window shown.我一直在制作蛇游戏,但是当我运行它时,它的表现就像它应该的那样,但是没有显示的 tkinter window。 When I delete the 'while' cycle, it shows the window, but without anything inside because the visualization is inside the cycle.当我删除“while”循环时,它显示 window,但内部没有任何内容,因为可视化在循环内。 I use pycharm pro 2021.3.2, and python 3.9.我使用 pycharm pro 2021.3.2 和 python 3.9。 Here is my code:这是我的代码:
from tkinter import *
import random as rm
import time
import keyboard as kb
# Creating the window and the canvas
root = Tk()
root.geometry("390x390")
root.resizable(False, False)
c = Canvas(root, width=390, height=390, bg="white")
c.pack()
#Creating lists of all possible x and y coordinates
board = []
for i in range(39):
for j in range(39):
board.append([i, j])
# Creating the Snake class
class Snake:
def __init__(self):
# Initializating:
self.head_cords = [20, 20] # A list that contains the x and the y coordinate of the Snake head
self.body_cords = [[17, 20], [18, 20], [19, 20]] # A list with smaller lists with coordinates of each Snake block
self.direction = "right" # The directions the Snake is facing
self.lenght = 4 # The Snake length (Not the game score, that is the number of eaten Apples, but the number of the Snake blocks + 1 for the head)
def move(self):
# Creating the movement function
# Movineg the body
self.body_cords.pop(0)
self.body_cords.append(self.head_cords)
# Moving the head according to the Snake direction
if self.direction == "up":
self.head_cords[1] = self.head_cords[1] - 1
if self.direction == "down":
self.head_cords[1] = self.head_cords[1] + 1
if self.direction == "left":
self.head_cords[0] = self.head_cords[0] - 1
if self.direction == "right":
self.head_cords[0] = self.head_cords[0] + 1
def check_dead(self):
# Creating the function to check if the Snake is dead basing on 2 conditions:
# 1: If the Snake hits the wall
if self.head_cords[0] > 39 or self.head_cords[0] < 1 or self.head_cords[1] > 39 or self.head_cords[1] < 1:
return True
# 2: If the Snake hits itself
elif self.head_cords in self.body_cords:
return True
else:
return False
def grow(self):
# Creating the growth function (it will be called if the Snake eats an Apple)
self.body_cords.insert(0, [self.body_cords[0][0], self.body_cords[0][1]])
Jordan = Snake() # Creating an instance of the Snake class
# Creating the Apple class
class Apple:
def __init__(self):
# Initializating the x and the y coordinates
# To place the Apple somewhere on the board, we should check which cells are already occupied by the Snake
snake_positions = []
for pos in Jordan.body_cords:
snake_positions.append(pos)
snake_positions.append(Jordan.head_cords)
# Then we choose which cells are not occupied by the Snake
possinle_positions = []
for pos in board:
if pos in snake_positions:
pass
else:
possinle_positions.append(pos)
# And choose one of them randomly
rm.choice(possinle_positions)
self.pos = rm.choice(possinle_positions)
def check_eaten(self):
# Creating the function to check if the Apple it eaten by the Snake
if Jordan.head_cords == self.pos:
return True
else:
return False
def __del__(self):
# Creating the Apple delete function
print("+1 Apple")
nessy = Apple() # Creating an instance of the Apple class
# Creating the visualization function
def visualizate():
# Clearing everything
c.create_rectangle(0, 0, 390, 390, fill="white")
# Visualizing the Snake
# Head
c.create_rectangle(Jordan.head_cords[0]*10-9, Jordan.head_cords[0]*10, Jordan.head_cords[1]*10-9, Jordan.head_cords[1]*10,
fill="yellow", outline="black", width=2)
# Body
for block in Jordan.body_cords:
c.create_rectangle(block[0]*10-9, block[0]*10, block[1]*10-9, block[1]*10, fill="yellow", outline="white", width=2)
# Visualizing the Apple
c.create_rectangle(nessy.pos[0]*10-9, nessy.pos[0]*10, nessy.pos[1]*10-9, nessy.pos[1]*10, fill="red", outline="black", width=1)
# Creating the contlors
# If you are from stackoverflow - ignore the controls. They do nothing now
def change_direction(id):
if id == 1:
Jordan.direction = "up"
if id == 2:
Jordan.direction = "down"
if id == 3:
Jordan.direction = "left"
if id == 4:
Jordan.direction = "right"
kb.add_hotkey("up", change_direction(1))
kb.add_hotkey("down", change_direction(2))
kb.add_hotkey("left", change_direction(3))
kb.add_hotkey("right", change_direction(4))
# The game cycle
while True:
visualizate() # Visualizing what we have
# Checking if the Snake is dead
if Jordan.check_dead():
time.sleep(5)
root.destroy() #Closing
print("You died")
break
# Checking if the Apple is eaten
if nessy.check_eaten():
del nessy
Jordan.grow()
nessy = Apple()
Jordan.move()
print("move")
time.sleep(1)
root.mainloop()
root.mainloop()
is special loop which gets key/mouse events from system, sends them to widgets, update widgets and (re)draw all on screen. root.mainloop()
是一个特殊的循环,它从系统获取键/鼠标事件,将它们发送到小部件,更新小部件并(重新)在屏幕上绘制所有内容。 But your while True
runs forever so it can't run mainloop()
.但是你的while True
永远运行,所以它不能运行mainloop()
。 You may have to run it loop in separated thread
.您可能必须在单独的thread
中循环运行它。 Or inside loop you have to use root.update()
to allow tkinter
to execute one loop (and update window).或者在循环内部,您必须使用root.update()
来允许tkinter
执行一个循环(并更新窗口)。 Or you should use root.after(milliseconds, function)
instead of while
-loop and sleep()
- to execute one loop periodically.或者您应该使用root.after(milliseconds, function)
而不是while
-loop 和sleep()
- 定期执行一个循环。
tkinter
can bind()
keys to functions and it doesn't need module keyboard
(BTW: on my Linux module keyboard
needs to run as root (admin)) tkinter
可以bind()
键到功能,它不需要模块keyboard
(顺便说一句:在我的 Linux 模块keyboard
需要以 root 身份运行(管理员))
If you would use change_direction("up")
instead change_direction(1)
then you could reduce function to def change_direction(direction): jordan.direction = direction
.如果您使用change_direction("up")
代替change_direction(1)
那么您可以将 function 减少到def change_direction(direction): jordan.direction = direction
。 But as for me add_hotkey()
may need function's name without ()
and without arguments - so called "callback"
- or you would have to use lambda
to create callback kb.add_hotkey("up", lambda:change_direction("up"))
But as for me add_hotkey()
may need function's name without ()
and without arguments - so called "callback"
- or you would have to use lambda
to create callback kb.add_hotkey("up", lambda:change_direction("up"))
You use wrong values in create_rectangle
and it creates big rectangles with different sizes.您在create_rectangle
中使用了错误的值,它会创建不同大小的大矩形。 You use create_rectangle
to create white rectangle to remove previous elements but it doesn't remove previous rectangles from canvas but it only hides them behind white rectanlge - and in some moment you may have hundreds hidden rectangles - you should use canvas.delete('all')
.您使用create_rectangle
创建白色矩形来删除以前的元素,但它不会从 canvas 中删除以前的矩形,但它只会将它们隐藏在白色矩形后面 - 在某些时候你可能有数百个隐藏的矩形 - 你应该使用canvas.delete('all')
。 OR you should keep object's ID - apple_id = c.create_rectange(...)
- and later move objects to new place c.move(apple_id, ...)
或者您应该保留对象的 ID - apple_id = c.create_rectange(...)
- 然后将对象移动到新位置c.move(apple_id, ...)
Last problem: when you move then you append head to body but it is list with to elements and Python doesn't duplicate list when it append to other list but it send only reference and later when you change head's position then it change also position in body - it needs to use .copy()
to append duplicated element self.body_cords.append(self.head_cords.copy())
Last problem: when you move then you append head to body but it is list with to elements and Python doesn't duplicate list when it append to other list but it send only reference and later when you change head's position then it change also position in body - 它需要使用.copy()
到 append 重复元素self.body_cords.append(self.head_cords.copy())
EDIT: I also moved function change_direction()
into Snake.change_direction()
编辑:我还将 function change_direction()
移动到Snake.change_direction()
EDIT: ==
gives True
or False
so in check_eaten()
you can write shorter using single line return (jordan.head_cords == self.pos)
without if/else
编辑: ==
给出True
或False
因此在check_eaten()
中,您可以使用单行return (jordan.head_cords == self.pos)
编写更短的代码,而无需if/else
This code works for me这段代码对我有用
import tkinter as tk # PE8: `import *` is not preferred
import random
import time
# --- classes --- # PEP8: all classes directly after imports
# Creating the Snake class
class Snake:
def __init__(self):
# Initializating:
self.head_cords = [20, 20] # A list that contains the x and the y coordinate of the Snake head
self.body_cords = [[17, 20], [18, 20], [19, 20]] # A list with smaller lists with coordinates of each Snake block
self.direction = "right" # The directions the Snake is facing
self.lenght = 4 # The Snake length (Not the game score, that is the number of eaten Apples, but the number of the Snake blocks + 1 for the head)
def move(self):
# Creating the movement function
# Movineg the body
self.body_cords.pop(0)
self.body_cords.append(self.head_cords.copy())
# Moving the head according to the Snake direction
if self.direction == "up":
self.head_cords[1] -= 1 # shorter with `-=` / `+=`
if self.direction == "down":
self.head_cords[1] += 1 # shorter with `-=` / `+=`
if self.direction == "left":
self.head_cords[0] -= 1 # shorter with `-=` / `+=`
if self.direction == "right":
self.head_cords[0] += 1 # shorter with `-=` / `+=`
def check_dead(self):
# Creating the function to check if the Snake is dead basing on 2 conditions:
# 1: If the Snake hits the wall
if self.head_cords[0] > 39 or self.head_cords[0] < 1 or self.head_cords[1] > 39 or self.head_cords[1] < 1:
print('collide wall')
return True
# 2: If the Snake hits itself
elif self.head_cords in self.body_cords:
print('collide itself')
return True
else:
return False
def grow(self):
# Creating the growth function (it will be called if the Snake eats an Apple)
self.body_cords.insert(0, [self.body_cords[0][0], self.body_cords[0][1]])
def change_direction(self, direction):
self.direction = direction
# Creating the Apple class
class Apple:
def __init__(self):
# Initializating the x and the y coordinates
# To place the Apple somewhere on the board, we should check which cells are already occupied by the Snake
snake_positions = []
for pos in jordan.body_cords:
snake_positions.append(pos)
snake_positions.append(jordan.head_cords)
# Then we choose which cells are not occupied by the Snake
possinle_positions = []
for pos in board:
if pos not in snake_positions:
possinle_positions.append(pos)
# And choose one of them randomly
self.pos = random.choice(possinle_positions)
def check_eaten(self):
# Creating the function to check if the Apple it eaten by the Snake
#if jordan.head_cords == self.pos:
# return True
#else:
# return False
return (jordan.head_cords == self.pos) # `==` gives `True` or `False`
def __del__(self):
# Creating the Apple delete function
print("+1 Apple")
# --- functions --- # PEP8: all functions directly after classes
# Creating the visualization function
def visualizate():
# Clearing everything
#c.create_rectangle(0, 0, 390, 390, fill="white")
c.delete('all')
# Visualizing the Snake
# Head
x = jordan.head_cords[0]*10
y = jordan.head_cords[1]*10
c.create_rectangle(x-9, y-9, x, y, fill="green", outline="black", width=2)
# Body
for block in jordan.body_cords:
x = block[0]*10
y = block[1]*10
c.create_rectangle(x-9, y-9, x, y, fill="yellow", outline="white", width=2)
# Visualizing the Apple
x = nessy.pos[0]*10
y = nessy.pos[1]*10
c.create_rectangle(x-9, y-9, x, y, fill="red", outline="black", width=1)
# --- main ---
# Creating the window and the canvas
root = tk.Tk()
root.geometry("390x390")
root.resizable(False, False)
c = tk.Canvas(root, width=390, height=390, bg="white")
c.pack()
# Creating lists of all possible x and y coordinates
board = []
for i in range(39):
for j in range(39):
board.append([i, j])
jordan = Snake() # Creating an instance of the Snake class # PEP8: `CamelCaseNames` only for classes - it helps to recognize class in code
nessy = Apple() # Creating an instance of the Apple class
root.bind("<Up>", lambda event:jordan.change_direction("up"))
root.bind("<Down>", lambda event:jordan.change_direction("down"))
root.bind("<Left>", lambda event:jordan.change_direction("left"))
root.bind("<Right>", lambda event:jordan.change_direction("right"))
# The game cycle
while True:
visualizate() # Visualizing what we have
print('check snake')
# Checking if the Snake is dead
if jordan.check_dead():
print("You died")
root.update()
time.sleep(5)
root.destroy() # Closing
break
print('check apple')
# Checking if the Apple is eaten
if nessy.check_eaten():
del nessy
jordan.grow()
nessy = Apple()
print("move")
jordan.move()
root.update() # allow `mainloop()` to update window
time.sleep(0.5)
#root.mainloop() # no need if you use `root.update()`
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.