pygame - 贪吃蛇小游戏

蛇每吃掉一个身体块,蛇身就增加一个长度。为了统一计算,界面的尺寸和游戏元素的位置都是身体块长度的倍数
1. 上下左右方向键(或者ASDW键)控制蛇的移动方向
2. 空格键暂停和继续

图片文件,复制到项目的asset\img目录下

pygame - 贪吃蛇小游戏_第1张图片

pygame - 贪吃蛇小游戏_第2张图片

pygame - 贪吃蛇小游戏_第3张图片

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

你可能感兴趣的:(pygame,python,开发语言)