Joc de șarpe în Python folosind grafica Turtle (Programare, Python, Grafică Broască Țestoasă)

Isaac Middlemiss a intrebat.

Deci, am lucrat la câteva jocuri în Python (battleships, tic-tac-toe etc.) și proiectul de săptămâna aceasta este Snake. Am o configurație de bază; șarpele se poate mișca și mănâncă mâncarea, dar nu am programat încă detectarea coliziunilor sau ieșirea de pe margine. Problema este timpul de răspuns. Dacă rulați codul de mai jos, veți vedea că șarpele răspunde la apăsarea tastelor, dar nu pentru câteva – le voi numi cadre – după apăsare. Nu prea înțeleg cum funcționează metoda listen(); o folosesc cumva corect? Dacă nu, cum ar trebui să o folosesc și, dacă da, cum aș putea remedia întârzierea? Știu despre Pygame, dar a) nu găsesc o versiune de 64 de biți ușor de instalat pentru python 3.4 (aceasta http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame nu este ușor de instalat, ce naiba este un fișier whl?) și b) vreau să mă provoc oricum. orice ajutor ar fi apreciat.

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 răspunsuri
cdlane

Ori de câte ori folosiți while True: (fără break) în codul de broască țestoasă, înfrângi event hander. În schimb, ar trebui să utilizați un ontimer() eveniment pentru a vă executa codul în mod compatibil cu gestionarul de evenimente. Mai jos este rescrierea mea a codului dvs. pentru a face acest lucru, împreună cu alte modificări funcționale și de stil:

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

Oferă acest lucru tipul de răspuns pe care îl căutați?

Comentarii

  • @donaldleckie, sunt de acord că un __eq__ este utilă, dar nu puteți implementa comparațiile x și y cu == deoarece broaștele țestoase se târăsc pe un plan în virgulă mobilă și este ușor să ajungi să compari 2.0 == 2.001. În schimb, turtle.distance() trebuie să fie folosit cu o valoare mică „suficient de apropiată”. –  > Por cdlane.
paddyg

Probabil că este o provocare pentru a face un joc rapid în acest fel (dar asta nu e chiar atât de rău). Cred că listen() ar trebui să meargă după onkey()s.

De asemenea, ar trebui să te uiți să scapi de tot codul duplicat. S-ar putea părea ușor pe termen scurt să faci doar copy/paste apoi să modifici. Dar dacă trebuie să faceți schimbări majore (cum ar fi după ce ați întrebat pe un forum), va fi anevoios și predispus la erori.

PS (EDIT) de asemenea, metoda ta Snake.moveOneStep() face o nouă instanță de Square doar pentru a verifica autocoliziunea, acest lucru pare extravagant de dragul eleganței. Mai bine să păstrați doar o listă de locații pe care python (ho, ho, ho) le poate verifica prin ea. (În afară de faptul că acest lucru probabil nu funcționează. Încercați 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])) 

Tomáš Jelínek

Versiunea mea:

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

Comentarii

  • Vă rugăm să furnizați o descriere mai detaliată a ceea ce ați schimbat și de ce ați schimbat. Doar postarea unui zid de cod nu este nici utilă, nici plăcută la citit sau ușor de înțeles. –  > Por Fabian Schöner.