CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
jackfrued

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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