编写目标

使用pygame仿照B站上经常可以刷到的小球能否在规定时间内逃脱

环境设置与窗口初始化

安装pygame

pip install pygame

导入必要的模块并初始化 Pygame

导入模块

导入pygamesys 模块,其中 pygame 用于游戏开发,sys 用于系统退出。

import pygame
import sys

初始化 Pygame并设置屏幕宽度和高度还有颜色

使用 pygame.init() 初始化所有 Pygame 模块。

# 初始化 Pygame
pygame.init()
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))

pygame.display.set_caption("小球逃脱")# 窗口标题

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

时钟与帧率

使用 pygame.time.Clock() 控制游戏的帧率。

clock = pygame.time.Clock()
FPS = 60

主循环

一个无限循环,用于处理事件、更新游戏状态和绘制内容。按下窗口的关闭按钮将退出循环并关闭游戏。

running = True
while running:
    clock.tick(FPS)  # 控制游戏帧率

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 填充背景
    screen.fill(WHITE)

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

整体代码

import pygame
import sys

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 主循环
running = True
while running:
    clock.tick(FPS)  # 控制游戏帧率

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 填充背景
    screen.fill(WHITE)

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

运行效果

绘制大圆

在窗口中央绘制一个静止的大圆。这个大圆将作为小球活动的边界,设定大圆的半径为300像素。

定义大圆的参数

CENTER = (WIDTH // 2, HEIGHT // 2) #保证位于屏幕的中心
BIG_CIRCLE_RADIUS = 300  # 大圆半径

大圆绘制代码

使用 pygame.draw.circle() 函数在屏幕上绘制大圆。

  • screen:绘制的目标表面。
  • BLACK:圆的颜色。
  • CENTER:圆心位置。
  • BIG_CIRCLE_RADIUS:圆的半径。
  • 2:圆的线宽,设置为2像素。
 pygame.draw.circle(screen, BLACK, CENTER, BIG_CIRCLE_RADIUS, 2)  # 线宽为2

整体代码

import pygame
import sys

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
BIG_CIRCLE_RADIUS = 300  # 大圆半径

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 主循环
running = True
while running:
    clock.tick(FPS)  # 控制游戏帧率

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 填充背景
    screen.fill(WHITE)

    # 画大圆
    pygame.draw.circle(screen, BLACK, CENTER, BIG_CIRCLE_RADIUS, 2)  # 线宽为2

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

创建小球类并绘制小球

创建一个 SmallCircle 类,用于管理小球的属性和行为。然后,在窗口中绘制一个静止的小球。

SmallCircle

初始化小球的半径、颜色和位置。初始位置设定在大圆的中心。draw 方法:使用 pygame.draw.circle() 在指定表面绘制小球。

class SmallCircle:
    def __init__(self, radius=10, color=RED):
        self.radius = radius
        self.color = color
        self.pos = list(CENTER)  # 初始位置在大圆中心

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

实例化并显示

small_circle = SmallCircle()

这里是实例化的小球

    # 画小球
    small_circle.draw(screen)

在主循环中绘制小球

整体代码

import pygame
import sys

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
BIG_CIRCLE_RADIUS = 300  # 大圆半径

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 小球类
class SmallCircle:
    def __init__(self, radius=10, color=RED):
        self.radius = radius
        self.color = color
        self.pos = list(CENTER)  # 初始位置在大圆中心

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

# 实例化小球
small_circle = SmallCircle()

# 主循环
running = True
while running:
    clock.tick(FPS)  # 控制游戏帧率

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 填充背景
    screen.fill(WHITE)

    # 画大圆
    pygame.draw.circle(screen, BLACK, CENTER, BIG_CIRCLE_RADIUS, 2)  # 线宽为2

    # 画小球
    small_circle.draw(screen)

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

红色的小球位于大圆的中心位置。此时,小球是静止的,没有运动。

实现小球的运动与碰撞检测

为小球添加运动能力,使其以固定速度向随机方向移动,同时,需要检测小球是否碰到大圆的边界,并在碰撞时反弹。

SmallCircle添加速度和移动逻辑

  • 速度设置:构建set_random_velocity方法为小球设置一个随机方向的速度向量。通过生成一个随机角度 angle,并计算水平和垂直速度分量。
  • 位置更新与碰撞检测:构建update方法,更新小球的位置,根据当前速度向量 vel;计算小球与大圆中心的距离 dist;如果小球触及或超过大圆边界(dist + self.radius >= BIG_CIRCLE_RADIUS),则反射其速度方向,实现弹跳效果。
  • 反射逻辑:计算小球与大圆中心的夹角 angle,更新速度向量,使其朝相反方向移动。

整体代码

import pygame
import sys
import math
import random

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
BIG_CIRCLE_RADIUS = 300  # 大圆半径

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 工具函数
def distance(p1, p2):
    """计算两点之间的距离"""
    return math.hypot(p1[0] - p2[0], p1[1] - p2[1])

def normalize(vec):
    """归一化一个向量"""
    mag = math.hypot(vec[0], vec[1])
    if mag == 0:
        return [0, 0]
    return [vec[0] / mag, vec[1] / mag]

# 小球类
class SmallCircle:
    def __init__(self, radius=10, color=RED, speed=4):
        self.radius = radius
        self.color = color
        self.speed = speed  # 固定速度大小
        self.pos = list(CENTER)  # 初始位置在大圆中心
        self.set_random_velocity()

    def set_random_velocity(self):
        """为小球设置一个随机的速度方向"""
        angle = random.uniform(0, 2 * math.pi)
        self.vel = [
            self.speed * math.cos(angle),
            self.speed * math.sin(angle)
        ]

    def update(self):
        """更新小球的位置,并处理与大圆的碰撞"""
        # 更新位置
        self.pos[0] += self.vel[0]
        self.pos[1] += self.vel[1]

        # 计算与大圆中心的距离
        dist = distance(self.pos, CENTER)

        # 检测碰撞:如果小球触及大圆边界
        if dist + self.radius >= BIG_CIRCLE_RADIUS:
            # 计算反射角
            angle = math.atan2(self.pos[1] - CENTER[1], self.pos[0] - CENTER[0])
            # 反射方向
            self.vel[0] = -self.speed * math.cos(angle)
            self.vel[1] = -self.speed * math.sin(angle)

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

# 实例化小球
small_circle = SmallCircle()

# 主循环
running = True
while running:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 更新小球位置
    small_circle.update()

    # 填充背景
    screen.fill(WHITE)

    # 画大圆
    pygame.draw.circle(screen, BLACK, CENTER, BIG_CIRCLE_RADIUS, 2)  # 线宽为2

    # 画小球
    small_circle.draw(screen)

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

小球从大圆中心向随机方向移动,并在碰到大圆边界时反弹,持续在大圆内部弹跳。

添加旋转缺口的大圆

在大圆上添加一个不断旋转的缺口。当小球通过这个缺口时,可以实现移除大圆

BigCircle

创建 BigCircle 类,管理大圆的缺口旋转和绘制。

初始化

定义中心位置半径缺口宽度旋转速度

update 方法

根据时间增量 dt 更新缺口的角度,确保缺口持续旋转。

is_within_notch 方法

判断给定角度是否位于当前缺口区域内。

draw 方法

绘制大圆的边界,计算缺口的外环和内环点,使用多边形绘制缺口区域(用白色填充,覆盖大圆的一部分)。

class BigCircle:
    def __init__(self, center, radius, notch_width_deg=30, angular_speed_deg_per_sec=30):
        self.center = center
        self.radius = radius
        self.notch_width = math.radians(notch_width_deg)  # 缺口宽度,转换为弧度
        self.angular_speed = math.radians(angular_speed_deg_per_sec)  # 旋转速度,弧度/秒
        self.current_angle = 0  # 当前缺口角度

    def update(self, dt):
        self.current_angle += self.angular_speed * dt
        self.current_angle %= 2 * math.pi  # 保持角度在0到2π之间

    def is_within_notch(self, angle):
        angle = angle % (2 * math.pi)
        start_angle = self.current_angle
        end_angle = (self.current_angle + self.notch_width) % (2 * math.pi)
        if start_angle < end_angle:
            return start_angle <= angle <= end_angle
        else:
            # 缺口跨越了0度
            return angle >= start_angle or angle <= end_angle

    def draw(self, surface):
        # 画大圆
        pygame.draw.circle(surface, BLACK, self.center, self.radius, 2)

        # 绘制缺口区域(用背景色填充)
        notch_points = []
        num_points = 30  # 缺口的平滑程度
        outer_radius = self.radius
        inner_radius = self.radius - 10  # 缺口的厚度

        # 计算缺口的外环点
        for i in range(num_points + 1):
            angle = self.current_angle + (self.notch_width * i) / num_points
            x = self.center[0] + outer_radius * math.cos(angle)
            y = self.center[1] + outer_radius * math.sin(angle)
            notch_points.append((x, y))

        # 计算缺口的内环点
        for i in range(num_points + 1):
            angle = self.current_angle + self.notch_width - (self.notch_width * i) / num_points
            x = self.center[0] + inner_radius * math.cos(angle)
            y = self.center[1] + inner_radius * math.sin(angle)
            notch_points.append((x, y))

        # 绘制缺口区域
        pygame.draw.polygon(surface, WHITE, notch_points)

SmallCircle

update

更新小球的位置,计算小球与大圆中心的距离和角度,检测是否碰到大圆边界,如果小球通过缺口(is_within_notch 返回 True),则重置小球位置,否则,反弹小球的速度方向。

bounce

计算反射后的速度方向,使小球反弹回大圆内部。

reset_position

将小球位置重置到大圆中心,并设置新的随机速度方向。

class SmallCircle:
    def __init__(self, radius=10, color=RED, speed=4):
        self.radius = radius
        self.color = color
        self.speed = speed  # 固定速度大小
        self.pos = list(CENTER)  # 初始位置在大圆中心
        self.set_random_velocity()

    def set_random_velocity(self):
        """为小球设置一个随机的速度方向"""
        angle = random.uniform(0, 2 * math.pi)
        self.vel = [
            self.speed * math.cos(angle),
            self.speed * math.sin(angle)
        ]

    def update(self, big_circle):
        """更新小球的位置,并处理与大圆的碰撞"""
        # 更新位置
        self.pos[0] += self.vel[0]
        self.pos[1] += self.vel[1]

        # 计算与大圆中心的距离
        dist = distance(self.pos, self.center())

        # 计算小球相对于大圆中心的角度
        angle = math.atan2(self.pos[1] - self.center()[1], self.pos[0] - self.center()[0])

        # 检测是否碰到大圆边界
        if dist + self.radius >= big_circle.radius:
            if big_circle.is_within_notch(angle):
                # 通过缺口,重置小球位置 删除大圆
                big_circles.remove(big_circle)
                self.reset_position()
            else:
                # 反弹
                self.bounce(angle)

    def center(self):
        """返回大圆的中心位置"""
        return CENTER

    def bounce(self, angle):
        """反弹小球的速度方向"""
        # 反射角度
        reflected_angle = angle + math.pi
        self.vel[0] = self.speed * math.cos(reflected_angle)
        self.vel[1] = self.speed * math.sin(reflected_angle)

    def reset_position(self):
        """重置小球到大圆中心,并设置新方向"""
        self.pos = list(CENTER)
        self.set_random_velocity()

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

整体代码

import pygame
import sys
import math
import random

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
BIG_CIRCLE_RADIUS = 300  # 大圆半径

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 工具函数
def distance(p1, p2):
    """计算两点之间的距离"""
    return math.hypot(p1[0] - p2[0], p1[1] - p2[1])

def normalize(vec):
    """归一化一个向量"""
    mag = math.hypot(vec[0], vec[1])
    if mag == 0:
        return [0, 0]
    return [vec[0] / mag, vec[1] / mag]

# 大圆类
class BigCircle:
    def __init__(self, center, radius, notch_width_deg=30, angular_speed_deg_per_sec=30):
        self.center = center
        self.radius = radius
        self.notch_width = math.radians(notch_width_deg)  # 缺口宽度,转换为弧度
        self.angular_speed = math.radians(angular_speed_deg_per_sec)  # 旋转速度,弧度/秒
        self.current_angle = 0  # 当前缺口角度

    def update(self, dt):
        """更新缺口的角度"""
        self.current_angle += self.angular_speed * dt
        self.current_angle %= 2 * math.pi  # 保持角度在0到2π之间

    def is_within_notch(self, angle):
        """判断给定角度是否在缺口区域内"""
        angle = angle % (2 * math.pi)
        start_angle = self.current_angle
        end_angle = (self.current_angle + self.notch_width) % (2 * math.pi)
        if start_angle < end_angle:
            return start_angle <= angle <= end_angle
        else:
            # 缺口跨越了0度
            return angle >= start_angle or angle <= end_angle

    def draw(self, surface):
        # 画大圆
        pygame.draw.circle(surface, BLACK, self.center, self.radius, 2)

        # 绘制缺口区域(用背景色填充)
        notch_points = []
        num_points = 30  # 缺口的平滑程度
        outer_radius = self.radius
        inner_radius = self.radius - 10  # 缺口的厚度

        # 计算缺口的外环点
        for i in range(num_points + 1):
            angle = self.current_angle + (self.notch_width * i) / num_points
            x = self.center[0] + outer_radius * math.cos(angle)
            y = self.center[1] + outer_radius * math.sin(angle)
            notch_points.append((x, y))

        # 计算缺口的内环点
        for i in range(num_points + 1):
            angle = self.current_angle + self.notch_width - (self.notch_width * i) / num_points
            x = self.center[0] + inner_radius * math.cos(angle)
            y = self.center[1] + inner_radius * math.sin(angle)
            notch_points.append((x, y))

        # 绘制缺口区域
        pygame.draw.polygon(surface, WHITE, notch_points)

# 小球类
class SmallCircle:
    def __init__(self, radius=10, color=RED, speed=4):
        self.radius = radius
        self.color = color
        self.speed = speed  # 固定速度大小
        self.pos = list(CENTER)  # 初始位置在大圆中心
        self.set_random_velocity()

    def set_random_velocity(self):
        """为小球设置一个随机的速度方向"""
        angle = random.uniform(0, 2 * math.pi)
        self.vel = [
            self.speed * math.cos(angle),
            self.speed * math.sin(angle)
        ]

    def update(self, big_circle):
        """更新小球的位置,并处理与大圆的碰撞"""
        # 更新位置
        self.pos[0] += self.vel[0]
        self.pos[1] += self.vel[1]

        # 计算与大圆中心的距离
        dist = distance(self.pos, self.center())

        # 计算小球相对于大圆中心的角度
        angle = math.atan2(self.pos[1] - self.center()[1], self.pos[0] - self.center()[0])

        # 检测是否碰到大圆边界
        if dist + self.radius >= big_circle.radius:
            if big_circle.is_within_notch(angle):
                # 通过缺口,重置小球位置
                self.reset_position()
                big_circles.remove(big_circle)
            else:
                # 反弹
                self.bounce(angle)

    def center(self):
        """返回大圆的中心位置"""
        return CENTER

    def bounce(self, angle):
        """反弹小球的速度方向"""
        # 反射角度
        reflected_angle = angle + math.pi
        self.vel[0] = self.speed * math.cos(reflected_angle)
        self.vel[1] = self.speed * math.sin(reflected_angle)

    def reset_position(self):
        """重置小球到大圆中心,并设置新方向"""
        self.pos = list(CENTER)
        self.set_random_velocity()

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

# 实例化大圆和小球
big_circle = BigCircle(center=CENTER, radius=BIG_CIRCLE_RADIUS)
small_circle = SmallCircle()

# 主循环
running = True
while running:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 更新大圆缺口
    big_circle.update(dt)

    # 更新小球位置
    small_circle.update(big_circle)

    # 填充背景
    screen.fill(WHITE)

    # 画大圆及其缺口
    big_circle.draw(screen)

    # 画小球
    small_circle.draw(screen)

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

管理多个大圆和小球

引入多个大圆和小球。每个大圆将有不同的半径、旋转速度和缺口宽度,而小球也可以有多个实例。

创建多个大圆

使用循环创建多个 BigCircle 实例,每个大圆的半径逐渐增大,缺口宽度和旋转速度随机分配。 将大圆按半径从大到小排序,确保在绘制时较大的大圆先绘制,避免被较小的大圆覆盖。

设置最大半径和最小半径

MIN_RADIUS_INIT = 30
MAX_RADIUS = 400

创建多个大圆

# 创建多个大圆实例
num_big_circles = 3  # 创建3个大圆
big_circles = []
radius_increment = (MAX_RADIUS - MIN_RADIUS_INIT) / num_big_circles
current_radius = MIN_RADIUS_INIT
for _ in range(num_big_circles):
    current_radius += radius_increment
    angular_speed = random.uniform(15, 45)  # 15到45度每秒
    notch_width_deg = random.uniform(20, 30)  # 缺口宽度在20到30度之间
    big_circle = BigCircle(center=CENTER, radius=current_radius, notch_width_deg=notch_width_deg, angular_speed_deg_per_sec=angular_speed)
    big_circles.append(big_circle)

# 按照半径从大到小排序,以确保较大的大圆先绘制
big_circles.sort(key=lambda bc: bc.radius, reverse=True)

创建多个小球

使用列表推导创建多个 SmallCircle 实例,颜色随机选择。

num_circles = 2  # 创建2个小球
colors = [RED, BLUE, GREEN]
small_circles = [
    SmallCircle(radius=10, color=random.choice(colors), speed=4)
    for _ in range(num_circles)
]

更新与绘制

在主循环中,更新所有大圆和小球的位置。 绘制所有大圆和小球到屏幕上。

running = True
while running:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 更新所有大圆缺口
    for big_circle in big_circles:
        big_circle.update(dt)

    # 更新所有小球位置
    for circle in small_circles:
        circle.update(big_circles)

    # 填充背景
    screen.fill(WHITE)

    # 画所有大圆及其缺口(从大到小绘制,以避免覆盖问题)
    for big_circle in big_circles:
        big_circle.draw(screen)

    # 画所有小球
    for circle in small_circles:
        circle.draw(screen)

    # 更新显示
    pygame.display.flip()

整体代码

import pygame
import sys
import math
import random

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
MIN_RADIUS_INIT = 30
MAX_RADIUS = 400

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 工具函数
def distance(p1, p2):
    """计算两点之间的距离"""
    return math.hypot(p1[0] - p2[0], p1[1] - p2[1])

def normalize(vec):
    """归一化一个向量"""
    mag = math.hypot(vec[0], vec[1])
    if mag == 0:
        return [0, 0]
    return [vec[0] / mag, vec[1] / mag]

# 大圆类
class BigCircle:
    def __init__(self, center, radius, notch_width_deg=30, angular_speed_deg_per_sec=30):
        self.center = center
        self.radius = radius
        self.notch_width = math.radians(notch_width_deg)  # 缺口宽度,转换为弧度
        self.angular_speed = math.radians(angular_speed_deg_per_sec)  # 旋转速度,弧度/秒
        self.current_angle = 0  # 当前缺口角度

    def update(self, dt):
        """更新缺口的角度"""
        self.current_angle += self.angular_speed * dt
        self.current_angle %= 2 * math.pi  # 保持角度在0到2π之间

    def is_within_notch(self, angle):
        """判断给定角度是否在缺口区域内"""
        angle = angle % (2 * math.pi)
        start_angle = self.current_angle
        end_angle = (self.current_angle + self.notch_width) % (2 * math.pi)
        if start_angle < end_angle:
            return start_angle <= angle <= end_angle
        else:
            # 缺口跨越了0度
            return angle >= start_angle or angle <= end_angle

    def draw(self, surface):
        # 画大圆
        pygame.draw.circle(surface, BLACK, self.center, self.radius, 2)

        # 绘制缺口区域(用背景色填充)
        notch_points = []
        num_points = 30  # 缺口的平滑程度
        outer_radius = self.radius
        inner_radius = self.radius - 10  # 缺口的厚度

        # 计算缺口的外环点
        for i in range(num_points + 1):
            angle = self.current_angle + (self.notch_width * i) / num_points
            x = self.center[0] + outer_radius * math.cos(angle)
            y = self.center[1] + outer_radius * math.sin(angle)
            notch_points.append((x, y))

        # 计算缺口的内环点
        for i in range(num_points + 1):
            angle = self.current_angle + self.notch_width - (self.notch_width * i) / num_points
            x = self.center[0] + inner_radius * math.cos(angle)
            y = self.center[1] + inner_radius * math.sin(angle)
            notch_points.append((x, y))

        # 绘制缺口区域
        pygame.draw.polygon(surface, WHITE, notch_points)

# 小球类
class SmallCircle:
    def __init__(self, radius=10, color=RED, speed=4):
        self.radius = radius
        self.color = color
        self.speed = speed  # 固定速度大小
        self.pos = list(CENTER)  # 初始位置在大圆中心
        self.set_random_velocity()

    def set_random_velocity(self):
        """为小球设置一个随机的速度方向"""
        angle = random.uniform(0, 2 * math.pi)
        self.vel = [
            self.speed * math.cos(angle),
            self.speed * math.sin(angle)
        ]

    def update(self, big_circles):
        """更新小球的位置,并处理与大圆的碰撞"""
        # 更新位置
        self.pos[0] += self.vel[0]
        self.pos[1] += self.vel[1]

        # 计算与大圆中心的距离
        dist = distance(self.pos, self.center())

        # 计算小球相对于大圆中心的角度
        angle = math.atan2(self.pos[1] - self.center()[1], self.pos[0] - self.center()[0])

        # 按照大圆半径从大到小排序
        sorted_big_circles = sorted(big_circles, key=lambda bc: bc.radius, reverse=True)

        for big_circle in sorted_big_circles:
            if dist + self.radius >= big_circle.radius:
                if big_circle.is_within_notch(angle):
                    # 通过缺口,重置小球位置
                    self.reset_position()
                    big_circles.remove(big_circle)
                else:
                    # 反弹
                    self.bounce(angle)
                break  # 处理完一个大圆后跳出循环

    def center(self):
        """返回大圆的中心位置"""
        return CENTER

    def bounce(self, angle):
        """反弹小球的速度方向"""
        # 反射角度
        reflected_angle = angle + math.pi
        self.vel[0] = self.speed * math.cos(reflected_angle)
        self.vel[1] = self.speed * math.sin(reflected_angle)

    def reset_position(self):
        """重置小球到大圆中心,并设置新方向"""
        self.pos = list(CENTER)
        self.set_random_velocity()

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

# 创建多个大圆实例
num_big_circles = 3  # 创建3个大圆
big_circles = []
radius_increment = (MAX_RADIUS - MIN_RADIUS_INIT) / num_big_circles
current_radius = MIN_RADIUS_INIT
for _ in range(num_big_circles):
    current_radius += radius_increment
    angular_speed = random.uniform(15, 45)  # 15到45度每秒
    notch_width_deg = random.uniform(20, 30)  # 缺口宽度在20到30度之间
    big_circle = BigCircle(center=CENTER, radius=current_radius, notch_width_deg=notch_width_deg, angular_speed_deg_per_sec=angular_speed)
    big_circles.append(big_circle)

# 按照半径从大到小排序,以确保较大的大圆先绘制
big_circles.sort(key=lambda bc: bc.radius, reverse=True)

# 创建多个小球
num_circles = 2  # 创建2个小球
colors = [RED, BLUE, GREEN]
small_circles = [
    SmallCircle(radius=10, color=random.choice(colors), speed=4)
    for _ in range(num_circles)
]

# 主循环
running = True
while running:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 更新所有大圆缺口
    for big_circle in big_circles:
        big_circle.update(dt)

    # 更新所有小球位置
    for circle in small_circles:
        circle.update(big_circles)

    # 填充背景
    screen.fill(WHITE)

    # 画所有大圆及其缺口(从大到小绘制,以避免覆盖问题)
    for big_circle in big_circles:
        big_circle.draw(screen)

    # 画所有小球
    for circle in small_circles:
        circle.draw(screen)

    # 更新显示
    pygame.display.flip()

# 退出 Pygame
pygame.quit()
sys.exit()

添加计时器与游戏状态

通过小球的移动清除所有大圆。游戏将根据时间和大圆的数量来判断胜利或失败。

字体设置

加载“微软雅黑”字体,如果不可用则使用默认字体,并在控制台打印提示信息。

# 字体设置
pygame.font.init()
try:
    font = pygame.font.SysFont("Microsoft YaHei", 36)
    large_font = pygame.font.SysFont("Microsoft YaHei", 72)
except:
    # 如果微软雅黑字体不可用,使用默认字体
    font = pygame.font.SysFont(None, 36)
    large_font = pygame.font.SysFont(None, 72)
    print("微软雅黑字体不可用,已使用默认字体。")

计时器

  • TOTAL_TIME:设定游戏的总时长为100秒。

  • remaining_time:跟踪剩余时间。

    # 计时器设置
    TOTAL_TIME = 100  # 总时间为100秒
    remaining_time = TOTAL_TIME
    

主循环更新

游戏状态

state 变量用于管理游戏的不同状态,包括运行中 ("running")、胜利 ("victory") 和失败 ("failure")。

状态检测

  • 如果游戏状态为 "running",则更新计时器、所有大圆和小球的位置。

  • 检查计时器是否到达0,若是则将状态设置为 "failure"。

  • 检查是否所有大圆都被移除,若是则将状态设置为 "victory"。

    绘制与显示

  • 运行中:绘制所有大圆、小球和剩余时间。

  • 胜利:显示“胜利!”的文字,并提示用户按任意键退出。

  • 失败:显示“失败!”的文字,并提示用户按任意键退出。

while True:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    if state == "running":
        # 更新计时器
        remaining_time -= dt
        if remaining_time <= 0:
            remaining_time = 0
            state = "failure"

        # 更新所有大圆
        for big_circle in big_circles:
            big_circle.update(dt)

        # 更新所有小球
        for circle in small_circles:
            circle.update(big_circles)

        # 检查是否所有大圆都被移除
        if len(big_circles) == 0:
            state = "victory"

    # 填充背景
    screen.fill(WHITE)

    if state == "running":
        # 画所有大圆及其缺口(从大到小绘制,以避免覆盖问题)
        for big_circle in big_circles:
            big_circle.draw(screen)

        # 画所有小球
        for circle in small_circles:
            circle.draw(screen)

        # 绘制计时器
        timer_text = font.render(f"剩余时间: {int(remaining_time)}s", True, BLACK)
        screen.blit(timer_text, (10, 10))

    elif state == "victory":
        # 绘制胜利画面
        victory_text = large_font.render("胜利!", True, GREEN)
        text_rect = victory_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        screen.blit(victory_text, text_rect)

        # 提示用户按下任意键退出
        prompt_text = font.render("按任意键退出", True, BLACK)
        prompt_rect = prompt_text.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        screen.blit(prompt_text, prompt_rect)

    elif state == "failure":
        # 绘制失败画面
        failure_text = large_font.render("失败!", True, RED)
        text_rect = failure_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        screen.blit(failure_text, text_rect)

        # 提示用户按下任意键退出
        prompt_text = font.render("按任意键退出", True, BLACK)
        prompt_rect = prompt_text.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        screen.blit(prompt_text, prompt_rect)

    # 如果游戏结束(胜利或失败),等待用户按键退出
    if state in ["victory", "failure"]:
        keys = pygame.key.get_pressed()
        if any(keys):
            pygame.quit()
            sys.exit()

    # 更新显示
    pygame.display.flip()

整体代码

import pygame
import sys
import math
import random

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
GRAY = (200, 200, 200)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
MIN_RADIUS_INIT = 30
MAX_RADIUS = 400

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 字体设置
pygame.font.init()
try:
    font = pygame.font.SysFont("Microsoft YaHei", 36)
    large_font = pygame.font.SysFont("Microsoft YaHei", 72)
except:
    # 如果微软雅黑字体不可用,使用默认字体
    font = pygame.font.SysFont(None, 36)
    large_font = pygame.font.SysFont(None, 72)
    print("微软雅黑字体不可用,已使用默认字体。")

# 工具函数
def distance(p1, p2):
    """计算两点之间的距离"""
    return math.hypot(p1[0] - p2[0], p1[1] - p2[1])

def normalize(vec):
    """归一化一个向量"""
    mag = math.hypot(vec[0], vec[1])
    if mag == 0:
        return [0, 0]
    return [vec[0] / mag, vec[1] / mag]

# 大圆类
class BigCircle:
    def __init__(self, center, radius, notch_width_deg=30, angular_speed_deg_per_sec=30):
        self.center = center
        self.radius = radius
        self.notch_width = math.radians(notch_width_deg)  # 缺口宽度,转换为弧度
        self.angular_speed = math.radians(angular_speed_deg_per_sec)  # 旋转速度,弧度/秒
        self.current_angle = 0  # 当前缺口角度

    def update(self, dt):
        """更新缺口的角度"""
        self.current_angle += self.angular_speed * dt
        self.current_angle %= 2 * math.pi  # 保持角度在0到2π之间

    def is_within_notch(self, angle):
        """判断给定角度是否在缺口区域内"""
        angle = angle % (2 * math.pi)
        start_angle = self.current_angle
        end_angle = (self.current_angle + self.notch_width) % (2 * math.pi)
        if start_angle < end_angle:
            return start_angle <= angle <= end_angle
        else:
            # 缺口跨越了0度
            return angle >= start_angle or angle <= end_angle

    def draw(self, surface):
        # 画大圆
        pygame.draw.circle(surface, BLACK, self.center, self.radius, 2)

        # 绘制缺口区域(用背景色填充)
        notch_points = []
        num_points = 30  # 缺口的平滑程度
        outer_radius = self.radius
        inner_radius = self.radius - 10  # 缺口的厚度

        # 计算缺口的外环点
        for i in range(num_points + 1):
            angle = self.current_angle + (self.notch_width * i) / num_points
            x = self.center[0] + outer_radius * math.cos(angle)
            y = self.center[1] + outer_radius * math.sin(angle)
            notch_points.append((x, y))

        # 计算缺口的内环点
        for i in range(num_points + 1):
            angle = self.current_angle + self.notch_width - (self.notch_width * i) / num_points
            x = self.center[0] + inner_radius * math.cos(angle)
            y = self.center[1] + inner_radius * math.sin(angle)
            notch_points.append((x, y))

        # 绘制缺口区域
        pygame.draw.polygon(surface, WHITE, notch_points)

# 小球类
class SmallCircle:
    def __init__(self, radius=10, color=RED, speed=4):
        self.radius = radius
        self.color = color
        self.speed = speed  # 固定速度大小
        self.pos = list(CENTER)  # 初始位置在大圆中心
        self.set_random_velocity()

    def set_random_velocity(self, incoming_angle=None, spread_deg=90):
        """
        为小球设置一个新的速度方向。
        如果传入了 `incoming_angle`,则新方向为 `incoming_angle + π ± spread_deg`。
        否则,设置为完全随机方向。
        """
        if incoming_angle is not None:
            # 计算反方向,并在其基础上随机偏离 ±spread_deg
            spread_rad = math.radians(spread_deg)
            min_angle = incoming_angle + math.pi - spread_rad
            max_angle = incoming_angle + math.pi + spread_rad
            min_angle %= 2 * math.pi
            max_angle %= 2 * math.pi
            if min_angle < max_angle:
                direction = random.uniform(min_angle, max_angle)
            else:
                # 角度范围跨越了0度
                direction = random.uniform(min_angle, 2 * math.pi) if random.random() < (
                            (2 * math.pi - min_angle) / (2 * math.pi - min_angle + max_angle)) else random.uniform(0, max_angle)
        else:
            # 完全随机方向
            direction = random.uniform(0, 2 * math.pi)

        self.vel = [
            self.speed * math.cos(direction),
            self.speed * math.sin(direction)
        ]

    def update(self, big_circles):
        """更新小球的位置,并处理与大圆的碰撞"""
        # 更新位置
        self.pos[0] += self.vel[0]
        self.pos[1] += self.vel[1]

        # 计算与大圆中心的距离
        dist = distance(self.pos, self.center())

        # 计算小球相对于大圆中心的角度
        angle = math.atan2(self.pos[1] - self.center()[1], self.pos[0] - self.center()[0])

        # 按照大圆半径从大到小排序
        sorted_big_circles = sorted(big_circles, key=lambda bc: bc.radius, reverse=True)

        for big_circle in sorted_big_circles:
            if dist + self.radius >= big_circle.radius:
                if big_circle.is_within_notch(angle):
                    # 通过缺口,重置小球位置
                    self.reset_position()
                else:
                    # 处理小球与大圆的碰撞反弹(方向随机)
                    incoming_angle = math.atan2(self.vel[1], self.vel[0])
                    self.set_random_velocity(incoming_angle, spread_deg=15)

                    # 调整位置避免卡入边界
                    overlap = dist + self.radius - big_circle.radius
                    normal = normalize([self.pos[0] - self.center()[0], self.pos[1] - self.center()[1]])
                    self.pos[0] -= overlap * normal[0]
                    self.pos[1] -= overlap * normal[1]
                break  # 处理完一个大圆后跳出循环

    def center(self):
        """返回大圆的中心位置"""
        return CENTER

    def reset_position(self):
        """重置小球到大圆中心,并设置新方向"""
        self.pos = list(CENTER)
        self.set_random_velocity()

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

# 创建多个大圆实例
num_big_circles = 3  # 创建3个大圆
big_circles = []
radius_increment = (MAX_RADIUS - MIN_RADIUS_INIT) / num_big_circles
current_radius = MIN_RADIUS_INIT
for _ in range(num_big_circles):
    current_radius += radius_increment
    angular_speed = random.uniform(15, 45)  # 15到45度每秒
    notch_width_deg = random.uniform(20, 30)  # 缺口宽度在20到30度之间
    big_circle = BigCircle(center=CENTER, radius=current_radius, notch_width_deg=notch_width_deg, angular_speed_deg_per_sec=angular_speed)
    big_circles.append(big_circle)

# 按照半径从大到小排序,以确保较大的大圆先绘制
big_circles.sort(key=lambda bc: bc.radius, reverse=True)

# 创建多个小球
num_circles = 2  # 创建2个小球
colors = [RED, BLUE, GREEN]
small_circles = [
    SmallCircle(radius=10, color=random.choice(colors), speed=4)
    for _ in range(num_circles)
]

# 计时器设置
TOTAL_TIME = 100  # 总时间为100秒
remaining_time = TOTAL_TIME

# 游戏状态
state = "running"  # 可以是 "running", "victory", "failure"

# 主循环
while True:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    if state == "running":
        # 更新计时器
        remaining_time -= dt
        if remaining_time <= 0:
            remaining_time = 0
            state = "failure"

        # 更新所有大圆
        for big_circle in big_circles:
            big_circle.update(dt)

        # 更新所有小球
        for circle in small_circles:
            circle.update(big_circles)

        # 检查是否所有大圆都被移除
        if len(big_circles) == 0:
            state = "victory"

    # 填充背景
    screen.fill(WHITE)

    if state == "running":
        # 画所有大圆及其缺口(从大到小绘制,以避免覆盖问题)
        for big_circle in big_circles:
            big_circle.draw(screen)

        # 画所有小球
        for circle in small_circles:
            circle.draw(screen)

        # 绘制计时器
        timer_text = font.render(f"剩余时间: {int(remaining_time)}s", True, BLACK)
        screen.blit(timer_text, (10, 10))

    elif state == "victory":
        # 绘制胜利画面
        victory_text = large_font.render("胜利!", True, GREEN)
        text_rect = victory_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        screen.blit(victory_text, text_rect)

        # 提示用户按下任意键退出
        prompt_text = font.render("按任意键退出", True, BLACK)
        prompt_rect = prompt_text.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        screen.blit(prompt_text, prompt_rect)

    elif state == "failure":
        # 绘制失败画面
        failure_text = large_font.render("失败!", True, RED)
        text_rect = failure_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        screen.blit(failure_text, text_rect)

        # 提示用户按下任意键退出
        prompt_text = font.render("按任意键退出", True, BLACK)
        prompt_rect = prompt_text.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        screen.blit(prompt_text, prompt_rect)

    # 如果游戏结束(胜利或失败),等待用户按键退出
    if state in ["victory", "failure"]:
        keys = pygame.key.get_pressed()
        if any(keys):
            pygame.quit()
            sys.exit()

    # 更新显示
    pygame.display.flip()

最终代码

import pygame
import sys
import math
import random

# 初始化 Pygame
pygame.init()

# 设置屏幕尺寸
WIDTH, HEIGHT = 1200, 900
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("小球逃脱")

# 设置颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
GRAY = (200, 200, 200)

# 定义大圆的参数
CENTER = (WIDTH // 2, HEIGHT // 2)
MIN_RADIUS_INIT = 30
MAX_RADIUS = 400

# 时钟控制帧率
clock = pygame.time.Clock()
FPS = 60

# 字体设置
pygame.font.init()
try:
    font = pygame.font.SysFont("Microsoft YaHei", 36)
    large_font = pygame.font.SysFont("Microsoft YaHei", 72)
except:
    # 如果微软雅黑字体不可用,使用默认字体
    font = pygame.font.SysFont(None, 36)
    large_font = pygame.font.SysFont(None, 72)
    print("微软雅黑字体不可用,已使用默认字体。")

# 工具函数
def distance(p1, p2):
    """计算两点之间的距离"""
    return math.hypot(p1[0] - p2[0], p1[1] - p2[1])

def normalize(vec):
    """归一化一个向量"""
    mag = math.hypot(vec[0], vec[1])
    if mag == 0:
        return [0, 0]
    return [vec[0] / mag, vec[1] / mag]

# 大圆类
class BigCircle:
    def __init__(self, center, radius, notch_width_deg=30, angular_speed_deg_per_sec=30):
        self.center = center
        self.radius = radius
        self.notch_width = math.radians(notch_width_deg)  # 缺口宽度,转换为弧度
        self.angular_speed = math.radians(angular_speed_deg_per_sec)  # 旋转速度,弧度/秒
        self.current_angle = 0  # 当前缺口角度

    def update(self, dt):
        """更新缺口的角度"""
        self.current_angle += self.angular_speed * dt
        self.current_angle %= 2 * math.pi  # 保持角度在0到2π之间

    def is_within_notch(self, angle):
        """判断给定角度是否在缺口区域内"""
        angle = angle % (2 * math.pi)
        start_angle = self.current_angle
        end_angle = (self.current_angle + self.notch_width) % (2 * math.pi)
        if start_angle < end_angle:
            return start_angle <= angle <= end_angle
        else:
            # 缺口跨越了0度
            return angle >= start_angle or angle <= end_angle

    def draw(self, surface):
        # 画大圆
        pygame.draw.circle(surface, BLACK, self.center, self.radius, 2)

        # 绘制缺口区域(用背景色填充)
        notch_points = []
        num_points = 30  # 缺口的平滑程度
        outer_radius = self.radius
        inner_radius = self.radius - 10  # 缺口的厚度

        # 计算缺口的外环点
        for i in range(num_points + 1):
            angle = self.current_angle + (self.notch_width * i) / num_points
            x = self.center[0] + outer_radius * math.cos(angle)
            y = self.center[1] + outer_radius * math.sin(angle)
            notch_points.append((x, y))

        # 计算缺口的内环点
        for i in range(num_points + 1):
            angle = self.current_angle + self.notch_width - (self.notch_width * i) / num_points
            x = self.center[0] + inner_radius * math.cos(angle)
            y = self.center[1] + inner_radius * math.sin(angle)
            notch_points.append((x, y))

        # 绘制缺口区域
        pygame.draw.polygon(surface, WHITE, notch_points)

# 小球类
class SmallCircle:
    def __init__(self, radius=10, color=RED, speed=4):
        self.radius = radius
        self.color = color
        self.speed = speed  # 固定速度大小
        self.pos = list(CENTER)  # 初始位置在大圆中心
        self.set_random_velocity()

    def set_random_velocity(self, incoming_angle=None, spread_deg=90):
        """
        为小球设置一个新的速度方向。
        如果传入了 `incoming_angle`,则新方向为 `incoming_angle + π ± spread_deg`。
        否则,设置为完全随机方向。
        """
        if incoming_angle is not None:
            # 计算反方向,并在其基础上随机偏离 ±spread_deg
            spread_rad = math.radians(spread_deg)
            min_angle = incoming_angle + math.pi - spread_rad
            max_angle = incoming_angle + math.pi + spread_rad
            min_angle %= 2 * math.pi
            max_angle %= 2 * math.pi
            if min_angle < max_angle:
                direction = random.uniform(min_angle, max_angle)
            else:
                # 角度范围跨越了0度
                direction = random.uniform(min_angle, 2 * math.pi) if random.random() < (
                            (2 * math.pi - min_angle) / (2 * math.pi - min_angle + max_angle)) else random.uniform(0, max_angle)
        else:
            # 完全随机方向
            direction = random.uniform(0, 2 * math.pi)

        self.vel = [
            self.speed * math.cos(direction),
            self.speed * math.sin(direction)
        ]

    def update(self, big_circles):
        """更新小球的位置,并处理与大圆的碰撞"""
        # 更新位置
        self.pos[0] += self.vel[0]
        self.pos[1] += self.vel[1]

        # 计算与大圆中心的距离
        dist = distance(self.pos, self.center())

        # 计算小球相对于大圆中心的角度
        angle = math.atan2(self.pos[1] - self.center()[1], self.pos[0] - self.center()[0])

        # 按照大圆半径从大到小排序
        sorted_big_circles = sorted(big_circles, key=lambda bc: bc.radius, reverse=True)

        for big_circle in sorted_big_circles:
            if dist + self.radius >= big_circle.radius:
                if big_circle.is_within_notch(angle):
                    # 通过缺口,重置小球位置
                    self.reset_position()
                    big_circles.remove(big_circle)
                else:
                    # 处理小球与大圆的碰撞反弹(方向随机)
                    incoming_angle = math.atan2(self.vel[1], self.vel[0])
                    self.set_random_velocity(incoming_angle, spread_deg=15)

                    # 调整位置避免卡入边界
                    overlap = dist + self.radius - big_circle.radius
                    normal = normalize([self.pos[0] - self.center()[0], self.pos[1] - self.center()[1]])
                    self.pos[0] -= overlap * normal[0]
                    self.pos[1] -= overlap * normal[1]
                break  # 处理完一个大圆后跳出循环

    def center(self):
        """返回大圆的中心位置"""
        return CENTER

    def reset_position(self):
        """重置小球到大圆中心,并设置新方向"""
        self.pos = list(CENTER)
        self.set_random_velocity()

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)

# 创建多个大圆实例
num_big_circles = 3  # 创建3个大圆
big_circles = []
radius_increment = (MAX_RADIUS - MIN_RADIUS_INIT) / num_big_circles
current_radius = MIN_RADIUS_INIT
for _ in range(num_big_circles):
    current_radius += radius_increment
    angular_speed = random.uniform(15, 45)  # 15到45度每秒
    notch_width_deg = random.uniform(20, 30)  # 缺口宽度在20到30度之间
    big_circle = BigCircle(center=CENTER, radius=current_radius, notch_width_deg=notch_width_deg, angular_speed_deg_per_sec=angular_speed)
    big_circles.append(big_circle)

# 按照半径从大到小排序,以确保较大的大圆先绘制
big_circles.sort(key=lambda bc: bc.radius, reverse=True)

# 创建多个小球
num_circles = 2  # 创建2个小球
colors = [RED, BLUE, GREEN]
small_circles = [
    SmallCircle(radius=10, color=random.choice(colors), speed=4)
    for _ in range(num_circles)
]

# 计时器设置
TOTAL_TIME = 100  # 总时间为100秒
remaining_time = TOTAL_TIME

# 游戏状态
state = "running"  # 可以是 "running", "victory", "failure"

# 代码延续自上一阶段

# 主循环
while True:
    dt = clock.tick(FPS) / 1000  # 获取时间增量(秒)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    if state == "running":
        # 更新计时器
        remaining_time -= dt
        if remaining_time <= 0:
            remaining_time = 0
            state = "failure"

        # 更新所有大圆
        for big_circle in big_circles:
            big_circle.update(dt)

        # 更新所有小球
        for circle in small_circles:
            circle.update(big_circles)

        # 检查是否所有大圆都被移除
        if len(big_circles) == 0:
            state = "victory"

    # 填充背景
    screen.fill(WHITE)

    if state == "running":
        # 画所有大圆及其缺口(从大到小绘制,以避免覆盖问题)
        for big_circle in big_circles:
            big_circle.draw(screen)

        # 画所有小球
        for circle in small_circles:
            circle.draw(screen)

        # 绘制计时器
        timer_text = font.render(f"剩余时间: {int(remaining_time)}s", True, BLACK)
        screen.blit(timer_text, (10, 10))

    elif state == "victory":
        # 绘制胜利画面
        victory_text = large_font.render("胜利!", True, GREEN)
        text_rect = victory_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        screen.blit(victory_text, text_rect)

        # 提示用户按下任意键退出
        prompt_text = font.render("按任意键退出", True, BLACK)
        prompt_rect = prompt_text.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        screen.blit(prompt_text, prompt_rect)

    elif state == "failure":
        # 绘制失败画面
        failure_text = large_font.render("失败!", True, RED)
        text_rect = failure_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        screen.blit(failure_text, text_rect)

        # 提示用户按下任意键退出
        prompt_text = font.render("按任意键退出", True, BLACK)
        prompt_rect = prompt_text.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        screen.blit(prompt_text, prompt_rect)

    # 如果游戏结束(胜利或失败),等待用户按键退出
    if state in ["victory", "failure"]:
        keys = pygame.key.get_pressed()
        if any(keys):
            pygame.quit()
            sys.exit()

    # 更新显示
    pygame.display.flip()