原文: http://zetcode.com/tkinter/snake/

在 Tkinter 教程的这一部分中,我们创建一个贪食蛇游戏克隆。

贪食蛇是较旧的经典视频游戏。 它最初是在 70 年代后期创建的。 后来它被带到 PC 上。 在这个游戏中,玩家控制蛇。 目的是尽可能多地吃苹果。 蛇每次吃一个苹果,它的身体就会长大。 蛇必须避开墙壁和自己的身体。

开发

蛇的每个关节的大小为 10 像素。 蛇由光标键控制。 最初,蛇具有三个关节。 游戏立即开始。 游戏结束后,我们将在窗口中心显示分数上方的游戏结束消息。

我们使用Canvas小部件来创建游戏。 游戏中的对象是图像。 我们使用画布方法创建图像项。 我们使用画布方法使用标签在画布上查找项目并进行碰撞检测。

snake.py

  1. #!/usr/bin/env python3
  2. """
  3. ZetCode Tkinter tutorial
  4. This is a simple Snake game
  5. clone.
  6. Author: Jan Bodnar
  7. Website: zetcode.com
  8. Last edited: April 2019
  9. """
  10. import sys
  11. import random
  12. from PIL import Image, ImageTk
  13. from tkinter import Tk, Frame, Canvas, ALL, NW
  14. class Cons:
  15. BOARD_WIDTH = 300
  16. BOARD_HEIGHT = 300
  17. DELAY = 100
  18. DOT_SIZE = 10
  19. MAX_RAND_POS = 27
  20. class Board(Canvas):
  21. def __init__(self):
  22. super().__init__(width=Cons.BOARD_WIDTH, height=Cons.BOARD_HEIGHT,
  23. background="black", highlightthickness=0)
  24. self.initGame()
  25. self.pack()
  26. def initGame(self):
  27. '''initializes game'''
  28. self.inGame = True
  29. self.dots = 3
  30. self.score = 0
  31. # variables used to move snake object
  32. self.moveX = Cons.DOT_SIZE
  33. self.moveY = 0
  34. # starting apple coordinates
  35. self.appleX = 100
  36. self.appleY = 190
  37. self.loadImages()
  38. self.createObjects()
  39. self.locateApple()
  40. self.bind_all("<Key>", self.onKeyPressed)
  41. self.after(Cons.DELAY, self.onTimer)
  42. def loadImages(self):
  43. '''loads images from the disk'''
  44. try:
  45. self.idot = Image.open("dot.png")
  46. self.dot = ImageTk.PhotoImage(self.idot)
  47. self.ihead = Image.open("head.png")
  48. self.head = ImageTk.PhotoImage(self.ihead)
  49. self.iapple = Image.open("apple.png")
  50. self.apple = ImageTk.PhotoImage(self.iapple)
  51. except IOError as e:
  52. print(e)
  53. sys.exit(1)
  54. def createObjects(self):
  55. '''creates objects on Canvas'''
  56. self.create_text(30, 10, text="Score: {0}".format(self.score),
  57. tag="score", fill="white")
  58. self.create_image(self.appleX, self.appleY, image=self.apple,
  59. anchor=NW, tag="apple")
  60. self.create_image(50, 50, image=self.head, anchor=NW, tag="head")
  61. self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
  62. self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")
  63. def checkAppleCollision(self):
  64. '''checks if the head of snake collides with apple'''
  65. apple = self.find_withtag("apple")
  66. head = self.find_withtag("head")
  67. x1, y1, x2, y2 = self.bbox(head)
  68. overlap = self.find_overlapping(x1, y1, x2, y2)
  69. for ovr in overlap:
  70. if apple[0] == ovr:
  71. self.score += 1
  72. x, y = self.coords(apple)
  73. self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
  74. self.locateApple()
  75. def moveSnake(self):
  76. '''moves the Snake object'''
  77. dots = self.find_withtag("dot")
  78. head = self.find_withtag("head")
  79. items = dots + head
  80. z = 0
  81. while z < len(items)-1:
  82. c1 = self.coords(items[z])
  83. c2 = self.coords(items[z+1])
  84. self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
  85. z += 1
  86. self.move(head, self.moveX, self.moveY)
  87. def checkCollisions(self):
  88. '''checks for collisions'''
  89. dots = self.find_withtag("dot")
  90. head = self.find_withtag("head")
  91. x1, y1, x2, y2 = self.bbox(head)
  92. overlap = self.find_overlapping(x1, y1, x2, y2)
  93. for dot in dots:
  94. for over in overlap:
  95. if over == dot:
  96. self.inGame = False
  97. if x1 < 0:
  98. self.inGame = False
  99. if x1 > Cons.BOARD_WIDTH - Cons.DOT_SIZE:
  100. self.inGame = False
  101. if y1 < 0:
  102. self.inGame = False
  103. if y1 > Cons.BOARD_HEIGHT - Cons.DOT_SIZE:
  104. self.inGame = False
  105. def locateApple(self):
  106. '''places the apple object on Canvas'''
  107. apple = self.find_withtag("apple")
  108. self.delete(apple[0])
  109. r = random.randint(0, Cons.MAX_RAND_POS)
  110. self.appleX = r * Cons.DOT_SIZE
  111. r = random.randint(0, Cons.MAX_RAND_POS)
  112. self.appleY = r * Cons.DOT_SIZE
  113. self.create_image(self.appleX, self.appleY, anchor=NW,
  114. image=self.apple, tag="apple")
  115. def onKeyPressed(self, e):
  116. '''controls direction variables with cursor keys'''
  117. key = e.keysym
  118. LEFT_CURSOR_KEY = "Left"
  119. if key == LEFT_CURSOR_KEY and self.moveX <= 0:
  120. self.moveX = -Cons.DOT_SIZE
  121. self.moveY = 0
  122. RIGHT_CURSOR_KEY = "Right"
  123. if key == RIGHT_CURSOR_KEY and self.moveX >= 0:
  124. self.moveX = Cons.DOT_SIZE
  125. self.moveY = 0
  126. RIGHT_CURSOR_KEY = "Up"
  127. if key == RIGHT_CURSOR_KEY and self.moveY <= 0:
  128. self.moveX = 0
  129. self.moveY = -Cons.DOT_SIZE
  130. DOWN_CURSOR_KEY = "Down"
  131. if key == DOWN_CURSOR_KEY and self.moveY >= 0:
  132. self.moveX = 0
  133. self.moveY = Cons.DOT_SIZE
  134. def onTimer(self):
  135. '''creates a game cycle each timer event'''
  136. self.drawScore()
  137. self.checkCollisions()
  138. if self.inGame:
  139. self.checkAppleCollision()
  140. self.moveSnake()
  141. self.after(Cons.DELAY, self.onTimer)
  142. else:
  143. self.gameOver()
  144. def drawScore(self):
  145. '''draws score'''
  146. score = self.find_withtag("score")
  147. self.itemconfigure(score, text="Score: {0}".format(self.score))
  148. def gameOver(self):
  149. '''deletes all objects and draws game over message'''
  150. self.delete(ALL)
  151. self.create_text(self.winfo_width() /2, self.winfo_height()/2,
  152. text="Game Over with score {0}".format(self.score), fill="white")
  153. class Snake(Frame):
  154. def __init__(self):
  155. super().__init__()
  156. self.master.title('Snake')
  157. self.board = Board()
  158. self.pack()
  159. def main():
  160. root = Tk()
  161. nib = Snake()
  162. root.mainloop()
  163. if __name__ == '__main__':
  164. main()

首先,我们将定义一些在游戏中使用的常量。

  1. class Cons:
  2. BOARD_WIDTH = 300
  3. BOARD_HEIGHT = 300
  4. DELAY = 100
  5. DOT_SIZE = 10
  6. MAX_RAND_POS = 27

BOARD_WIDTHBOARD_HEIGHT常数确定电路板的大小。 DELAY常数确定游戏的速度。 DOT_SIZE是苹果的大小和蛇的点。 MAX_RAND_POS常数用于计算苹果的随机位置。

initGame()方法初始化变量,加载图像并启动超时功能。

  1. self.createObjects()
  2. self.locateApple()

createObjects()方法在画布上创建项目。 locateApple()在画布上随机放置一个苹果。

  1. self.bind_all("<Key>", self.onKeyPressed)

我们将键盘事件绑定到onKeyPressed()方法。 游戏由键盘光标键控制。

  1. try:
  2. self.idot = Image.open("dot.png")
  3. self.dot = ImageTk.PhotoImage(self.idot)
  4. self.ihead = Image.open("head.png")
  5. self.head = ImageTk.PhotoImage(self.ihead)
  6. self.iapple = Image.open("apple.png")
  7. self.apple = ImageTk.PhotoImage(self.iapple)
  8. except IOError as e:
  9. print(e)
  10. sys.exit(1)

在这些行中,我们加载图像。 贪食蛇游戏中包含三个图像:头部,圆点和苹果。

  1. def createObjects(self):
  2. '''creates objects on Canvas'''
  3. self.create_text(30, 10, text="Score: {0}".format(self.score),
  4. tag="score", fill="white")
  5. self.create_image(self.appleX, self.appleY, image=self.apple,
  6. anchor=NW, tag="apple")
  7. self.create_image(50, 50, image=self.head, anchor=NW, tag="head")
  8. self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
  9. self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")

createObjects()方法中,我们在画布上创建游戏对象。 这些是帆布项目。 它们被赋予初始的 x 和 y 坐标。 image参数提供要显示的图像。 anchor参数设置为NW; 这样,画布项目的坐标就是项目的左上角。 如果我们希望能够在根窗口的边框旁边显示图像,这很重要。 尝试删除锚点,看看会发生什么。 tag参数用于识别画布上的项目。 一个标签可用于多个画布项目。

checkAppleCollision()方法检查蛇是否击中了苹果对象。 如果是这样,我们会增加分数,添加另一个蛇形关节,并称为locateApple()

  1. apple = self.find_withtag("apple")
  2. head = self.find_withtag("head")

find_withtag()方法使用其标签在画布上找到一个项目。 我们需要两个项目:蛇的头和苹果。 请注意,即使只有一个带有给定标签的项目,该方法也会返回一个元组。 苹果产品就是这种情况。 然后,可以通过以下方式访问苹果项目:apple[0]

  1. x1, y1, x2, y2 = self.bbox(head)
  2. overlap = self.find_overlapping(x1, y1, x2, y2)

bbox()方法返回项目的边界框点。 find_overlapping()方法查找给定坐标的冲突项。

  1. for ovr in overlap:
  2. if apple[0] == ovr:
  3. x, y = self.coords(apple)
  4. self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
  5. self.locateApple()

如果苹果与头部碰撞,我们将在苹果对象的坐标处创建一个新的点项目。 我们调用locateApple()方法,该方法从画布上删除旧的苹果项目,然后创建并随机放置一个新的项目。

moveSnake()方法中,我们有游戏的关键算法。 要了解它,请看一下蛇是如何运动的。 我们控制蛇的头。 我们可以使用光标键更改其方向。 其余关节在链上向上移动一个位置。 第二关节移动到第一个关节的位置,第三关节移动到第二个关节的位置,依此类推。

  1. z = 0
  2. while z < len(items)-1:
  3. c1 = self.coords(items[z])
  4. c2 = self.coords(items[z+1])
  5. self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
  6. z += 1

该代码将关节向上移动。

  1. self.move(head, self.moveX, self.moveY)

我们使用move()方法移动磁头。 按下光标键时,将设置self.moveXself.moveY变量。

checkCollisions()方法中,我们确定蛇是否击中了自己或撞墙之一。

  1. x1, y1, x2, y2 = self.bbox(head)
  2. overlap = self.find_overlapping(x1, y1, x2, y2)
  3. for dot in dots:
  4. for over in overlap:
  5. if over == dot:
  6. self.inGame = False

如果蛇用头撞到关节之一,我们就结束游戏。

  1. if y1 > Cons.BOARD_HEIGHT - Cons.DOT_SIZE:
  2. self.inGame = False

如果蛇击中Board的底部,我们就结束游戏。

locateApple()方法在板上随机找到一个新苹果,然后删除旧的苹果。

  1. apple = self.find_withtag("apple")
  2. self.delete(apple[0])

在这里,我们找到并删除了被蛇吃掉的苹果。

  1. r = random.randint(0, Cons.MAX_RAND_POS)

我们得到一个从 0 到MAX_RAND_POS-1的随机数。

  1. self.appleX = r * Cons.DOT_SIZE
  2. ...
  3. self.appleY = r * Cons.DOT_SIZE

这些行设置了apple对象的 x 和 y 坐标。

onKeyPressed()方法中,我们在游戏过程中对按下的键做出反应。

  1. LEFT_CURSOR_KEY = "Left"
  2. if key == LEFT_CURSOR_KEY and self.moveX <= 0:
  3. self.moveX = -Cons.DOT_SIZE
  4. self.moveY = 0

如果我们按左光标键,则相应地设置self.moveXself.moveY变量。 在moveSnake()方法中使用这些变量来更改蛇对象的坐标。 还要注意,当蛇向右行驶时,我们不能立即向左转。

  1. def onTimer(self):
  2. '''creates a game cycle each timer event '''
  3. self.drawScore()
  4. self.checkCollisions()
  5. if self.inGame:
  6. self.checkAppleCollision()
  7. self.moveSnake()
  8. self.after(Cons.DELAY, self.onTimer)
  9. else:
  10. self.gameOver()

DELAY ms,就会调用onTimer()方法。 如果我们参与了游戏,我们将调用三种构建游戏逻辑的方法。 否则,游戏结束。 计时器基于after()方法,该方法仅在DELAY ms 之后调用一次方法。 要重复调用计时器,我们递归调用onTimer()方法。

  1. def drawScore(self):
  2. '''draws score'''
  3. score = self.find_withtag("score")
  4. self.itemconfigure(score, text="Score: {0}".format(self.score))

drawScore()方法在板上画分数。

  1. def gameOver(self):
  2. '''deletes all objects and draws game over message'''
  3. self.delete(ALL)
  4. self.create_text(self.winfo_width() /2, self.winfo_height()/2,
  5. text="Game Over with score {0}".format(self.score), fill="white")

如果游戏结束,我们将删除画布上的所有项目。 然后,我们将游戏的最终比分绘制在屏幕中央。

Tkinter 中的贪食蛇 - 图1

图:贪食蛇

这是用 Tkinter 创建的贪食蛇电脑游戏。