使用 Turtle 图形的 Python 蛇游戏 [英] Snake game in Python using Turtle graphics

查看:39
本文介绍了使用 Turtle 图形的 Python 蛇游戏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我一直在用 Python 开发一些游戏(战舰、井字游戏等),本周的项目是 Snake.我有一个基本的设置;蛇可以移动并吃掉食物,但我还没有在碰撞检测中编程或离开边缘.问题是响应时间.如果您运行下面的代码,您会看到蛇对按键做出响应,但不会响应几次——我将它们称为帧——在按下之后.我不太明白 listen() 方法是如何工作的;我是否正确使用它?如果没有,我应该如何使用它,如果是,我该如何解决延迟?我知道 Pygame,但是 a) 我找不到一个易于安装的 64 位版本的 python 3.4(这个 http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame 不容易安装,whl 文件到底是什么?)和 b)我想要无论如何都要挑战自己.任何帮助将不胜感激.

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/#pygame is not easy to install, what the heck is a whl file?) and b) I want to challenge myself anyway. Any help would be appreciated.

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

推荐答案

每当你在海龟代码中使用 while True: (sans break)事件处理程序.您应该改为使用 ontimer() 事件来与事件处理程序兼容地运行您的代码.下面是我重写您的代码以执行此操作以及其他一些功能和样式调整:

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?

这篇关于使用 Turtle 图形的 Python 蛇游戏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆