蛇每吃掉一个身体块,蛇身就增加一个长度。为了统一计算,界面的尺寸和游戏元素的位置都是身体块长度的倍数
1. 上下左右方向键(或者ASDW键)控制蛇的移动方向
2. 空格键暂停和继续
图片文件,复制到项目的asset\img目录下
import sys
import pygame
from pygame import Rect, font
import random
# control panel contains the controllers and score
ControlPanelColor = (100, 100, 100)
# game panel is the main area for gaming
GamePanelColor = (0, 0, 0)
SnakeSize = 30
MaxWidthBlock = 15
MaxHeightBlock = 10
ControlPanelHeightBlock = 2
SnakeStartX = MaxWidthBlock // 2
SnakeStartY = MaxHeightBlock // 2
ControlPanelHeight = ControlPanelHeightBlock * SnakeSize
GamePanelWidth = MaxWidthBlock * SnakeSize
GamePanelHeight = MaxHeightBlock * SnakeSize
ControlPanelRect = Rect((0, 0), (GamePanelWidth, ControlPanelHeight))
GamePanelRect = Rect((0, ControlPanelHeight), (GamePanelWidth, GamePanelHeight - ControlPanelHeight))
Tick = 20
Tick_Snake_Move = 10
# two buttons to increase and decrease the game speed
minus_btn_rect = None
plus_btn_rect = None
# score
score_value = 0
# the SnakeBody
class SnakeBody:
def __init__(self, x, y, direction, ishead=False, istail=False):
'''
身体块,蛇身是由多个身体块组成,头和尾也是图案不同的身体块
:param x: 身体块坐标x
:param y: 身体块坐标y
:param direction: 身体块显示的方向
:param ishead: 是否头身体块
:param istail:是否尾身体块
'''
self.__x = x
self.__y = y
self.__ishead = ishead
self.__istail = istail
self.__direction = direction
name = None
if self.__ishead:
name = "head.png"
elif self.__istail:
name = "tail.png"
else:
name = "body.png"
angle = 0
match direction:
case pygame.K_UP:
angle = 0
case pygame.K_DOWN:
angle = 180
case pygame.K_LEFT:
angle = 90
case pygame.K_RIGHT:
angle = -90
img = pygame.image.load(f"asset/img/{name}")
img = pygame.transform.rotate(img, angle)
self.image = pygame.transform.scale(img, (SnakeSize, SnakeSize))
def get_rect(self):
return Rect((self.__x, self.__y), (SnakeSize, SnakeSize))
def move(self, x, y):
self.__x = self.__x + x
self.__y = self.__y + y
def set_direction(self, direction):
if self.__direction == direction:
return
self.__direction = direction
name = None
if self.__ishead:
name = "head.png"
elif self.__istail:
name = "tail.png"
else:
name = "body.png"
angle = 0
match direction:
case pygame.K_UP:
angle = 0
case pygame.K_DOWN:
angle = 180
case pygame.K_LEFT:
angle = 90
case pygame.K_RIGHT:
angle = -90
img = pygame.image.load(f"asset/img/{name}")
img = pygame.transform.rotate(img, angle)
self.image = pygame.transform.scale(img, (SnakeSize, SnakeSize))
def get_direction(self):
return self.__direction
class Snake:
bodys = []
new_body = None
__new_direction = pygame.K_UP
__tick_movement = 0
__tick_create_body = 0
__stop = False
__is_paused = False
def __init__(self):
self.bodys.insert(0, SnakeBody(SnakeSize * SnakeStartX, SnakeSize * SnakeStartY, pygame.K_UP, True, False))
self.bodys.insert(1,
SnakeBody(SnakeSize * SnakeStartX, SnakeSize * (SnakeStartY + 1), pygame.K_UP, False, False))
self.bodys.insert(2,
SnakeBody(SnakeSize * SnakeStartX, SnakeSize * (SnakeStartY + 2), pygame.K_UP, False, True))
def set_direction(self, direction):
# do not set inverse direction
if ((self.bodys[0].get_direction() == pygame.K_UP and direction != pygame.K_DOWN) or
(self.bodys[0].get_direction() == pygame.K_DOWN and direction != pygame.K_UP) or
(self.bodys[0].get_direction() == pygame.K_LEFT and direction != pygame.K_RIGHT) or
(self.bodys[0].get_direction() == pygame.K_RIGHT and direction != pygame.K_LEFT)):
self.__new_direction = direction
def move(self):
if self.__stop:
return
if self.__is_paused:
return
self.__tick_movement += 1
if self.__tick_movement <= Tick_Snake_Move:
return
self.__tick_movement = 0
length = len(self.bodys)
head = self.bodys[0]
oldheadpos = head.get_rect()
oldheaddirection = head.get_direction()
# update head direction and move
head.set_direction(self.__new_direction)
match self.__new_direction:
case pygame.K_UP:
head.move(0, -SnakeSize)
case pygame.K_DOWN:
head.move(0, SnakeSize)
case pygame.K_LEFT:
head.move(-SnakeSize, 0)
case pygame.K_RIGHT:
head.move(SnakeSize, 0)
if ((self.new_body is not None) and
(head.get_rect().x == self.new_body.get_rect().x and head.get_rect().y == self.new_body.get_rect().y)):
# as head move, the old head position is empty,
# add the new body at the second position
self.new_body.set_direction(head.get_direction())
offsetx = oldheadpos.x - self.new_body.get_rect().x
offsety = oldheadpos.y - self.new_body.get_rect().y
self.new_body.move(offsetx, offsety)
self.bodys.insert(1, self.new_body)
self.new_body = None
global score_value
score_value += 1
else:
# as head move, the old head position is empty,
# move the second-to-last body to the second body
second2lastbody = self.bodys[length - 2]
second2lastpos = second2lastbody.get_rect()
second2lastdirection = second2lastbody.get_direction()
offsetx = oldheadpos.x - second2lastpos.x
offsety = oldheadpos.y - second2lastpos.y
second2lastbody.set_direction(oldheaddirection)
second2lastbody.move(offsetx, offsety)
self.bodys.remove(second2lastbody)
self.bodys.insert(1, second2lastbody)
# move tail to the direction of the second-to-last body
tailbody = self.bodys[length - 1]
tailbody.set_direction(second2lastdirection)
offsetx = second2lastpos.x - tailbody.get_rect().x
offsety = second2lastpos.y - tailbody.get_rect().y
tailbody.move(offsetx, offsety)
def stop(self):
self.__stop = True
def create_body(self):
self.__tick_create_body += 1
if self.__tick_create_body <= 30:
return
if self.is_paused():
return
self.__tick_create_body = 0
if self.new_body is not None:
return
x, y = 0, 0
while True:
isspare = True
intx = random.randint(0, MaxWidthBlock - 1)
inty = random.randint(ControlPanelHeightBlock, MaxHeightBlock - 1)
x = intx * SnakeSize
y = inty * SnakeSize
for b in self.bodys:
rect = b.get_rect()
if rect.x == x and rect.y == y:
isspare = False
break
if isspare:
break
print(f"create body block at {intx}, {inty}")
self.new_body = SnakeBody(x, y, pygame.K_UP, False, False)
def is_collided(self):
iscollided = False
head = self.bodys[0]
headrect = self.bodys[0].get_rect()
# boundary collision
if headrect.x <= (0 - SnakeSize) or headrect.x >= GamePanelWidth or \
headrect.y <= (ControlPanelHeight - SnakeSize) or headrect.y >= (ControlPanelHeight + GamePanelHeight):
iscollided = True
# body collision
else:
if head.get_direction() == pygame.K_LEFT:
pass
for b in self.bodys[1:len(self.bodys)]:
if head.get_rect().colliderect(b.get_rect()):
iscollided = True
break
return iscollided
def pause(self):
self.__is_paused = not self.__is_paused
def is_paused(self):
return self.__is_paused
def display_result():
final_text1 = "Game Over"
final_surf = pygame.font.SysFont("Arial", SnakeSize * 2).render(final_text1, 1, (242, 3, 36)) # 设置颜色
screen.blit(final_surf, [screen.get_width() / 2 - final_surf.get_width() / 2,
screen.get_height() / 2 - final_surf.get_height() / 2]) # 设置显示位置
def display_paused():
paused_text = "Paused"
paused_surf = pygame.font.SysFont("Arial", SnakeSize * 2).render(paused_text, 1, (242, 3, 36))
screen.blit(paused_surf, [screen.get_width() / 2 - paused_surf.get_width() / 2,
screen.get_height() / 2 - paused_surf.get_height() / 2])
def display_control_panel():
global minus_btn_rect, plus_btn_rect
color = (242, 3, 36)
speed_text = "Speed"
speed_surf = pygame.font.SysFont("Arial", SnakeSize).render(speed_text, 1, "blue") # 设置颜色
speed_rect = speed_surf.get_rect()
speed_rect.x, speed_rect.y = 0, 0
screen.blit(speed_surf, speed_rect)
offsetx = speed_rect.x + speed_rect.width + 10
text_minus = "-"
minus_btn = pygame.font.SysFont("Arial", SnakeSize).render(text_minus, 1, color) # 设置颜色
minus_btn_rect = minus_btn.get_rect()
minus_btn_rect.x, minus_btn_rect.y = offsetx, 0
screen.blit(minus_btn, minus_btn_rect)
offsetx = minus_btn_rect.x + minus_btn_rect.width + 10
text_speed_value = str(Tick - Tick_Snake_Move)
speed_value_surf = pygame.font.SysFont("Arial", SnakeSize).render(text_speed_value, 1, color) # 设置颜色
speed_value_rect = speed_value_surf.get_rect()
speed_value_rect.x, speed_value_rect.y = offsetx, 0
screen.blit(speed_value_surf, speed_value_rect)
offsetx = speed_value_rect.x + speed_value_rect.width + 10
text_plus = "+"
plus_btn = pygame.font.SysFont("Arial", SnakeSize).render(text_plus, 1, color) # 设置颜色
plus_btn_rect = plus_btn.get_rect()
plus_btn_rect.x, plus_btn_rect.y = offsetx, 0
screen.blit(plus_btn, plus_btn_rect)
score_value_text = str(score_value)
score_value_surf = pygame.font.SysFont("Arial", SnakeSize).render(score_value_text, 1, color) # 设置颜色
score_value_rect = score_value_surf.get_rect()
score_value_rect.x = GamePanelWidth - score_value_rect.width
score_value_rect.y = 0
screen.blit(score_value_surf, score_value_rect)
score_text = "Score"
score_surf = pygame.font.SysFont("Arial", SnakeSize).render(score_text, 1, "blue") # 设置颜色
score_rect = score_surf.get_rect()
score_rect.x = score_value_rect.x - score_rect.width - 10
score_rect.y = 0
screen.blit(score_surf, score_rect)
def check_click(position):
global Tick_Snake_Move
if minus_btn_rect == None or plus_btn_rect == None:
return
x, y = position[0], position[1]
minus_btn_x, minus_btn_y = minus_btn_rect.x, minus_btn_rect.y
plus_btn_x, plus_btn_y = plus_btn_rect.x, plus_btn_rect.y
if minus_btn_x < x < minus_btn_x + minus_btn_rect.width and \
minus_btn_y < y < minus_btn_y + minus_btn_rect.height:
Tick_Snake_Move += 1
elif plus_btn_x < x < plus_btn_x + plus_btn_rect.width and \
plus_btn_y < y < plus_btn_y + plus_btn_rect.height:
Tick_Snake_Move -= 1
pygame.init()
pygame.font.init() # 初始化字体
screen = pygame.display.set_mode((GamePanelWidth, ControlPanelHeight + GamePanelHeight))
clock = pygame.time.Clock()
snake = Snake()
screen.fill(ControlPanelColor, ControlPanelRect)
while True:
clock.tick(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT or event.key == pygame.K_a:
snake.set_direction(pygame.K_LEFT)
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
snake.set_direction(pygame.K_RIGHT)
elif event.key == pygame.K_UP or event.key == pygame.K_w:
snake.set_direction(pygame.K_UP)
elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
snake.set_direction(pygame.K_DOWN)
elif event.key == pygame.K_SPACE:
snake.pause()
if pygame.mouse.get_pressed()[0]:
check_click(pygame.mouse.get_pos())
screen.fill(GamePanelColor, (0, ControlPanelHeight, GamePanelWidth, ControlPanelHeight + GamePanelHeight))
snake.move()
for body in snake.bodys:
screen.blit(body.image, body.get_rect())
# collision detection
if snake.is_collided():
snake.stop()
display_result()
else:
# new body
snake.create_body()
if snake.new_body is not None:
screen.blit(snake.new_body.image, snake.new_body.get_rect())
screen.fill(ControlPanelColor, (0, 0, GamePanelWidth, ControlPanelHeight))
display_control_panel()
if snake.is_paused():
display_paused()
pygame.display.flip()