Snake game in Python using Turtle graphics

非 Y 不嫁゛ 提交于 2019-12-06 12:03:57

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:

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?

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.

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)]))

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])) 

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