学号20242313 2024 - 2025 《Python 程序设计》实验 4 报告

学号20242313 2024 - 2025 《Python 程序设计》实验 4 报告

课程:《Python 程序设计》
班级: 2423
姓名: 曾海鹏
学号:20242313
实验教师:王志强
实验日期:2025 年 5 月 14 日
必修 / 选修: 公选课

1. 实验内容

(1)使用面向对象编程思想设计并实现一个完整的游戏程序 ——《球球大作战》。
(2)实现游戏的核心功能,包括玩家控制、AI 对手、碰撞检测、分数系统和复活机制。
(3)优化游戏体验,确保复活位置分散、大小适中、距离合理,避免初始碰撞导致的不公平性。
(4)将程序代码托管到码云,并撰写规范的实验报告。

2. 实验过程及结果

2.1 设计原因与分析设计

设计原因:

球球大作战是我最开始接触且第一次喜欢上的游戏,而且在学习编程的时候有一次在B站上刷到过一个up主的视频,主要内容是如何用C语言手搓球球大作战这款游戏,当时感觉很新奇很震撼,如今学习了python,也想手搓一款简易版的球球大作战。

分析设计:

先使用豆包给我一个编写好的代码,使我对编写游戏有一个整体框架,在测试代码实用性时对其进行改错和优化,遇到问题(比如python环境问题,安装路径问题等)可以先自己思考问题,再询问豆包,对于一些过于专业的内容依旧不懂的寻求学霸或者老师的帮助。

本次实验采用面向对象编程思想,将球球大作战组件抽象为以下类:
GameObject:基类,定义物体的基本属性(位置、大小、颜色)和方法(绘制、碰撞检测)。
Food:食物类,随机生成在地图上,模拟球球大作战中的彩豆。
AIPlayer:AI 控制的球,具备自主移动和追逐逻辑,主要角色为人机。
Player:玩家控制的球,支持键盘或鼠标控制,主要通过wasd或箭头上下左右来控制球球的移动。
Game:游戏主类,管理游戏流程、食物刷新和场景设计等。

2.2 核心功能实现

(1)玩家控制与移动
支持两种控制方式:
键盘控制:使用方向键或 WASD 移动。
鼠标控制:按 M 键切换,玩家自动向鼠标指针方向移动。
(2)碰撞检测与分数系统
玩家与食物碰撞:玩家吞噬食物后变大并获得分数。
玩家与 AI 碰撞:若玩家尺寸大于 AI 的 1.2 倍,可吞噬 AI;反之则被吞噬(玩家死亡)。
AI 之间的碰撞:大球吃小球,符合常理。
(3)复活机制优化
预生成 10 个分散的复活点,确保彼此距离≥300 像素。
复活时随机选择未使用的位置,避免扎堆。
新生成的 AI 球大小限制在 15-25 之间,防止初始尺寸过大导致不公平。

2.3 主要设计过程

(1)pygame的安装和python环境的搭建

首先需要安装必要的库pygame,要求我们在终端pip install pygame
而在此过程中可能会遇到pip 版本过低为 21.1.1,而最新版本是 25.0.1,建议升级 pip 以获取最新功能和安全补丁。
此时需要利用在Python 安装路径下的解释器来执行 pip 升级,在终端使用c:\program files\python38\python.exe -m pip install --upgrade pip来实现。
但PowerShell 可能无法识别c:\program\files\python38\python.exe这个路径,这就需要在 Windows 中添加路径到 PATH。通过以下步骤实现:

  1. 右键点击 "此电脑" → 选择 "属性" → 点击 "高级系统设置"
  2. 在弹出的窗口中,点击 "环境变量" 按钮
  3. "系统变量"列表中,找到"Path"变量 → 点击"编辑"
  4. 点击 "新建" → 复制粘贴路径: C:\Users\33834\AppData\Roaming\Python\Python38\Scripts

但安装好了pygame库,可能PyCharm 仍然无法找到已安装的pygame库,这通常是因为 PyCharm 使用的 Python 环境与安装 pygame 的环境不一致。这就需要确认 PyCharm 使用的 Python 环境,并检查 pygame 是否安装在该环境中,最后在 PyCharm 中手动安装 pygame即可。

  1. Python Interpreter面板中,点击右侧的+按钮
  2. 在搜索框中输入pygame
  3. 选择最新版本(例如 2.6.1),然后点击Install Package

在我的python中出现如下版本表示成功:

(2)代码的改正和优化

1.主要问题:在创建FoodAIPlayer类对象时出现了AttributeError: 'Food' object has no attribute 'size'的问题。

原因分析: 尝试时使用self.size作为随机位置的边界,但此时self.size还没有被设置(因为super().__init__还未执行)。
解决方案:
在调用父类构造函数之前,先初始化size属性:
代码修改后:

# 定义食物类,继承自游戏物体基类
class Food(GameObject):
    def __init__(self):
        # 先初始化size,再调用父类构造函数
        size = random.randint(FOOD_SIZE_MIN, FOOD_SIZE_MAX)
        color = YELLOW  # 食物统一为黄色
        
        # 随机生成食物位置(使用已初始化的size)
        x = random.randint(size, WIDTH - size)
        y = random.randint(size, HEIGHT - size)
        
        # 调用父类构造函数
        super().__init__(x, y, size, color)
        
# 定义AI控制的球类,继承自游戏物体基类
class AIPlayer(GameObject):
    def __init__(self):
        # 先初始化size,再调用父类构造函数
        size = random.randint(AI_SIZE_MIN, AI_SIZE_MAX)
        color = random.choice(COLORS)  # 随机颜色
        
        # 随机生成AI球位置(使用已初始化的size)
        x = random.randint(size, WIDTH - size)
        y = random.randint(size, HEIGHT - size)
        
        # 调用父类构造函数
        super().__init__(x, y, size, color)
        self.speed = max(1, size / 10)  # 大小影响速度,越大越慢

2.优化方面:

主要在原来代码基础上将球球的运动空间设置的大一些,最好超出屏幕,设置球球的AI玩家大球能够吃掉小球,死亡后可以复活为小球。
运动空间设置方面:修改WIDTH, HEIGHT = 800, 600改为2600,2600大小即可。
AI玩家方面:改进 AIPlayer 类,让 AI 球更智能,大球追逐小球,小球躲避大球。

复活方面:添加初始化代码,更新AI球位置并显示死亡提示,其他基本保持不变。
优化时常见的问题主要是使用方向键用不了,
我个人认为以下这段代码是解决问题最为关键的:
它移除静态方法装饰器,保留实例方法,但确保不访问实例属性。

class Game:
    def __init__(self):
        self.clock = pygame.time.Clock()
        self.running = True
        self.paused = False
        self.player = Player()
        self.food = [Food() for _ in range(30)]
        self.ai_players = [AIPlayer() for _ in range(10)]
        self.food_spawn_timer = 0
        self.ai_spawn_timer = 0
        self.control_mode = "keyboard"
        self.death_timer = 0
        self.player_alive = True
        self.respawn_points = self._generate_respawn_points(10)  # 正确调用
        self.used_respawns = set()  # 记录已使用的复活点
    
    def _generate_respawn_points(self, count):
        """生成一组分散的复活点"""
        points = []
        for _ in range(count):
            max_attempts = 100
            for _ in range(max_attempts):
                x = random.randint(RESPAWN_AREA_MARGIN, WORLD_WIDTH - RESPAWN_AREA_MARGIN)
                y = random.randint(RESPAWN_AREA_MARGIN, WORLD_HEIGHT - RESPAWN_AREA_MARGIN)
                
                if all(math.sqrt((x-p[0])**2 + (y-p[1])** 2) > RESPAWN_MIN_DISTANCE for p in points):
                    points.append((x, y))
                    break
        return points

2.4 源代码实现

以下是游戏的初始代码:

import pygame  
import random  
import math  
import sys  
  
# 初始化pygame  
pygame.init()  
  
# 确保中文显示正常  
pygame.font.init()  
font_path = pygame.font.match_font('simsun')  # 使用系统中的宋体  
if not font_path:  
    font_path = pygame.font.get_default_font()  # 如果没有找到宋体,使用默认字体  
font = pygame.font.Font(font_path, 24)  
  
# 游戏窗口设置  
WIDTH, HEIGHT =2600,2600  
screen = pygame.display.set_mode((WIDTH, HEIGHT))  
pygame.display.set_caption("球球大作战 - 简化版")  
  
# 颜色定义  
WHITE = (255, 255, 255)  
BLACK = (0, 0, 0)  
RED = (255, 0, 0)  
GREEN = (0, 255, 0)  
BLUE = (0, 0, 255)  
YELLOW = (255, 255, 0)  
COLORS = [RED, GREEN, BLUE, YELLOW, (255, 165, 0), (128, 0, 128), (0, 128, 128)]  
  
# 游戏常量  
FPS = 60  
PLAYER_INIT_SIZE = 20  # 玩家初始大小  
PLAYER_SPEED = 5  # 玩家移动速度  
FOOD_SIZE_MIN = 5  # 食物最小大小  
FOOD_SIZE_MAX = 15  # 食物最大大小  
AI_SIZE_MIN = 15  # AI球最小大小  
AI_SIZE_MAX = 40  # AI球最大大小  
FOOD_SPAWN_RATE = 50  # 食物生成速率  
AI_SPAWN_RATE = 200  # AI球生成速率  
SCORE_PER_FOOD = 1  # 每个食物的分数  
SCORE_PER_AI = 5  # 每个AI球的分数  
  
  
# 定义游戏物体基类  
class GameObject:  
    def __init__(self, x, y, size, color):  
        self.x = x  # x坐标  
        self.y = y  # y坐标  
        self.size = size  # 物体大小(半径)  
        self.color = color  # 物体颜色  
  
    def draw(self, surface):  
        """在指定表面绘制物体"""  
        pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), self.size)  
  
    def is_colliding_with(self, other):  
        """检测与另一个物体是否碰撞"""  
        distance = math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)  
        return distance < (self.size + other.size)  
  
  
# 定义食物类,继承自游戏物体基类  
class Food(GameObject):  
    def __init__(self):  
        # 先初始化size,再调用父类构造函数  
        size = random.randint(FOOD_SIZE_MIN, FOOD_SIZE_MAX)  
        color = YELLOW  # 食物统一为黄色  
  
        # 随机生成食物位置(使用已初始化的size)  
        x = random.randint(size, WIDTH - size)  
        y = random.randint(size, HEIGHT - size)  
  
        # 调用父类构造函数  
        super().__init__(x, y, size, color)  
  
  
# 定义AI控制的球类,继承自游戏物体基类  
class AIPlayer(GameObject):  
    def __init__(self):  
        # 先初始化size,再调用父类构造函数  
        size = random.randint(AI_SIZE_MIN, AI_SIZE_MAX)  
        color = random.choice(COLORS)  # 随机颜色  
  
        # 随机生成AI球位置(使用已初始化的size)  
        x = random.randint(size, WIDTH - size)  
        y = random.randint(size, HEIGHT - size)  
  
        # 调用父类构造函数  
        super().__init__(x, y, size, color)  
        self.speed = max(1, size / 10)  # 大小影响速度,越大越慢  
  
    def move_towards(self, target_x, target_y):  
        """向目标位置移动"""  
        # 计算方向向量  
        dx = target_x - self.x  
        dy = target_y - self.y  
        distance = math.sqrt(dx ** 2 + dy ** 2)  
  
        if distance > 0:  
            # 标准化方向向量并乘以速度  
            self.x += (dx / distance) * self.speed  
            self.y += (dy / distance) * self.speed  
  
        # 边界检测,防止AI球移出屏幕  
        self.x = max(self.size, min(self.x, WIDTH - self.size))  
        self.y = max(self.size, min(self.y, HEIGHT - self.size))  
  
  
# 定义玩家控制的球类,继承自游戏物体基类  
class Player(GameObject):  
    def __init__(self):  
        # 玩家初始位置在屏幕中心  
        x = WIDTH // 2  
        y = HEIGHT // 2  
        size = PLAYER_INIT_SIZE  
        color = BLUE  # 玩家球为蓝色  
        super().__init__(x, y, size, color)  
        self.score = 0  # 玩家分数  
        self.speed = PLAYER_SPEED  # 玩家移动速度  
  
    def move_with_keyboard(self, keys):  
        """通过键盘控制移动"""  
        dx, dy = 0, 0  
  
        if keys[pygame.K_w] or keys[pygame.K_UP]:  
            dy -= self.speed  
        if keys[pygame.K_s] or keys[pygame.K_DOWN]:  
            dy += self.speed  
        if keys[pygame.K_a] or keys[pygame.K_LEFT]:  
            dx -= self.speed  
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:  
            dx += self.speed  
  
        # 更新位置  
        self.x += dx  
        self.y += dy  
  
        # 边界检测,防止玩家球移出屏幕  
        self.x = max(self.size, min(self.x, WIDTH - self.size))  
        self.y = max(self.size, min(self.y, HEIGHT - self.size))  
  
    def move_with_mouse(self, mouse_x, mouse_y):  
        """通过鼠标控制移动"""  
        # 计算方向向量  
        dx = mouse_x - self.x  
        dy = mouse_y - self.y  
        distance = math.sqrt(dx ** 2 + dy ** 2)  
  
        if distance > 0:  
            # 标准化方向向量并乘以速度  
            self.x += (dx / distance) * self.speed  
            self.y += (dy / distance) * self.speed  
  
        # 边界检测  
        self.x = max(self.size, min(self.x, WIDTH - self.size))  
        self.y = max(self.size, min(self.y, HEIGHT - self.size))  
  
    def eat(self, other):  
        """吞噬其他物体"""  
        if self.size > other.size:  
            # 计算吞噬后增加的大小(基于被吞噬物体的大小)  
            size_increase = other.size * 0.5  
            self.size += size_increase  
  
            # 根据被吞噬物体类型增加分数  
            if isinstance(other, Food):  
                self.score += SCORE_PER_FOOD  
            elif isinstance(other, AIPlayer):  
                self.score += SCORE_PER_AI  
  
            return True  
        return False  
  
# 游戏主类  
class Game:  
    def __init__(self):  
        self.clock = pygame.time.Clock()  
        self.running = True  
        self.paused = False  
        self.player = Player()  
        self.food = [Food() for _ in range(10)]  # 初始生成10个食物  
        self.ai_players = [AIPlayer() for _ in range(5)]  # 初始生成5个AI球  
        self.food_spawn_timer = 0  
        self.ai_spawn_timer = 0  
        self.control_mode = "keyboard"  # 控制模式:keyboard或mouse  
  
    def process_events(self):  
        """处理游戏事件"""  
        for event in pygame.event.get():  
            if event.type == pygame.QUIT:  
                self.running = False  
            elif event.type == pygame.KEYDOWN:  
                if event.key == pygame.K_ESCAPE:  
                    self.running = False  
                elif event.key == pygame.K_SPACE:  
                    self.paused = not self.paused  
                elif event.key == pygame.K_m:  
                    self.control_mode = "mouse" if self.control_mode == "keyboard" else "keyboard"  
  
    def update(self):  
        """更新游戏状态"""  
        if self.paused:  
            return  
  
        # 根据控制模式移动玩家  
        if self.control_mode == "keyboard":  
            keys = pygame.key.get_pressed()  
            self.player.move_with_keyboard(keys)  
        else:  
            mouse_x, mouse_y = pygame.mouse.get_pos()  
            self.player.move_with_mouse(mouse_x, mouse_y)  
  
        # 更新AI球位置(AI球会朝向玩家移动)  
        for ai in self.ai_players:  
            ai.move_towards(self.player.x, self.player.y)  
  
        # 食物生成逻辑  
        self.food_spawn_timer += 1  
        if self.food_spawn_timer >= FOOD_SPAWN_RATE:  
            self.food.append(Food())  
            self.food_spawn_timer = 0  
  
        # AI球生成逻辑  
        self.ai_spawn_timer += 1  
        if self.ai_spawn_timer >= AI_SPAWN_RATE:  
            self.ai_players.append(AIPlayer())  
            self.ai_spawn_timer = 0  
  
        # 碰撞检测:玩家吞噬食物  
        food_to_remove = []  
        for food_item in self.food:  
            if self.player.is_colliding_with(food_item):  
                if self.player.eat(food_item):  
                    food_to_remove.append(food_item)  
        for item in food_to_remove:  
            self.food.remove(item)  
  
        # 碰撞检测:玩家吞噬AI球  
        ai_to_remove = []  
        for ai_item in self.ai_players:  
            if self.player.is_colliding_with(ai_item):  
                if self.player.eat(ai_item):  
                    ai_to_remove.append(ai_item)  
        for item in ai_to_remove:  
            self.ai_players.remove(item)  
  
            # 碰撞检测:AI球之间的互动(简单逻辑:大AI球吞噬小AI球)  
            ai_to_remove = []  # 记录需要移除的AI球  
  
            # 使用副本进行循环,避免修改原始列表  
            for i in range(len(self.ai_players)):  
                for j in range(i + 1, len(self.ai_players)):  
                    # 检查索引是否仍然有效  
                    if i >= len(self.ai_players) or j >= len(self.ai_players):  
                        break 
                    ai1 = self.ai_players[i]  
                    ai2 = self.ai_players[j]  
                    if ai1.is_colliding_with(ai2):  
                        if ai1.size > ai2.size and ai2 not in ai_to_remove:  
                            ai1.size += ai2.size * 0.3  
                            ai_to_remove.append(ai2)  # 标记为移除  
                        elif ai2.size > ai1.size and ai1 not in ai_to_remove:  
                            ai2.size += ai1.size * 0.3  
                            ai_to_remove.append(ai1)  # 标记为移除  
            # 统一移除标记的AI球  
            for ai in ai_to_remove:  
                if ai in self.ai_players:  # 确保元素还在列表中  
                    self.ai_players.remove(ai)  
    def render(self):  
        """渲染游戏画面"""  
        screen.fill(WHILE)  # 清屏为黑色 
        # 绘制所有食物  
        for food_item in self.food:  
            food_item.draw(screen)  
  
        # 绘制所有AI球  
        for ai_item in self.ai_players:  
            ai_item.draw(screen)  
  
        # 绘制玩家球  
        self.player.draw(screen)  
        # 绘制分数和游戏信息  
        score_text = font.render(f"分数: {self.player.score}", True, WHITE)  
        screen.blit(score_text, (10, 10))  
        size_text = font.render(f"大小: {int(self.player.size)}", True, WHITE)  
        screen.blit(size_text, (10, 40))  
        control_text = font.render(f"控制方式: {'鼠标' if self.control_mode == 'mouse' else '键盘'} (按M切换)", True,  
                                   WHITE)  
        screen.blit(control_text, (10, 70))  
        pause_text = font.render(f"游戏状态: {'已暂停' if self.paused else '进行中'} (按空格暂停)", True, WHITE)  
        screen.blit(pause_text, (10, 100))  
        pygame.display.flip()  # 更新显示  
    def run(self):  
        """运行游戏主循环"""  
        while self.running:  
            self.process_events()  
            self.update()  
            self.render()  
            self.clock.tick(FPS)  # 控制帧率  
# 程序入口  
if __name__ == "__main__":  
    game = Game()  
    game.run()  
    pygame.quit()  
    sys.exit()

之后是游戏的进阶代码:(能够复活,大范围活动,AI玩家可以大球吃小球等)

import pygame  
import random  
import math  
import sys  
  
# 初始化pygame  
pygame.init()  
  
# 确保中文显示正常  
pygame.font.init()  
font_path = pygame.font.match_font('simsun')  # 使用系统中的宋体  
if not font_path:  
    font_path = pygame.font.get_default_font()  # 如果没有找到宋体,使用默认字体  
font = pygame.font.Font(font_path, 24)  
  
# 游戏窗口设置  
WINDOW_WIDTH, WINDOW_HEIGHT = 1200, 800  # 实际窗口大小  
WORLD_WIDTH, WORLD_HEIGHT = 5000, 5000  # 更大的游戏世界(超出屏幕)  
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))  
pygame.display.set_caption("球球大作战 - 增强版")  
  
# 颜色定义  
WHITE = (255, 255, 255)  
BLACK = (0, 0, 0)  
RED = (255, 0, 0)  
GREEN = (0, 255, 0)  
BLUE = (0, 0, 255)  
YELLOW = (255, 255, 0)  
COLORS = [RED, GREEN, BLUE, YELLOW, (255, 165, 0), (128, 0, 128), (0, 128, 128)]  
  
# 游戏常量  
FPS = 60  
PLAYER_INIT_SIZE = 20  # 玩家初始大小  
PLAYER_SPEED = 5  # 玩家移动速度  
FOOD_SIZE_MIN = 5  # 食物最小大小  
FOOD_SIZE_MAX = 15  # 食物最大大小  
AI_SIZE_MIN = 15  # AI球最小大小  
AI_SIZE_MAX = 40  # AI球最大大小  
FOOD_SPAWN_RATE = 50  # 食物生成速率  
AI_SPAWN_RATE = 200  # AI球生成速率  
SCORE_PER_FOOD = 1  # 每个食物的分数  
SCORE_PER_AI = 5  # 每个AI球的分数  
RESPAWN_TIME = 120  # 复活时间(帧数)  
RESPAWN_MIN_DISTANCE = 300  # 复活点之间的最小距离  
RESPAWN_AREA_MARGIN = 50  # 复活区域边缘留白  
  
  
# 定义游戏物体基类  
class GameObject:  
    def __init__(self, x, y, size, color):  
        self.x = x  # x坐标  
        self.y = y  # y坐标  
        self.size = size  # 物体大小(半径)  
        self.color = color  # 物体颜色  
  
    def is_on_screen(self, camera_x, camera_y):  
        """检查物体是否在屏幕范围内"""  
        return (self.x + self.size > camera_x and self.x - self.size < camera_x + WINDOW_WIDTH and  
                self.y + self.size > camera_y and self.y - self.size < camera_y + WINDOW_HEIGHT)  
  
    def draw(self, surface, camera_x, camera_y):  
        """只在屏幕范围内绘制物体"""  
        if self.is_on_screen(camera_x, camera_y):  
            # 计算在屏幕上的相对位置  
            screen_x = self.x - camera_x  
            screen_y = self.y - camera_y  
            pygame.draw.circle(surface, self.color, (int(screen_x), int(screen_y)), self.size)  
  
    def is_colliding_with(self, other):  
        """检测与另一个物体是否碰撞"""  
        distance = math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)  
        return distance < (self.size + other.size)  
  
  
# 定义食物类  
class Food(GameObject):  
    def __init__(self):  
        size = random.randint(FOOD_SIZE_MIN, FOOD_SIZE_MAX)  
        color = YELLOW  
        x = random.randint(size, WORLD_WIDTH - size)  
        y = random.randint(size, WORLD_HEIGHT - size)  
        super().__init__(x, y, size, color)  
  
  
# 定义AI控制的球类  
class AIPlayer(GameObject):  
    def __init__(self):  
        size = random.randint(AI_SIZE_MIN, AI_SIZE_MIN + 10)  # 限制初始大小  
        color = random.choice(COLORS)  
        x = random.randint(size, WORLD_WIDTH - size)  
        y = random.randint(size, WORLD_HEIGHT - size)  
        super().__init__(x, y, size, color)  
        self.speed = max(1, 5 - size / 10)  # 小球更快,大球更慢  
  
    def move(self, player, ai_players, food_list):  
        # 寻找最近的目标  
        nearest_target = None  
        nearest_distance = float('inf')  
  
        # 优先追逐比自己小的AI球  
        for ai in ai_players:  
            if ai.size < self.size * 0.9:  # 只追逐明显更小的球  
                distance = math.sqrt((self.x - ai.x) ** 2 + (self.y - ai.y) ** 2)  
                if distance < nearest_distance:  
                    nearest_distance = distance  
                    nearest_target = ai  
  
        # 如果没有更小的AI球,考虑食物  
        if nearest_target is None:  
            for food in food_list:  
                distance = math.sqrt((self.x - food.x) ** 2 + (self.y - food.y) ** 2)  
                if distance < nearest_distance:  
                    nearest_distance = distance  
                    nearest_target = food  
  
        # 如果没有食物,考虑玩家  
        if nearest_target is None and player.size < self.size * 0.9:  
            nearest_target = player  
  
        # 如果有目标,向目标移动  
        if nearest_target:  
            dx = nearest_target.x - self.x  
            dy = nearest_target.y - self.y  
            distance = math.sqrt(dx ** 2 + dy ** 2)  
  
            if distance > 0:  
                self.x += (dx / distance) * self.speed  
                self.y += (dy / distance) * self.speed  
  
        # 边界检测  
        self.x = max(self.size, min(self.x, WORLD_WIDTH - self.size))  
        self.y = max(self.size, min(self.y, WORLD_HEIGHT - self.size))  
  
  
# 定义玩家控制的球类  
class Player(GameObject):  
    def __init__(self, x=None, y=None):  
        # 如果没有指定位置,使用屏幕中心  
        if x is None:  
            x = WORLD_WIDTH // 2  
        if y is None:  
            y = WORLD_HEIGHT // 2  
  
        super().__init__(x, y, PLAYER_INIT_SIZE, BLUE)  
        self.score = 0  
        self.speed = PLAYER_SPEED  
  
    def move_with_keyboard(self, keys):  
        dx, dy = 0, 0  
  
        if keys[pygame.K_w] or keys[pygame.K_UP]:  
            dy -= self.speed  
        if keys[pygame.K_s] or keys[pygame.K_DOWN]:  
            dy += self.speed  
        if keys[pygame.K_a] or keys[pygame.K_LEFT]:  
            dx -= self.speed  
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:  
            dx += self.speed  
  
        self.x += dx  
        self.y += dy  
  
        self.x = max(self.size, min(self.x, WORLD_WIDTH - self.size))  
        self.y = max(self.size, min(self.y, WORLD_HEIGHT - self.size))  
  
    def move_with_mouse(self, mouse_x, mouse_y, camera_x, camera_y):  
        # 计算世界坐标中的鼠标位置  
        world_x = mouse_x + camera_x  
        world_y = mouse_y + camera_y  
  
        dx = world_x - self.x  
        dy = world_y - self.y  
        distance = math.sqrt(dx ** 2 + dy ** 2)  
  
        if distance > 0:  
            self.x += (dx / distance) * self.speed  
            self.y += (dy / distance) * self.speed  
  
        self.x = max(self.size, min(self.x, WORLD_WIDTH - self.size))  
        self.y = max(self.size, min(self.y, WORLD_HEIGHT - self.size))  
  
    def eat(self, other):  
        if self.size > other.size:  
            size_increase = other.size * 0.5  
            self.size += size_increase  
  
            if isinstance(other, Food):  
                self.score += SCORE_PER_FOOD  
            elif isinstance(other, AIPlayer):  
                self.score += SCORE_PER_AI  
  
            return True  
        return False  
  
# 游戏主类  
class Game:  
    def __init__(self):  
        self.clock = pygame.time.Clock()  
        self.running = True  
        self.paused = False  
        self.player = Player()  
        self.food = [Food() for _ in range(30)]  
        self.ai_players = [AIPlayer() for _ in range(10)]  
        self.food_spawn_timer = 0  
        self.ai_spawn_timer = 0  
        self.control_mode = "keyboard"  
        self.death_timer = 0  
        self.player_alive = True  
        self.respawn_points = self._generate_respawn_points(10)  # 正确调用  
        self.used_respawns = set()  # 记录已使用的复活点  
  
    def _generate_respawn_points(self, count):  
        """生成一组分散的复活点"""  
        points = []  
        for _ in range(count):  
            max_attempts = 100  
            for _ in range(max_attempts):  
                x = random.randint(RESPAWN_AREA_MARGIN, WORLD_WIDTH - RESPAWN_AREA_MARGIN)  
                y = random.randint(RESPAWN_AREA_MARGIN, WORLD_HEIGHT - RESPAWN_AREA_MARGIN)  
  
                if all(math.sqrt((x - p[0]) ** 2 + (y - p[1]) ** 2) > RESPAWN_MIN_DISTANCE for p in points):  
                    points.append((x, y))  
                    break  
        return points  
  
    def _get_respawn_position(self):  
        """获取一个未被使用的复活点位置"""  
        # 如果所有复活点都被使用了,清空记录  
        if len(self.used_respawns) >= len(self.respawn_points):  
            self.used_respawns.clear()  
  
        # 随机选择一个未被使用的复活点  
        available = [i for i in range(len(self.respawn_points)) if i not in self.used_respawns]  
        index = random.choice(available)  
        self.used_respawns.add(index)  
        return self.respawn_points[index]  
  
    def process_events(self):  
        for event in pygame.event.get():  
            if event.type == pygame.QUIT:  
                self.running = False  
            elif event.type == pygame.KEYDOWN:  
                if event.key == pygame.K_ESCAPE:  
                    self.running = False  
                elif event.key == pygame.K_SPACE:  
                    self.paused = not self.paused  
                elif event.key == pygame.K_m:  
                    self.control_mode = "mouse" if self.control_mode == "keyboard" else "keyboard"  
  
    def update(self):  
        if self.paused:  
            return  
  
        if not self.player_alive:  
            self.death_timer += 1  
            if self.death_timer >= RESPAWN_TIME:  
                # 从复活点生成新玩家  
                x, y = self._get_respawn_position()  
                self.player = Player(x, y)  
                self.player_alive = True  
                self.death_timer = 0  
            return  
  
        # 根据控制模式移动玩家  
        if self.control_mode == "keyboard":  
            keys = pygame.key.get_pressed()  
            self.player.move_with_keyboard(keys)  
        else:  
            mouse_x, mouse_y = pygame.mouse.get_pos()  
            camera_x = max(0, min(WORLD_WIDTH - WINDOW_WIDTH, self.player.x - WINDOW_WIDTH // 2))  
            camera_y = max(0, min(WORLD_HEIGHT - WINDOW_HEIGHT, self.player.y - WINDOW_HEIGHT // 2))  
            self.player.move_with_mouse(mouse_x, mouse_y, camera_x, camera_y)  
  
        # 更新AI球位置  
        for ai in self.ai_players[:]:  
            ai.move(self.player, self.ai_players, self.food)  
  
        # 食物生成逻辑  
        self.food_spawn_timer += 1  
        if self.food_spawn_timer >= FOOD_SPAWN_RATE:  
            self.food.append(Food())  
            self.food_spawn_timer = 0  
  
        # AI球生成逻辑  
        self.ai_spawn_timer += 1  
        if self.ai_spawn_timer >= AI_SPAWN_RATE:  
            # 从复活点生成新AI球  
            x, y = self._get_respawn_position()  
            new_ai = AIPlayer()  
            new_ai.x, new_ai.y = x, y  
            self.ai_players.append(new_ai)  
            self.ai_spawn_timer = 0  
  
        # 碰撞检测:玩家吞噬食物  
        food_to_remove = []  
        for food_item in self.food:  
            if self.player.is_colliding_with(food_item):  
                if self.player.eat(food_item):  
                    food_to_remove.append(food_item)  
        for item in food_to_remove:  
            self.food.remove(item)  
  
        # 碰撞检测:玩家与AI球  
        for ai in self.ai_players[:]:  
            if self.player.is_colliding_with(ai):  
                if self.player.size > ai.size:  
                    self.player.eat(ai)  
                    self.ai_players.remove(ai)  
                    # 从复活点生成新AI球  
                    x, y = self._get_respawn_position()  
                    new_ai = AIPlayer()  
                    new_ai.x, new_ai.y = x, y  
                    self.ai_players.append(new_ai)  
                elif ai.size > self.player.size * 1.2:  
                    # 玩家死亡  
                    self.player_alive = False  
  
        # AI球之间的碰撞  
        ai_to_remove = []  
        for i in range(len(self.ai_players)):  
            for j in range(i + 1, len(self.ai_players)):  
                if i >= len(self.ai_players) or j >= len(self.ai_players):  
                    break  
  
                ai1 = self.ai_players[i]  
                ai2 = self.ai_players[j]  
  
                if ai1.is_colliding_with(ai2):  
                    if ai1.size > ai2.size * 1.1:  
                        ai1.size += ai2.size * 0.2  
                        ai_to_remove.append(ai2)  
                    elif ai2.size > ai1.size * 1.1:  
                        ai2.size += ai1.size * 0.2  
                        ai_to_remove.append(ai1)  
  
        # 移除被吃掉的AI球,并生成新的  
        for ai in ai_to_remove:  
            if ai in self.ai_players:  
                self.ai_players.remove(ai)  
                # 从复活点生成新AI球  
                x, y = self._get_respawn_position()  
                new_ai = AIPlayer()  
                new_ai.x, new_ai.y = x, y  
                self.ai_players.append(new_ai)  
  
    def render(self):  
        screen.fill(BLACK)  
  
        # 计算相机偏移(跟随玩家)  
        camera_x = max(0,  
                       min(WORLD_WIDTH - WINDOW_WIDTH, self.player.x - WINDOW_WIDTH // 2)) if self.player_alive else 0  
        camera_y = max(0, min(WORLD_HEIGHT - WINDOW_HEIGHT,  
                              self.player.y - WINDOW_HEIGHT // 2)) if self.player_alive else 0  
  
        # 绘制所有食物  
        for food_item in self.food:  
            if food_item.is_on_screen(camera_x, camera_y):  
                food_item.draw(screen, camera_x, camera_y)  
  
        # 绘制所有AI球  
        for ai_item in self.ai_players:  
            if ai_item.is_on_screen(camera_x, camera_y):  
                ai_item.draw(screen, camera_x, camera_y)  
  
        # 绘制玩家  
        if self.player_alive:  
            self.player.draw(screen, camera_x, camera_y)  
        else:  
            # 显示死亡提示  
            death_text = font.render("你已死亡,即将复活...", True, WHITE)  
            screen.blit(death_text, (WINDOW_WIDTH // 2 - death_text.get_width() // 2,  
                                     WINDOW_HEIGHT // 2 - death_text.get_height() // 2))  
  
        # 绘制分数和游戏信息  
        score_text = font.render(f"分数: {self.player.score if self.player_alive else 0}", True, WHITE)  
        screen.blit(score_text, (10, 10))  
  
        size_text = font.render(f"大小: {int(self.player.size) if self.player_alive else 0}", True, WHITE)  
        screen.blit(size_text, (10, 40))  
  
        control_text = font.render(f"控制方式: {'鼠标' if self.control_mode == 'mouse' else '键盘'} (按M切换)", True,  
                                   WHITE)  
        screen.blit(control_text, (10, 70))  
  
        pause_text = font.render(f"游戏状态: {'已暂停' if self.paused else '进行中'} (按空格暂停)", True, WHITE)  
        screen.blit(pause_text, (10, 100))  
  
        # 绘制世界边界  
        pygame.draw.rect(screen, WHITE,  
                         (max(0, -camera_x), max(0, -camera_y),  
                          min(WINDOW_WIDTH, WORLD_WIDTH), min(WINDOW_HEIGHT, WORLD_HEIGHT)), 2)  
  
        pygame.display.flip()  
  
    def run(self):  
        while self.running:  
            self.process_events()  
            self.update()  
            self.render()  
            self.clock.tick(FPS)  
  
  
# 程序入口  
if __name__ == "__main__":  
    game = Game()  
    game.run()  
    pygame.quit()  
    sys.exit()

2.4实验知识点分析

通过本次实验,我主要学习了面向对象编程,列表集合以及调用一些函数,如random.randint(),print()等等,还有一些专业的知识如游戏循环碰撞检测用户交互等,虽然没有那么熟练,但是有所收获。

2.5 测试结果

(1)功能测试
启动游戏后,玩家可通过键盘或鼠标控制球体移动。
成功吞噬食物和 AI 球后,玩家尺寸增大,分数增加。
被更大的 AI 球吞噬后,玩家会在随机位置复活,且新生成的 AI 球尺寸适中。
(2)复活机制测试
通过调试模式观察复活点分布(按 D 键显示):
复活点均匀分布在地图上,彼此距离较远。
玩家和 AI 复活时会选择未被使用的位置,有效避免了扎堆现象。
测试截图

3. 实验过程中遇到的问题和解决过程

  1. 问题 1:游戏无法移动
    原因FoodAIPlayer类初始化时使用了未定义的self.size属性。
    解决方案:在调用父类构造函数前先初始化size,并使用局部变量计算位置。
  2. 问题 2:复活位置扎堆,体验不佳
    原因:复活点随机生成,未考虑与其他物体的距离。
    解决方案
    预生成一组分散的复活点,确保彼此距离≥300 像素。
    使用记录系统避免重复使用同一复活点。
  3. 问题 3:初始 AI 球尺寸过大,导致玩家易死亡
    原因:AI 球生成时随机范围过大(15-40)。
    解决方案:限制复活的 AI 球尺寸在 15-25 之间,降低初始难度。

4. 代码托管

将代码推送到码云仓库:
https://gitee.com/jame-zhp_0/pythonhomework/blob/e14eb2c421dc6f4bd3d75f5688100aa3b08035c7/.idea/qiqiu2.py
https://gitee.com/jame-zhp_0/pythonhomework/blob/e14eb2c421dc6f4bd3d75f5688100aa3b08035c7/.idea/qiuqiu.py

5. 参考资料

  1. 《Python 游戏编程入门》(作者:Mike Dawson)
  2. Pygame 官方文档:https://www.pygame.org/docs/
  3. Cryptography 模块文档:https://pycryptodome.readthedocs.io/
  4. AI人工智能体豆包。
posted @ 2025-05-29 00:49  20242313曾海鹏  阅读(42)  评论(0)    收藏  举报