使用Turtle图形的Python蛇游戏

时间:2015-05-05 10:14:39

标签: python turtle-graphics

所以我一直在用Python(战列舰,tic-tac-toe等)制作一些游戏,本周的项目是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()

3 个答案:

答案 0 :(得分:3)

每当你在乌龟代码中使用while True:(没有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()

这是否提供了您正在寻找的响应类型?

答案 1 :(得分:0)

以这种方式制作快速游戏可能是一个挑战(但这并非全是坏事)。我认为listen()应该在onkey()之后。

您还应该考虑删除所有重复的代码。短期内复制/粘贴然后改变似乎很容易。但是如果你必须做出重大改变(比如在论坛上提问之后),那将会很乏味且容易出错。

PS(EDIT)你的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])) 

答案 2 :(得分:-2)

我的版本:

#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()