20241123实验四 《Python程序设计》实验报告

20241123 2024-2025-2 《Python程序设计》实验四报告

课程:《Python程序设计》
班级: 2411
姓名: 胡钧涛
学号:20241123
实验教师:王志强
实验日期:2025年5月14日
必修/选修: 公选课

实验前言(设计原因)

小的时候也许大家都玩过诸如俄罗斯方块、贪吃蛇、推箱子之类的小游戏,而现在我学习了python之后,第一个想法就是以这些小游戏为灵感,在这基础上再加上一些自己的设计,所以这次的实验四我就以推箱子这款小游戏为模板,开始了自己的编码旅程(狗头)

实验内容

先简略编写出基层代码,实现对这款游戏的初步设计,其次,引入自己喜欢的人物图片作为游戏主人公,并在里面加入自己想要的游戏特效

实验过程(相应代码内容和设计步骤)

一安装所需要的平台

使用python写游戏,有一个必定绕不开的环节,那就是pygame安装,它是一个专门用于开发 2D 游戏和多媒体应用的 Python 库,提供了一系列丰富的功能模块,让开发者可以轻松实现图形渲染、音频处理、用户输入等游戏开发所需的核心功能,所以我们首先要下载安装pygame

推箱子游戏代码

对函数的引入和初始化

import pygame
import sys
import json
import os
from typing import Dict, Tuple, List, Optional, Any
import random

# 初始化 Pygame
pygame.init()

对游戏底层功能的实现

# 确保中文正常显示
pygame.font.init()
font = pygame.font.SysFont(['SimHei', 'WenQuanYi Micro Hei', 'Heiti TC'], 36)
small_font = pygame.font.SysFont(['SimHei', 'WenQuanYi Micro Hei', 'Heiti TC'], 24)
tiny_font = pygame.font.SysFont(['SimHei', 'WenQuanYi Micro Hei', 'Heiti TC'], 16)

# 游戏常量
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
TILE_SIZE = 50  # 游戏格子大小
PLAYER_SIZE = 50  # 人物图片尺寸
FPS = 60

# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
WALL_COLOR = (50, 50, 200)
BOX_COLOR = (200, 100, 50)
TARGET_COLOR = (50, 200, 50)
PLAYER_COLOR = (200, 50, 50)
BOX_ON_TARGET_COLOR = (150, 150, 0)
BACKGROUND_COLOR = (230, 230, 230)
MENU_COLOR = (200, 200, 200)
BUTTON_COLOR = (100, 150, 200)
BUTTON_HOVER_COLOR = (120, 170, 220)
ERROR_COLOR = (255, 100, 100)

# 方向定义
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)

# 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("推箱子")
clock = pygame.time.Clock()

此外,为了使我设计的游戏更具趣味性,我又添加了通关撒花代码的设计(这里可以根据个人需要,可有可无)

# 撒花效果颜色
CONFETTI_COLORS = [
    (255, 0, 0),  # 红色
    (255, 165, 0),  # 橙色
    (255, 255, 0),  # 黄色
    (0, 128, 0),  # 绿色
    (0, 0, 255),  # 蓝色
    (75, 0, 130),  # 靛色
    (238, 130, 238)  # 紫色
]
 撒花效果类
class Confetti:
    def __init__(self, count: int = 100) -> None:
        self.particles = []
        self.count = count
        self.reset()

    def reset(self) -> None:
        """重置所有粒子"""
        self.particles = []
        for _ in range(self.count):
            self.particles.append(self.create_particle())

    def create_particle(self) -> Dict[str, Any]:
        """创建单个粒子"""
        return {
            'x': random.randint(0, SCREEN_WIDTH),
            'y': random.randint(-50, 0),
            'size': random.randint(2, 6),
            'color': random.choice(CONFETTI_COLORS),
            'speed_x': random.uniform(-2, 2),
            'speed_y': random.uniform(2, 5),
            'rotation': random.uniform(0, 360),
            'rotation_speed': random.uniform(-5, 5)
        }

    def update(self) -> None:
        """更新所有粒子状态"""
        for particle in self.particles:
            particle['x'] += particle['speed_x']
            particle['y'] += particle['speed_y']
            particle['rotation'] += particle['rotation_speed']

            # 如果粒子飞出屏幕底部,重新放置到顶部
            if particle['y'] > SCREEN_HEIGHT:
                particle['y'] = random.randint(-50, 0)
                particle['x'] = random.randint(0, SCREEN_WIDTH)

    def draw(self, screen: pygame.Surface) -> None:
        """绘制所有粒子"""
        for particle in self.particles:
            rect = pygame.Rect(particle['x'], particle['y'], particle['size'], particle['size'])
            pygame.draw.rect(screen, particle['color'], rect)

在设计游戏关卡时,我暂时没找到怎么让游戏本身自己生成,或者是太复杂了,我现在还学不会,所以我就自己手动设置关卡了,这个笨办法虽然好用就是有点费手

# 游戏关卡
levels = [
    # 关卡 1
    [
        "############",
        "#          #",
        "#  #####   #",
        "#  #   #   #",
        "#  # @### ##",
        "#  # $ $ $ #",
        "#  #   .#  #",
        "#  #  ...# #",
        "#  #  ...# #",
        "#  #   .#  #",
        "#          #",
        "############"
    ],
    # 关卡 2
    [
        "############",
        "#   #####  #",
        "#   #   #  #",
        "#   #$  #  #",
        "# ### # ## #",
        "#   #   # ##",
        "# # #  @#  #",
        "# # #$ $#  #",
        "# #.##.#  ##",
        "# #...#    #",
        "# #####    #",
        "############"
    ],
    # 关卡 3
    [
        "############",
        "#   #      #",
        "#   # #####",
        "# $ $ $   #",
        "#   # #$ #",
        "#   # ## #",
        "#  @# $ # #",
        "## #   # ##",
        " #  #  #  #",
        " # ..#  #  #",
        " # ...#    #",
        "############"
    ]
]

另外,由于python本身生成游戏人物和箱子的素材太丑,所以我就自己在网上找了几组图片,引入素材使用的代码,自己设计了这款游戏的元素

class AssetManager:
    def __init__(self, assets_dir: str = "assets") -> None:
        self.assets_dir = assets_dir
        self.images: Dict[str, pygame.Surface] = {}
        self.config = self.load_config()
        self.load_all_images()

    def load_config(self) -> Dict:
        """加载素材配置文件"""
        config_path = os.path.join(self.assets_dir, "config.json")
        default_config = {
            "player": "player.png",
            "wall": "wall.png",
            "box": "box.png",
            "box_on_target": "box_on_target.png",
            "target": "target.png",
            "background": "background.png",
            "menu_background": "menu_background.png",
            "button": "button.png",
            "button_hover": "button_hover.png"
        }

        try:
            if os.path.exists(config_path):
                with open(config_path, "r", encoding="utf-8") as f:
                    return json.load(f)
            else:
                # 创建默认配置文件
                os.makedirs(self.assets_dir, exist_ok=True)
                with open(config_path, "w", encoding="utf-8") as f:
                    json.dump(default_config, f, indent=4)
                return default_config
        except Exception as e:
            print(f"加载配置文件失败: {e}")
            return default_config

    def load_image(self, asset_name: str, default_size: Tuple[int, int] = (TILE_SIZE, TILE_SIZE)) -> pygame.Surface:
        """加载单个图像,若失败则返回默认图形"""
        asset_filename = self.config.get(asset_name, "")
        if not asset_filename:
            return self.create_default_surface(size=default_size, asset_name=asset_name)

        asset_path = os.path.join(self.assets_dir, asset_filename)

        try:
            if os.path.exists(asset_path):
                # 使用pygame.image.load加载图像
                image = pygame.image.load(asset_path)

                # 检查图像是否有alpha通道
                if image.get_alpha() is None:
                    image = image.convert()  # 优化无透明通道的图像
                else:
                    image = image.convert_alpha()  # 优化有透明通道的图像

                # 调整图像大小
                return pygame.transform.scale(image, default_size)
            else:
                print(f"图像文件不存在: {asset_path}")
                return self.create_default_surface(size=default_size, asset_name=asset_name)
        except Exception as e:
            print(f"加载图像失败: {e}")
            return self.create_default_surface(size=default_size, asset_name=asset_name)

    def load_all_images(self) -> None:
        """加载所有游戏图像"""
        # 加载玩家图像
        self.images["player"] = self.load_image("player", (PLAYER_SIZE, PLAYER_SIZE))

        # 加载游戏元素图像
        for element in ["wall", "box", "box_on_target", "target"]:
            self.images[element] = self.load_image(element)

        # 加载背景和菜单图像
        self.images["background"] = self.load_image("background", (SCREEN_WIDTH, SCREEN_HEIGHT))
        self.images["menu_background"] = self.load_image("menu_background", (SCREEN_WIDTH, SCREEN_HEIGHT))

        # 加载按钮图像
        self.images["button"] = self.load_image("button", (200, 50))
        self.images["button_hover"] = self.load_image("button_hover", (200, 50))

    def create_default_surface(self, size: Tuple[int, int], asset_name: str) -> pygame.Surface:
        """创建默认图形"""
        surface = pygame.Surface(size, pygame.SRCALPHA)

        if asset_name == "player":
            surface.fill(PLAYER_COLOR)
            # 简单的人脸标记
            pygame.draw.circle(surface, WHITE, (size[0] // 2, size[1] // 3), size[0] // 10)
            pygame.draw.circle(surface, WHITE, (size[0] // 2 + size[0] // 5, size[1] // 3), size[0] // 10)
        elif asset_name == "wall":
            surface.fill(WALL_COLOR)
        elif asset_name == "box":
            surface.fill(BOX_COLOR)
        elif asset_name == "box_on_target":
            surface.fill(BOX_ON_TARGET_COLOR)
        elif asset_name == "target":
            surface.fill(TARGET_COLOR)
        elif asset_name == "background":
            surface.fill(BACKGROUND_COLOR)
        elif "button" in asset_name:
            surface.fill(BUTTON_COLOR if "hover" not in asset_name else BUTTON_HOVER_COLOR)
            pygame.draw.rect(surface, BLACK, (0, 0, size[0], size[1]), 2)
        else:
            surface.fill(ERROR_COLOR)
            error_text = tiny_font.render("ERROR", True, WHITE)
            surface.blit(error_text, (size[0] // 2 - error_text.get_width() // 2,
                                      size[1] // 2 - error_text.get_height() // 2))

        return surface

    def get_image(self, asset_name: str) -> pygame.Surface:
        """获取图像,若不存在则返回默认"""
        return self.images.get(asset_name, self.create_default_surface((TILE_SIZE, TILE_SIZE), asset_name))

这是我用到的图片





对游戏主体代码的实现

# 玩家角色类
class Player:
    def __init__(self, x: int, y: int, asset_manager: AssetManager) -> None:
        self.x = x
        self.y = y
        self.asset_manager = asset_manager

    def move(self, direction: Tuple[int, int]) -> None:
        """直接移动玩家位置"""
        self.x += direction[0]
        self.y += direction[1]

    def draw(self, screen: pygame.Surface, offset_x: int, offset_y: int) -> None:
        """绘制玩家"""
        # 计算居中位置
        x_pos = offset_x + self.x * TILE_SIZE
        y_pos = offset_y + self.y * TILE_SIZE

        # 绘制玩家图像
        screen.blit(self.asset_manager.get_image("player"), (x_pos, y_pos))


# 推箱子游戏类
class SokobanGame:
    def __init__(self, asset_manager: AssetManager) -> None:
        self.asset_manager = asset_manager
        self.current_level = 0
        self.reset_level()
        self.moves = 0
        self.history = []  # 用于撤销操作
        self.state = "playing"  # playing, level_complete, game_complete
        self.confetti = Confetti()  # 创建撒花效果实例
        self.show_confetti = False  # 是否显示撒花效果

    def load_level(self, level_index: int) -> None:
        level_data = levels[level_index]
        self.walls = set()
        self.boxes = set()
        self.targets = set()

        player_x, player_y = 0, 0

        for y, row in enumerate(level_data):
            for x, char in enumerate(row):
                if char == '#':
                    self.walls.add((x, y))
                elif char == '$':
                    self.boxes.add((x, y))
                elif char == '.':
                    self.targets.add((x, y))
                elif char == '@':
                    player_x, player_y = x, y
                elif char == '*':  # 箱子在目标上
                    self.boxes.add((x, y))
                    self.targets.add((x, y))
                elif char == '+':  # 玩家在目标上
                    player_x, player_y = x, y
                    self.targets.add((x, y))

        # 创建玩家角色
        self.player = Player(player_x, player_y, self.asset_manager)

    def reset_level(self) -> None:
        self.load_level(self.current_level)
        self.moves = 0
        self.history = []
        self.state = "playing"
        self.show_confetti = False  # 重置关卡时不显示撒花

    def move_player(self, direction: Tuple[int, int]) -> bool:
        if self.state != "playing":
            return False

        # 保存当前状态用于撤销
        self.history.append((self.player.x, self.player.y, self.boxes.copy(), self.moves))

        new_x = self.player.x + direction[0]
        new_y = self.player.y + direction[1]

        # 检查是否可以移动
        if (new_x, new_y) in self.walls:
            self.history.pop()  # 移除无效的历史记录
            return False

        # 检查是否推箱子
        if (new_x, new_y) in self.boxes:
            box_new_x = new_x + direction[0]
            box_new_y = new_y + direction[1]

            # 检查箱子是否可以移动
            if (box_new_x, box_new_y) in self.walls or (box_new_x, box_new_y) in self.boxes:
                self.history.pop()  # 移除无效的历史记录
                return False

            # 移动箱子
            self.boxes.remove((new_x, new_y))
            self.boxes.add((box_new_x, box_new_y))

        # 移动玩家
        self.player.move(direction)
        self.moves += 1

        # 检查关卡是否完成
        if self.is_level_complete():
            self.state = "level_complete"
            self.show_confetti = True  # 关卡完成时显示撒花
            self.confetti.reset()  # 重置撒花效果

        return True

    def undo_move(self) -> bool:
        if self.state != "playing" or not self.history:
            return False

        self.player.x, self.player.y, self.boxes, self.moves = self.history.pop()
        return True

    def is_level_complete(self) -> bool:
        for box in self.boxes:
            if box not in self.targets:
                return False
        return True

    def next_level(self) -> bool:
        if self.current_level < len(levels) - 1:
            self.current_level += 1
            self.reset_level()
            return True
        else:
            self.state = "game_complete"
            self.show_confetti = True  # 游戏完成时显示撒花
            self.confetti.reset()  # 重置撒花效果
            return False

    def draw(self, screen: pygame.Surface) -> None:
        # 绘制背景
        screen.blit(self.asset_manager.get_image("background"), (0, 0))

        # 计算偏移量以使游戏居中显示
        max_x = max(max(x for x, y in self.walls), self.player.x, max(x for x, y in self.boxes))
        max_y = max(max(y for x, y in self.walls), self.player.y, max(y for x, y in self.boxes))
        offset_x = (SCREEN_WIDTH - (max_x + 1) * TILE_SIZE) // 2
        offset_y = (SCREEN_HEIGHT - (max_y + 1) * TILE_SIZE) // 2

        # 绘制目标点
        for x, y in self.targets:
            rect = pygame.Rect(offset_x + x * TILE_SIZE, offset_y + y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
            screen.blit(self.asset_manager.get_image("target"), rect)

        # 绘制墙壁
        for x, y in self.walls:
            rect = pygame.Rect(offset_x + x * TILE_SIZE, offset_y + y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
            screen.blit(self.asset_manager.get_image("wall"), rect)

        # 绘制箱子
        for box in self.boxes:
            x, y = box
            rect = pygame.Rect(offset_x + x * TILE_SIZE, offset_y + y * TILE_SIZE, TILE_SIZE, TILE_SIZE)

            # 根据箱子是否在目标点上选择不同的图像
            image_key = "box_on_target" if box in self.targets else "box"
            screen.blit(self.asset_manager.get_image(image_key), rect)

        # 绘制玩家
        self.player.draw(screen, offset_x, offset_y)

        # 显示当前关卡和移动次数
        level_text = small_font.render(f"关卡: {self.current_level + 1}/{len(levels)}", True, BLACK)
        moves_text = small_font.render(f"移动次数: {self.moves}", True, BLACK)
        screen.blit(level_text, (10, 10))
        screen.blit(moves_text, (10, 40))

        # 显示操作说明
        controls_text = tiny_font.render(
            "↑↓←→:移动  R:重置关卡  U:撤销  N:下一关  ESC:菜单", True, BLACK)
        screen.blit(controls_text, (10, SCREEN_HEIGHT - 20))

        # 如果关卡完成,显示消息
        if self.state == "level_complete":
            if self.current_level == len(levels) - 1:
                message = "恭喜你通关了所有关卡!"
            else:
                message = "关卡完成!按 N 进入下一关"

            # 创建半透明背景
            s = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
            s.fill((0, 0, 0, 128))
            screen.blit(s, (0, 0))

            # 显示消息
            text = font.render(message, True, WHITE)
            text_rect = text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 30))
            screen.blit(text, text_rect)

            # 显示移动次数
            moves_msg = f"本关移动次数: {self.moves}"
            moves_text = small_font.render(moves_msg, True, WHITE)
            moves_rect = moves_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20))
            screen.blit(moves_text, moves_rect)

            # 显示继续提示
            continue_text = small_font.render("按 R 重新开始本关", True, WHITE)
            continue_rect = continue_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 70))
            screen.blit(continue_text, continue_rect)

        # 如果游戏完成,显示消息
        if self.state == "game_complete":
            # 创建半透明背景
            s = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
            s.fill((0, 0, 0, 128))
            screen.blit(s, (0, 0))

            # 显示消息
            text = font.render("恭喜你通关了所有关卡!", True, WHITE)
            text_rect = text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 30))
            screen.blit(text, text_rect)

            # 显示总移动次数
            total_moves = sum(len(level) for level in levels)
            moves_msg = f"总移动次数: {self.moves}"
            moves_text = small_font.render(moves_msg, True, WHITE)
            moves_rect = moves_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20))
            screen.blit(moves_text, moves_rect)

            # 显示继续提示
            continue_text = small_font.render("按 R 重新开始游戏", True, WHITE)
            continue_rect = continue_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 70))
            screen.blit(continue_text, continue_rect)

        # 更新并绘制撒花效果
        if self.show_confetti:
            self.confetti.update()
            self.confetti.draw(screen)


# 游戏菜单类
class GameMenu:
    def __init__(self, asset_manager: AssetManager) -> None:
        self.asset_manager = asset_manager
        self.buttons = []
        self.create_buttons()

    def create_buttons(self) -> None:
        # 创建开始游戏按钮
        start_button = {
            "rect": pygame.Rect(SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 - 80, 200, 50),
            "text": "开始游戏",
            "action": "start"
        }

        # 创建关卡选择按钮
        levels_button = {
            "rect": pygame.Rect(SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 - 20, 200, 50),
            "text": "选择关卡",
            "action": "levels"
        }

        # 创建退出按钮
        exit_button = {
            "rect": pygame.Rect(SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 + 40, 200, 50),
            "text": "退出游戏",
            "action": "exit"
        }

        self.buttons = [start_button, levels_button, exit_button]

    def handle_event(self, event: pygame.event.Event) -> Optional[str]:
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:  # 左键点击
                for button in self.buttons:
                    if button["rect"].collidepoint(event.pos):
                        return button["action"]
        return None

    def draw(self, screen: pygame.Surface) -> None:
        # 绘制背景
        screen.blit(self.asset_manager.get_image("menu_background"), (0, 0))

        # 绘制标题
        title_text = font.render("推箱子游戏", True, BLACK)
        title_rect = title_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 150))
        screen.blit(title_text, title_rect)

        # 绘制作者和版本信息
        info_text = tiny_font.render("作者: 怀素 | 版本: 1.0", True, BLACK)
        info_rect = info_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT - 30))
        screen.blit(info_text, info_rect)

        # 绘制按钮
        for button in self.buttons:
            # 检查鼠标是否悬停在按钮上
            button_image = self.asset_manager.get_image(
                "button_hover" if button["rect"].collidepoint(pygame.mouse.get_pos()) else "button")

            # 绘制按钮
            screen.blit(button_image, button["rect"])

            # 绘制按钮文字
            text = small_font.render(button["text"], True, BLACK)
            text_rect = text.get_rect(center=button["rect"].center)
            screen.blit(text, text_rect)


# 关卡选择菜单类
class LevelSelectMenu:
    def __init__(self, asset_manager: AssetManager) -> None:
        self.asset_manager = asset_manager
        self.buttons = []
        self.back_button = {
            "rect": pygame.Rect(50, SCREEN_HEIGHT - 70, 150, 50),
            "text": "返回主菜单",
            "action": "back"
        }
        self.create_level_buttons()

    def create_level_buttons(self) -> None:
        self.buttons = []
        for i in range(len(levels)):
            row = i // 3
            col = i % 3
            button = {
                "rect": pygame.Rect(SCREEN_WIDTH // 2 - 200 + col * 150,
                                    SCREEN_HEIGHT // 2 - 100 + row * 80, 120, 60),
                "text": f"关卡 {i + 1}",
                "action": "level_" + str(i)
            }
            self.buttons.append(button)

    def handle_event(self, event: pygame.event.Event) -> Tuple[Optional[str], Optional[int]]:
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:  # 左键点击
                for button in self.buttons:
                    if button["rect"].collidepoint(event.pos):
                        level_index = int(button["action"].split("_")[1])
                        return ("start_level", level_index)

                if self.back_button["rect"].collidepoint(event.pos):
                    return ("back", None)
        return (None, None)

    def draw(self, screen: pygame.Surface) -> None:
        # 绘制背景
        screen.blit(self.asset_manager.get_image("menu_background"), (0, 0))

        # 绘制标题
        title_text = font.render("选择关卡", True, BLACK)
        title_rect = title_text.get_rect(center=(SCREEN_WIDTH // 2, 100))
        screen.blit(title_text, title_rect)

        # 绘制关卡按钮
        for button in self.buttons:
            # 检查鼠标是否悬停在按钮上
            button_image = self.asset_manager.get_image(
                "button_hover" if button["rect"].collidepoint(pygame.mouse.get_pos()) else "button")

            # 绘制按钮
            screen.blit(button_image, button["rect"])

            # 绘制按钮文字
            text = small_font.render(button["text"], True, BLACK)
            text_rect = text.get_rect(center=button["rect"].center)
            screen.blit(text, text_rect)

        # 绘制返回按钮
        back_image = self.asset_manager.get_image(
            "button_hover" if self.back_button["rect"].collidepoint(pygame.mouse.get_pos()) else "button")
        screen.blit(back_image, self.back_button["rect"])

        back_text = small_font.render(self.back_button["text"], True, BLACK)
        back_text_rect = back_text.get_rect(center=self.back_button["rect"].center)
        screen.blit(back_text, back_text_rect)

主函数代码

def main() -> None:
    # 创建素材管理器
    asset_manager = AssetManager()

    # 创建游戏组件
    game = SokobanGame(asset_manager)
    main_menu = GameMenu(asset_manager)
    level_select = LevelSelectMenu(asset_manager)

    # 游戏状态
    state = "menu"  # menu, level_select, playing

    running = True
    while running:
        # 处理事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            if state == "menu":
                action = main_menu.handle_event(event)
                if action == "start":
                    game.reset_level()
                    state = "playing"
                elif action == "levels":
                    state = "level_select"
                elif action == "exit":
                    running = False

            elif state == "level_select":
                action, level_index = level_select.handle_event(event)
                if action == "start_level":
                    game.current_level = level_index
                    game.reset_level()
                    state = "playing"
                elif action == "back":
                    state = "menu"

            elif state == "playing":
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_UP:
                        game.move_player(UP)
                    elif event.key == pygame.K_DOWN:
                        game.move_player(DOWN)
                    elif event.key == pygame.K_LEFT:
                        game.move_player(LEFT)
                    elif event.key == pygame.K_RIGHT:
                        game.move_player(RIGHT)
                    elif event.key == pygame.K_r:
                        game.reset_level()
                        game.show_confetti = False  # 重置时关闭撒花
                    elif event.key == pygame.K_n:
                        if game.state == "level_complete":
                            game.next_level()
                            if game.state == "game_complete":
                                state = "menu"
                    elif event.key == pygame.K_u:
                        game.undo_move()
                    elif event.key == pygame.K_ESCAPE:
                        state = "menu"
                        game.show_confetti = False  # 返回菜单时关闭撒花

                # 如果关卡完成,按N进入下一关
                if game.state == "level_complete" and event.type == pygame.KEYDOWN and event.key == pygame.K_n:
                    if not game.next_level():
                        state = "menu"
                        game.show_confetti = False  # 返回菜单时关闭撒花

                # 如果游戏完成,按R重新开始
                if game.state == "game_complete" and event.type == pygame.KEYDOWN and event.key == pygame.K_r:
                    game.reset_level()
                    state = "playing"
                    game.show_confetti = False  # 重新开始时关闭撒花

        # 绘制当前状态
        if state == "menu":
            main_menu.draw(screen)
        elif state == "level_select":
            level_select.draw(screen)
        elif state == "playing":
            game.draw(screen)

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

        # 控制帧率
        clock.tick(FPS)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()

实验结果展示



代码托管到码云

https://gitee.com/jun-huaisu/pythonwork/blob/master/推箱子.py

实验中遇到的问题及解决办法

一、设置游戏关卡

在一开始时,我希望能够让python自己生成关卡模式,但是生成的太随机,而且很多变量都不太好控制,当然主要还是代码设计难度太高了,所以我就采用了一个好用问题也很少的笨办法,那就是自己手动设置(手动狗头)

二、对于素材的引入

在一开始添加素材进推箱子代码文件夹中时,老是由于尺寸问题导致图片解析错误,之后又因为代码部分细节的设计错误,浪费了很多时间,好在在同学和ai查错的帮助下,还是解决了这一问题

三、添加动画帧

因为使用动画帧时,没太能理解人物动画帧加载逻辑self.asset_manager.images.get("player_down") 没拿到动画帧,就会用 self.asset_manager.get_image("player") ,但要是 get_image 也没正确返回,就总画不出游戏人物,这一问题也卡了我很久(苦笑)

四、代码运行错误

这个问题的解决主要就是根据python上的报错提示一遍遍的改就行了

收获与心得体会

就借老师这句话,也祝老师万事顺意,马到成功!
同时,感谢老师这一学期以来的陪伴和辛勤付出,真的感觉自己向你学到了很多东西,python实在太权威了,很多问题都能从python里找到解决的办法,虽然这学期的python课结束了,但我还好继续学习python,到时候有不会的还找您哈。最后再说一句:强哥,YYDS!

参考资料

《Python 编程从入门到实践》
deepseek
https://docs.python.org/

视频链接

https://t.bilibili.com/1076447567541174311?share_source=pc_native

posted @ 2025-06-09 19:27  君怀素  阅读(51)  评论(0)    收藏  举报