snake.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. from abc import ABCMeta, abstractmethod
  2. from enum import Enum, unique
  3. from random import randrange
  4. from threading import Thread
  5. import pygame
  6. class Color(object):
  7. """颜色"""
  8. GRAY = (242, 242, 242)
  9. BLACK = (0, 0, 0)
  10. GREEN = (0, 255, 0)
  11. PINK = (255, 20, 147)
  12. @unique
  13. class Direction(Enum):
  14. """方向"""
  15. UP = 0
  16. RIGHT = 1
  17. DOWN = 2
  18. LEFT = 3
  19. class GameObject(object, metaclass=ABCMeta):
  20. """游戏中的对象"""
  21. def __init__(self, x=0, y=0, color=Color.BLACK):
  22. """
  23. 初始化方法
  24. :param x: 横坐标
  25. :param y: 纵坐标
  26. :param color: 颜色
  27. """
  28. self._x = x
  29. self._y = y
  30. self._color = color
  31. @property
  32. def x(self):
  33. return self._x
  34. @property
  35. def y(self):
  36. return self._y
  37. @abstractmethod
  38. def draw(self, screen):
  39. """
  40. 绘制
  41. :param screen: 屏幕
  42. """
  43. pass
  44. class Wall(GameObject):
  45. """围墙"""
  46. def __init__(self, x, y, width, height, color=Color.BLACK):
  47. """
  48. 初始化方法
  49. :param x: 横坐标
  50. :param y: 纵坐标
  51. :param width: 宽度
  52. :param height: 高度
  53. :param color: 颜色
  54. """
  55. super().__init__(x, y, color)
  56. self._width = width
  57. self._height = height
  58. @property
  59. def width(self):
  60. return self._width
  61. @property
  62. def height(self):
  63. return self._height
  64. def draw(self, screen):
  65. pygame.draw.rect(screen, self._color,
  66. (self._x, self._y, self._width, self._height), 4)
  67. class Food(GameObject):
  68. """食物"""
  69. def __init__(self, x, y, size, color=Color.PINK):
  70. """
  71. 初始化方法
  72. :param x: 横坐标
  73. :param y: 纵坐标
  74. :param size: 大小
  75. :param color: 颜色
  76. """
  77. super().__init__(x, y, color)
  78. self._size = size
  79. self._hidden = False
  80. def draw(self, screen):
  81. if not self._hidden:
  82. pygame.draw.circle(screen, self._color,
  83. (self._x + self._size // 2, self._y + self._size // 2),
  84. self._size // 2, 0)
  85. self._hidden = not self._hidden
  86. class SnakeNode(GameObject):
  87. """蛇身上的节点"""
  88. def __init__(self, x, y, size, color=Color.GREEN):
  89. """
  90. 初始化方法
  91. :param x: 横坐标
  92. :param y: 纵坐标
  93. :param size: 大小
  94. :param color: 颜色
  95. """
  96. super().__init__(x, y, color)
  97. self._size = size
  98. @property
  99. def size(self):
  100. return self._size
  101. def draw(self, screen):
  102. pygame.draw.rect(screen, self._color,
  103. (self._x, self._y, self._size, self._size), 0)
  104. pygame.draw.rect(screen, Color.BLACK,
  105. (self._x, self._y, self._size, self._size), 1)
  106. class Snake(GameObject):
  107. """蛇"""
  108. def __init__(self, x, y, size=20, length=5):
  109. """
  110. 初始化方法
  111. :param x: 横坐标
  112. :param y: 纵坐标
  113. :param size: 大小
  114. :param length: 初始长度
  115. """
  116. super().__init__()
  117. self._dir = Direction.LEFT
  118. self._nodes = []
  119. self._alive = True
  120. self._new_dir = None
  121. for index in range(length):
  122. node = SnakeNode(x + index * size, y, size)
  123. self._nodes.append(node)
  124. @property
  125. def dir(self):
  126. return self._dir
  127. @property
  128. def alive(self):
  129. return self._alive
  130. @property
  131. def head(self):
  132. return self._nodes[0]
  133. def change_dir(self, new_dir):
  134. """
  135. 改变方向
  136. :param new_dir: 新方向
  137. """
  138. if new_dir != self._dir and \
  139. (self._dir.value + new_dir.value) % 2 != 0:
  140. self._new_dir = new_dir
  141. def move(self):
  142. """移动"""
  143. if self._new_dir:
  144. self._dir, self._new_dir = self._new_dir, None
  145. snake_dir = self._dir
  146. x, y, size = self.head.x, self.head.y, self.head.size
  147. if snake_dir == Direction.UP:
  148. y -= size
  149. elif snake_dir == Direction.RIGHT:
  150. x += size
  151. elif snake_dir == Direction.DOWN:
  152. y += size
  153. else:
  154. x -= size
  155. new_head = SnakeNode(x, y, size)
  156. self._nodes.insert(0, new_head)
  157. self._nodes.pop()
  158. def collide(self, wall):
  159. """
  160. 撞墙
  161. :param wall: 围墙
  162. """
  163. head = self.head
  164. if head.x < wall.x or head.x + head.size > wall.x + wall.width \
  165. or head.y < wall.y or head.y + head.size > wall.y + wall.height:
  166. self._alive = False
  167. def eat_food(self, food):
  168. """
  169. 吃食物
  170. :param food: 食物
  171. :return: 吃到食物返回True否则返回False
  172. """
  173. if self.head.x == food.x and self.head.y == food.y:
  174. tail = self._nodes[-1]
  175. self._nodes.append(tail)
  176. return True
  177. return False
  178. def eat_self(self):
  179. """咬自己"""
  180. for index in range(4, len(self._nodes)):
  181. node = self._nodes[index]
  182. if node.x == self.head.x and node.y == self.head.y:
  183. self._alive = False
  184. def draw(self, screen):
  185. for node in self._nodes:
  186. node.draw(screen)
  187. def main():
  188. def refresh():
  189. """刷新游戏窗口"""
  190. screen.fill(Color.GRAY)
  191. wall.draw(screen)
  192. food.draw(screen)
  193. snake.draw(screen)
  194. pygame.display.flip()
  195. def handle_key_event(key_event):
  196. """处理按键事件"""
  197. key = key_event.key
  198. if key == pygame.K_F2:
  199. reset_game()
  200. elif key in (pygame.K_a, pygame.K_w, pygame.K_d, pygame.K_s):
  201. if snake.alive:
  202. if key == pygame.K_w:
  203. new_dir = Direction.UP
  204. elif key == pygame.K_d:
  205. new_dir = Direction.RIGHT
  206. elif key == pygame.K_s:
  207. new_dir = Direction.DOWN
  208. else:
  209. new_dir = Direction.LEFT
  210. snake.change_dir(new_dir)
  211. def create_food():
  212. """创建食物"""
  213. unit_size = snake.head.size
  214. max_row = wall.height // unit_size
  215. max_col = wall.width // unit_size
  216. row = randrange(0, max_row)
  217. col = randrange(0, max_col)
  218. return Food(wall.x + unit_size * col, wall.y + unit_size * row, unit_size)
  219. def reset_game():
  220. """重置游戏"""
  221. nonlocal food, snake
  222. food = create_food()
  223. snake = Snake(250, 290)
  224. def background_task():
  225. nonlocal running, food
  226. while running:
  227. if snake.alive:
  228. refresh()
  229. clock.tick(10)
  230. if snake.alive:
  231. snake.move()
  232. snake.collide(wall)
  233. if snake.eat_food(food):
  234. food = create_food()
  235. snake.eat_self()
  236. """
  237. class BackgroundTask(Thread):
  238. def run(self):
  239. nonlocal running, food
  240. while running:
  241. if snake.alive:
  242. refresh()
  243. clock.tick(10)
  244. if snake.alive:
  245. snake.move()
  246. snake.collide(wall)
  247. if snake.eat_food(food):
  248. food = create_food()
  249. snake.eat_self()
  250. """
  251. wall = Wall(10, 10, 600, 600)
  252. snake = Snake(250, 290)
  253. food = create_food()
  254. pygame.init()
  255. screen = pygame.display.set_mode((620, 620))
  256. pygame.display.set_caption('贪吃蛇')
  257. # 创建控制游戏每秒帧数的时钟
  258. clock = pygame.time.Clock()
  259. running = True
  260. # 启动后台线程负责刷新窗口和让蛇移动
  261. # BackgroundTask().start()
  262. Thread(target=background_task).start()
  263. # 处理事件的消息循环
  264. while running:
  265. for event in pygame.event.get():
  266. if event.type == pygame.QUIT:
  267. running = False
  268. elif event.type == pygame.KEYDOWN:
  269. handle_key_event(event)
  270. pygame.quit()
  271. if __name__ == '__main__':
  272. main()