使用 Turtle 图形的 Python 蛇游戏
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30050194/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Snake game in Python using Turtle graphics
提问by Isaac Middlemiss
So I've been working on a few games in Python (battleships, tic-tac-toe etc.) and this week's project is Snake. I've got a basic set-up going; the snake can move and eats the food but I haven't programmed in collision detection or going off the edge yet. The problem is response time. If you run the code below, you'll see that the snake responds to key presses, but not for a couple of - I'll call them frames - after the press. I don't quite understand how the listen() method works; am I using it properly? If not, how should I use it, and if so, how can I fix the delay? I know about Pygame, but a) I can't find an easy to install 64 bit version for python 3.4 (this one http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygameis not easy to install, what the heck is a whl file?) and b) I want to challenge myself anyway. Any help would be appreciated.
所以我一直在用 Python 开发一些游戏(战舰、井字游戏等),本周的项目是 Snake。我有一个基本的设置;蛇可以移动并吃掉食物,但我还没有在碰撞检测中编程或离开边缘。问题是响应时间。如果您运行下面的代码,您会看到蛇对按键做出响应,但不会响应几次——我将它们称为帧——在按下之后。我不太明白 listen() 方法是如何工作的;我是否正确使用它?如果没有,我应该如何使用它,如果是,我该如何解决延迟?我知道 Pygame,但是 a) 我找不到一个易于安装的 64 位版本的 python 3.4(这个http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame不容易安装,whl 文件到底是什么?)和 b)无论如何我都想挑战自己。任何帮助,将不胜感激。
import random
import turtle
import time
class Square:
def __init__(self, x, y):
self.x = x
self.y = y
def drawself(self, turtle):
# draw a black box at its coordinates, leaving a small gap between cubes
turtle.goto(self.x - 9, self.y - 9)
turtle.begin_fill()
for i in range(4):
turtle.forward(18)
turtle.left(90)
turtle.end_fill()
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
self.state = "ON"
def changelocation(self):
# I haven't programmed it to spawn outside the snake's body yet
self.x = random.randint(0, 20)*20 - 200
self.y = random.randint(0, 20)*20 - 200
def drawself(self, turtle):
# similar to the Square drawself, but blinks on and off
if self.state == "ON":
turtle.goto(self.x - 9, self.y - 9)
turtle.begin_fill()
for i in range(4):
turtle.forward(18)
turtle.left(90)
turtle.end_fill()
def changestate(self):
# controls the blinking
self.state = "OFF" if self.state == "ON" else "ON"
class Snake:
def __init__(self):
self.headposition = [20, 0] # keeps track of where it needs to go next
self.body = [Square(-20, 0), Square(0, 0), Square(20, 0)] # body is a list of squares
self.nextX = 1 # tells the snake which way it's going next
self.nextY = 0
self.crashed = False # I'll use this when I get around to collision detection
self.nextposition = [self.headposition[0] + 20*self.nextX,
self.headposition[1] + 20*self.nextY]
# prepares the next location to add to the snake
def moveOneStep(self):
if Square(self.nextposition[0], self.nextposition[1]) not in self.body:
# attempt (unsuccessful) at collision detection
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
# moves the snake head to the next spot, deleting the tail
del self.body[0]
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
# resets the head and nextposition
self.nextposition = [self.headposition[0] + 20*self.nextX,
self.headposition[1] + 20*self.nextY]
else:
self.crashed = True # more unsuccessful collision detection
def moveup(self): # pretty obvious what these do
self.nextX = 0
self.nextY = 1
def moveleft(self):
self.nextX = -1
self.nextY = 0
def moveright(self):
self.nextX = 1
self.nextY = 0
def movedown(self):
self.nextX = 0
self.nextY = -1
def eatFood(self):
# adds the next spot without deleting the tail, extending the snake by 1
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
self.nextposition = [self.headposition[0] + 20*self.nextX,
self.headposition[1] + 20*self.nextY]
def drawself(self, turtle): # draws the whole snake when called
for segment in self.body:
segment.drawself(turtle)
class Game:
def __init__(self):
# game object has a screen, a turtle, a basic snake and a food
self.screen = turtle.Screen()
self.artist = turtle.Turtle()
self.artist.up()
self.artist.hideturtle()
self.snake = Snake()
self.food = Food(100, 0)
self.counter = 0 # this will be used later
self.commandpending = False # as will this
def nextFrame(self):
while True: # now here's where it gets fiddly...
game.screen.listen()
game.screen.onkey(game.snakedown, "Down")
game.screen.onkey(game.snakeup, "Up")
game.screen.onkey(game.snakeleft, "Left")
game.screen.onkey(game.snakeright, "Right")
turtle.tracer(0) # follow it so far?
self.artist.clear()
if self.counter == 5:
# only moves to next frame every 5 loops, this was an attempt to get rid of the turning delay
if (self.snake.nextposition[0], self.snake.nextposition[1]) == (self.food.x, self.food.y):
self.snake.eatFood()
self.food.changelocation()
else:
self.snake.moveOneStep()
self.counter = 0
else:
self.counter += 1
self.food.changestate() # makes the food flash
self.food.drawself(self.artist) # show the food and snake
self.snake.drawself(self.artist)
turtle.update()
self.commandpending = False
time.sleep(0.05)
def snakeup(self):
print("going up") # put this in for debugging purposes
if not self.commandpending:
# should allow only one turn each frame; I don't think it's working
self.snake.moveup()
self.commandpending = True
def snakedown(self):
print("going down")
if not self.commandpending:
self.snake.movedown()
self.commandpending = True
def snakeleft(self):
print("going left")
if not self.commandpending:
self.snake.moveleft()
self.commandpending = True
def snakeright(self):
print("going right")
if not self.commandpending:
self.snake.moveright()
self.commandpending = True
game = Game()
game.nextFrame()
print("game over!")
game.screen.mainloop()
回答by paddyg
It probably is a challenge to make a speedy game this way (but that's not all bad). I think that listen()
should go after the onkey()
s.
以这种方式制作快速游戏可能是一个挑战(但这并不全是坏事)。我认为listen()
应该在onkey()
s 之后。
You should also look at getting rid of all the duplicated code. It might seem easy short term to just copy/paste then alter. But if you have to make major changes (like after asking on a forum) it will be tedious and error prone to alter.
您还应该考虑摆脱所有重复的代码。在短期内复制/粘贴然后更改似乎很容易。但是,如果您必须进行重大更改(例如在论坛上询问之后),则更改将很乏味且容易出错。
PS (EDIT) also your Snake.moveOneStep() method makes a new instance of Square just to check for self collision, this seems extravagant for the sake of elegance. Better to just keep a list of locations that python (ho, ho) can check through. (Quite apart from this probably not working. Try print(Square(1,2) in [Square(1,2)])
)
PS(编辑)你的 Snake.moveOneStep() 方法也创建了一个新的 Square 实例,只是为了检查自碰撞,为了优雅,这似乎是奢侈的。最好只保留 python (ho, ho) 可以检查的位置列表。(除了这可能不起作用。尝试print(Square(1,2) in [Square(1,2)])
)
def check_self_collision(self, x, y):
for s in self.body:
if s.x == x and s.y == y:
return False
return True
def moveOneStep(self):
if self.check_self_collision(self.nextposition[0], self.nextposition[1]):
# attempt (unsuccessful) at collision detection
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
回答by cdlane
Whenever you use while True:
(sans break
) in turtle code, you're defeating the event hander. You should instead use an ontimer()
event to run your code compatibly with the event handler. Below is my rewrite of your code to do this along with some other functional and style tweaks:
每当您在海龟代码中使用while True:
(sans break
) 时,您就击败了事件处理程序。您应该改为使用ontimer()
事件来与事件处理程序兼容地运行您的代码。以下是我对您的代码的重写以及其他一些功能和样式调整:
from turtle import Turtle, Screen
import random
import time
SIZE = 20
class Square:
def __init__(self, x, y):
self.x = x
self.y = y
def drawself(self, turtle):
""" draw a black box at its coordinates, leaving a small gap between cubes """
turtle.goto(self.x - SIZE // 2 - 1, self.y - SIZE // 2 - 1)
turtle.begin_fill()
for _ in range(4):
turtle.forward(SIZE - SIZE // 10)
turtle.left(90)
turtle.end_fill()
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
self.is_blinking = True
def changelocation(self):
# I haven't programmed it to spawn outside the snake's body yet
self.x = random.randint(0, SIZE) * SIZE - 200
self.y = random.randint(0, SIZE) * SIZE - 200
def drawself(self, turtle):
# similar to the Square drawself, but blinks on and off
if self.is_blinking:
turtle.goto(self.x - SIZE // 2 - 1, self.y - SIZE // 2 - 1)
turtle.begin_fill()
for _ in range(4):
turtle.forward(SIZE - SIZE // 10)
turtle.left(90)
turtle.end_fill()
def changestate(self):
# controls the blinking
self.is_blinking = not self.is_blinking
class Snake:
def __init__(self):
self.headposition = [SIZE, 0] # keeps track of where it needs to go next
self.body = [Square(-SIZE, 0), Square(0, 0), Square(SIZE, 0)] # body is a list of squares
self.nextX = 1 # tells the snake which way it's going next
self.nextY = 0
self.crashed = False # I'll use this when I get around to collision detection
self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
# prepares the next location to add to the snake
def moveOneStep(self):
if Square(self.nextposition[0], self.nextposition[1]) not in self.body:
# attempt (unsuccessful) at collision detection
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
# moves the snake head to the next spot, deleting the tail
del self.body[0]
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
# resets the head and nextposition
self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
else:
self.crashed = True # more unsuccessful collision detection
def moveup(self): # pretty obvious what these do
self.nextX, self.nextY = 0, 1
def moveleft(self):
self.nextX, self.nextY = -1, 0
def moveright(self):
self.nextX, self.nextY = 1, 0
def movedown(self):
self.nextX, self.nextY = 0, -1
def eatFood(self):
# adds the next spot without deleting the tail, extending the snake by 1
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
def drawself(self, turtle): # draws the whole snake when called
for segment in self.body:
segment.drawself(turtle)
class Game:
def __init__(self):
# game object has a screen, a turtle, a basic snake and a food
self.screen = Screen()
self.artist = Turtle(visible=False)
self.artist.up()
self.artist.speed("slowest")
self.snake = Snake()
self.food = Food(100, 0)
self.counter = 0 # this will be used later
self.commandpending = False # as will this
self.screen.tracer(0) # follow it so far?
self.screen.listen()
self.screen.onkey(self.snakedown, "Down")
self.screen.onkey(self.snakeup, "Up")
self.screen.onkey(self.snakeleft, "Left")
self.screen.onkey(self.snakeright, "Right")
def nextFrame(self):
self.artist.clear()
if (self.snake.nextposition[0], self.snake.nextposition[1]) == (self.food.x, self.food.y):
self.snake.eatFood()
self.food.changelocation()
else:
self.snake.moveOneStep()
if self.counter == 10:
self.food.changestate() # makes the food flash slowly
self.counter = 0
else:
self.counter += 1
self.food.drawself(self.artist) # show the food and snake
self.snake.drawself(self.artist)
self.screen.update()
self.screen.ontimer(lambda: self.nextFrame(), 100)
def snakeup(self):
if not self.commandpending:
self.commandpending = True
self.snake.moveup()
self.commandpending = False
def snakedown(self):
if not self.commandpending:
self.commandpending = True
self.snake.movedown()
self.commandpending = False
def snakeleft(self):
if not self.commandpending:
self.commandpending = True
self.snake.moveleft()
self.commandpending = False
def snakeright(self):
if not self.commandpending:
self.commandpending = True
self.snake.moveright()
self.commandpending = False
game = Game()
screen = Screen()
screen.ontimer(lambda: game.nextFrame(), 100)
screen.mainloop()
Does this provide the kind of response for which you're looking?
这是否提供了您正在寻找的那种响应?
回答by Tomá? Jelínek
My version:
我的版本:
#coding: utf-8
from Tkinter import *
import random
import time
class Levely:
def __init__(self):
self.urovne=[
[[0, 0, 0, 0, 0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]],
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 1, 0, 0, 0], [1, 1, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 1, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 1, 1, 1, 0, 1], [0, 0, 1, 0, 0, 0, 1, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 1, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 1, 0, 0, 0, 0, 0, 0, 1], [0, 0, 1, 0, 0, 0, 0, 0, 1, 1], [0, 0, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 1], [0, 0, 0, 1, 0, 0, 1, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 1, 1, 0], [0, 0, 1, 1, 1, 0, 0, 0, 0, 0]],
]
self.data=[[400,13],[400,10],[400,13],[400,13],[400,13],[400,13]]
print "Choose from", len(self.urovne), "levels"
self.vyber=input("Level: ")
self.vyber-=1
h=Had(self)
class Had:
def __init__(self,Levely):
self.l=Levely
self.level=self.l.urovne[self.l.vyber]
self.mrizka=len(self.level[0])
self.velikost=self.l.data[self.l.vyber][0]
self.vtelo=100
self.r=self.l.data[self.l.vyber][1]
self.x=0
self.y=0
self.u=0
self.k=self.velikost
self.c=(self.velikost/self.mrizka)
self.poprve=0
self.neco=[[0,0],0,0,0]
self.ukonceni=None
self.aakce1=None
self.aakce2=None
self.aakce3=None
self.aakce4=None
self.s=[0,0,0,0]
self.j=[]
self.konec=0
self.score=0
self.pocet_zelenych=0
self.okno=Tk()
self.platno=Canvas(self.okno,width=self.velikost,height=self.velikost,bg="white")
self.platno.pack()
self.tl=Button(self.okno, text="Restart", command=self.start)
self.tl.pack(fill=BOTH)
self.start()
self.okno.bind("<Key-d>", self.akce1)
self.okno.bind("<Key-w>", self.akce2)
self.okno.bind("<Key-s>", self.akce3)
self.okno.bind("<Key-a>", self.akce4)
self.okno.bind("<Key-r>", self.start1)
def akce1(self, klik):
self.akce11()
def akce2(self, klik):
self.akce21()
def akce3(self, klik):
self.akce31()
def akce4(self, klik):
self.akce41()
def start1(self, klik):
self.start()
def akce11(self):
if int(self.s[1])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce11)
if int(self.s[1])%self.c==0:
self.x=self.c
self.y=0
self.u=0
if self.poprve==1:
self.okno.after_cancel(self.aakce1)
self.stop()
self.pohyb()
def akce21(self):
if int(self.s[0])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce21)
if int(self.s[0])%self.c==0:
self.x=0
self.y=-self.c
self.u=0
if self.poprve==1:
self.okno.after_cancel(self.aakce2)
self.stop()
self.pohyb()
def akce31(self):
if int(self.s[0])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce31)
if int(self.s[0])%self.c==0:
self.x=0
self.y=self.c
self.u=1
if self.poprve==1:
self.okno.after_cancel(self.aakce3)
self.stop()
self.pohyb()
def akce41(self):
if int(self.s[1])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce41)
if int(self.s[1])%self.c==0:
self.x=-self.c
self.y=0
self.u=0
if self.poprve==1:
self.okno.after_cancel(self.aakce4)
self.stop()
self.pohyb()
def pohyb(self):
self.smrt()
if self.konec==1:
return None
self.test()
s=self.platno.coords(self.hlava)
self.s=self.platno.coords(self.hlava)
self.platno.delete(ALL)
self.hlava=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="green4", outline="white")
self.jablko=self.platno.create_rectangle(self.j[0],self.j[1],self.j[2],self.j[3], fill="red", outline="red")
for x in range(self.mrizka):
for y in range(self.mrizka):
if self.level[x][y]==0:
continue
if self.level[x][y]==1:
#KURVVAAAAA x,y,x,y
self.block=self.platno.create_rectangle(y*self.c,(x*self.c),(y*self.c)+self.c,(x*self.c)+self.c, fill="black")
self.test()
s=self.platno.coords(self.hlava)
self.poloha.append(s)
self.delka=len(self.poloha)
if s[self.u]<=self.k:
self.dx=self.x
self.dy=self.y
self.platno.move(self.hlava,self.dx/10,self.dy/10)
s=self.platno.coords(self.hlava)
self.nahrada=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="green4", outline="green4")
if s[self.u]>=self.k:
self.dx=0
self.dy=0
bla="Restart-Score:", int(self.score)
self.tl.config(text=bla)
for a in range(self.delka):
if 1==1:
self.ocas=self.platno.create_rectangle(self.poloha[a][0],self.poloha[a][1],self.poloha[a][2],self.poloha[a][3], fill="green2", outline="green2")
self.poloha_zeleny=self.platno.coords(self.ocas)
self.zeleny.append(self.poloha_zeleny)
self.pocet_zelenych=len(self.zeleny)
if self.pocet_zelenych>=self.delka:
del self.zeleny[0]
if self.delka>=self.vtelo:
self.neco=self.poloha[0]
del self.poloha[0]
self.s=self.platno.coords(self.hlava)
self.nahrada=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="green4", outline="green4")
self.ukonceni=self.okno.after(self.r,self.pohyb)
def smrt(self):
s=self.platno.coords(self.hlava)
bla="Restart-Score:", int(self.score)
if self.level[int(s[1]/self.c)][int(s[0]/self.c)]==1:
self.platno.delete(self.hlava)
self.tl.config(text=bla)
self.konec=1
self.smrtak=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="brown", outline="brown")
for b in range(len(self.zeleny)):
if s==self.zeleny[(b-1)]:
self.platno.delete(self.hlava)
self.tl.config(text=bla)
self.konec=1
self.smrtak=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="brown", outline="brown")
def stop(self):
if self.poprve==1:
self.okno.after_cancel(self.ukonceni)
self.poprve=1
def start(self):
self.vtelo=60
self.platno.delete("all")
self.tl.config(text="Restart")
self.poloha=[]
self.zeleny=[]
self.konec=0
self.pocet_zelenych=0
self.score=0
self.poprve=0
self.dx=0
self.dy=0
if self.aakce1!=None:
self.okno.after_cancel(self.aakce1)
self.aakce1=None
if self.aakce2!=None:
self.okno.after_cancel(self.aakce2)
self.aakce2=None
if self.aakce3!=None:
self.okno.after_cancel(self.aakce3)
self.aakce3=None
if self.aakce4!=None:
self.okno.after_cancel(self.aakce4)
self.aakce4=None
for x in range(self.mrizka):
for y in range(self.mrizka):
if self.level[x][y]==0:
continue
if self.level[x][y]==1:
#KURVVAAAAA x,y,x,y
self.block=self.platno.create_rectangle(y*self.c,(x*self.c),(y*self.c)+self.c,(x*self.c)+self.c, fill="black")
self.hlava=self.platno.create_rectangle(0,0,self.c,self.c, fill="green4", outline="green4")
self.generace()
s=self.platno.coords(self.hlava)
self.dx=self.c
self.dy=self.c
def generace(self):
self.nx=random.randint(0,self.mrizka-1)
self.ny=random.randint(0,self.mrizka-1)
for x in self.zeleny:
if int(x[0]/self.c)==self.nx and int(x[1]/self.c)==self.ny:
self.generace()
if self.level[self.ny][self.nx]==1:
self.generace()
if self.level[self.ny][self.nx]!=1:
self.jablko=self.platno.create_rectangle(self.nx*self.c,self.ny*self.c,self.nx*self.c+self.c,self.ny*self.c+self.c, fill="red", outline="red")
def test(self):
s=self.platno.coords(self.hlava)
self.j=self.platno.coords(self.jablko)
if s==self.j:
self.vtelo+=5
self.score+=0.5
self.generace()
def mezery(self):
for x in range(30):
print ""
levliky=Levely()
mainloop()