软工第二次作业

Github仓库链接:https://github.com/Yolaineyan/Yolaineyan

1. 游戏功能实现

界面设计:

使用Pygame或其他图形库设计游戏界面,包括主菜单、游戏界面和结束界面。

游戏逻辑:

实现图案的生成与分三层摆放,确保图案能够被合理匹配和消除。
玩家通过点击选择图案并消除,当所有图案被消除时游戏成功,当图案超出框内时失败。
难度设置:难度分为简单普通困难,三种模式。

2. 代码要求

使用GitHub Copilot或其他AIGC工具生成至少30%的代码,并在注释中标注自动生成的部分。
确保代码结构清晰,具有良好的可读性和注释。

  1. 游戏初始化
    使用 Pygame 初始化游戏,并创建窗口。窗口的宽高被设为 1920x1080 的一半以符合用户的缩放要求。
    加载了多种游戏资源(图片、音乐等),包括背景图片、牌组图片、按钮图片以及背景音乐。
  2. 自定义类
    定义了 CustomTile 类,用来表示游戏中的每一张牌。每张牌都包含图片、位置、类型(tag)、层级(layer),以及状态(status)。
    状态 status:代表是否可以点击,1 表示可以点击,0 表示被遮挡不可点击。
  3. 难度选择
    difficulty_select() 函数处理游戏开始前的难度选择。通过加载不同难度按钮,用户可以点击选择“easy”、“normal” 或 “hard”模式。直到用户点击选择某个难度按钮,才会进入游戏。
  4. 牌组初始化
    init_tile_group() 函数负责生成一组 12x12 的牌,并且随机打乱顺序放置在游戏界面上。
    牌分为多层,初始有四层牌,每一层的牌数逐层减少。
    牌的种类有六种,通过随机打乱顺序后按规则摆放在游戏画面中。
  5. 游戏主循环
    main() 函数为游戏的主循环,调用难度选择函数,并不断刷新屏幕,检查用户的输入和操作。
    在每一帧,游戏会处理玩家点击事件,绘制当前的游戏界面。
  6. 游戏的绘制
    draw() 函数负责在每一帧绘制当前游戏状态,包括:
    牌的展示:牌堆与遮罩,显示的牌是否可点击。
    玩家已点击的牌会放入卡槽中。
    若卡槽中牌的数量达到7张,显示结束画面(失败)。
    若所有牌都已消除,则显示胜利画面。
  7. 鼠标点击响应
    on_mouse_down() 处理用户的鼠标点击事件,负责:
    处理点击“返回主菜单”按钮,重置游戏并返回主菜单。
    当玩家点击牌时,牌从桌面移动到卡槽中,根据不同难度规则来判断是否能消除这些牌:
    easy 模式:相同牌达到1张即可消除。
    normal 模式:相同牌达到2张即可消除。
    hard 模式:相同牌达到3张才可消除。
    还包括点击“清空道具”功能,清空当前卡槽中的牌。
  8. 返回主菜单
    return_to_menu() 函数用于处理返回主菜单的逻辑,重置所有游戏状态,并重新开始游戏。
  9. 游戏音乐
    在游戏启动时,背景音乐会循环播放,增加游戏的氛围感。

import pygame
import random
import sys

# 定义游戏相关属性
TITLE = '羊了个羊小游戏'
WIDTH = 1920/2 # 放大后的宽度
HEIGHT = 1080/2  # 放大后的高度
FPS = 120

# 自定义游戏常量
T_WIDTH = 55  # 牌的宽度
T_HEIGHT = 55 # 牌的高度

# 下方牌堆的位置
DOCK = pygame.Rect((300, 485), (T_WIDTH *6, T_HEIGHT))  # 调整牌堆的位置

# 上方的所有牌
tiles = []
# 牌堆里的牌
docks = []

# 难度设置
DIFFICULTY = ''

# 清空道具的属性
CLEAR_ITEM_IMAGE = pygame.image.load('images/clear_item.png')  # 假设你有一个清空道具的图片
CLEAR_ITEM_RECT = pygame.Rect((WIDTH - 200, 20), (100, 100))  # 调整道具的位置
has_clear_item = True  # 玩家是否拥有清空道具的标志

# 返回主菜单按钮
BACK_BUTTON_IMAGE = pygame.image.load('images/back_button.png')  # 假设你有一个返回按钮的图片
BACK_BUTTON_RECT = pygame.Rect((10, 10), (100, 50))  # 按钮位置和大小

# 初始化 Pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))  # 设置为窗口模式,并放大
pygame.display.set_caption(TITLE)
clock = pygame.time.Clock()

# 加载背景和遮罩图像
background = pygame.image.load('images/back.png')
background = pygame.transform.scale(background, (WIDTH, HEIGHT))  # 背景适配屏幕
mask = pygame.image.load('images/mask.png')
mask = pygame.transform.scale(mask, (T_WIDTH, T_HEIGHT))  # 遮罩也需要适配
end = pygame.image.load('images/end.png')
end = pygame.transform.scale(end, (WIDTH/2, HEIGHT/2))  # 结束界面适配
win = pygame.image.load('images/win.png')
win = pygame.transform.scale(win, (WIDTH/2, HEIGHT/2))  # 胜利界面适配

# 加载按钮图片
easy_button = pygame.image.load('images/easy_button.png')
normal_button = pygame.image.load('images/normal_button.png')
hard_button = pygame.image.load('images/hard_button.png')

# 播放背景音乐
pygame.mixer.music.load('music/bgm.mp3')
pygame.mixer.music.play(-1)

# 自定义牌类
class CustomTile:
    def __init__(self, image, rect, tag, layer, status):
        self.image = image
        self.rect = rect
        self.tag = tag
        self.layer = layer
        self.status = status

# 难度选择界面
def difficulty_select():
    global DIFFICULTY
    easy_rect = easy_button.get_rect(center=(WIDTH // 2, HEIGHT // 2 - 150))  # 调整按钮位置
    normal_rect = normal_button.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    hard_rect = hard_button.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 150))

    DIFFICULTY = ''  # 初始化难度为空字符串
    while DIFFICULTY not in ['easy', 'normal', 'hard']:
        screen.fill((0, 0, 0))  # 用黑色填充屏幕
        screen.blit(background, (0, 0))  # 绘制背景图像
        screen.blit(easy_button, easy_rect)  # 绘制简单模式按钮
        screen.blit(normal_button, normal_rect)  # 绘制普通模式按钮
        screen.blit(hard_button, hard_rect)  # 绘制困难模式按钮
        pygame.display.update()  # 更新屏幕显示

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if easy_rect.collidepoint(event.pos):
                    DIFFICULTY = 'easy'
                elif normal_rect.collidepoint(event.pos):
                    DIFFICULTY = 'normal'
                elif hard_rect.collidepoint(event.pos):
                    DIFFICULTY = 'hard'

    return DIFFICULTY

# 初始化牌组,12*12张牌随机打乱
def init_tile_group():
    ts = ['zuanshi', 'hongshi', 'tiedin', 'lvbaoshi', 'meitan', 'qingshi'] * 6
    random.shuffle(ts)
    n = 0
    for k in range(4):  # 4层
        for i in range(4 - k):  # 每层减1行
            for j in range(4 - k):
                t = ts[n]  # 获取排种类
                n += 1
                tile_image = pygame.image.load(f'images/{t}.png')  # 加载对应的图片
                tile_image = pygame.transform.scale(tile_image, (T_WIDTH, T_HEIGHT))
                tile_rect = tile_image.get_rect()
                tile_rect.topleft = (350 + (k * 0.5 + j) * T_WIDTH, 150 + (k * 0.5 + i) * T_HEIGHT * 0.9)
                tile = CustomTile(tile_image, tile_rect, t, k, 1 if k == 3 else 0)
                tiles.append(tile)
    for i in range(6):  # 剩余的6张牌放下面(为了凑整能通关)
        t = ts[n]
        n += 1
        tile_image = pygame.image.load(f'images/{t}.png')
        tile_image = pygame.transform.scale(tile_image, (T_WIDTH, T_HEIGHT))
        tile_rect = tile_image.get_rect()
        tile_rect.topleft = (300 + i * T_WIDTH, 375)  # 调整底部位置
        tile = CustomTile(tile_image, tile_rect, t, 0, 1)
        tiles.append(tile)

# 游戏帧绘制函数
def draw():
    screen.blit(background, (0, 0))
    for tile in tiles:
        screen.blit(tile.image, tile.rect)
        if tile.status == 0:
            screen.blit(mask, tile.rect)
    for i, tile in enumerate(docks):
        tile.rect.left = DOCK.x + i * T_WIDTH
        tile.rect.top = DOCK.y
        screen.blit(tile.image, tile.rect)
    if len(docks) >= 7:
        screen.blit(end, (0, 0))
    if not tiles:
        screen.blit(win, (0, 0))
    if has_clear_item:
        screen.blit(CLEAR_ITEM_IMAGE, CLEAR_ITEM_RECT)
    screen.blit(BACK_BUTTON_IMAGE, BACK_BUTTON_RECT)  # 绘制返回主菜单按钮
    pygame.display.flip()

# 鼠标点击响应
def on_mouse_down(pos):
    global docks, has_clear_item
    if BACK_BUTTON_RECT.collidepoint(pos):
        return_to_menu()  # 调用返回主菜单函数
    if len(docks) >= 7 or not tiles:
        return
    if has_clear_item and CLEAR_ITEM_RECT.collidepoint(pos):
        has_clear_item = False  # 消耗道具
        # 将卡槽里的牌放到底部牌堆的上方
        bottom_deck_start_y = 485 - T_HEIGHT  # 底部牌堆的上方起始y坐标
        for i, tile in enumerate(docks):
            tile.status = 1  # 重置状态为可点击
            # 计算应该放置的位置(底部牌堆的上方)
            new_x = 300 + i * T_WIDTH
            new_y = bottom_deck_start_y + (i // 7) * T_HEIGHT
            tile.rect.topleft = (new_x, new_y)
            tiles.append(tile)  # 加入到tiles列表
        docks.clear()  # 清空卡槽
        return
    for tile in reversed(tiles):  # 逆序循环是为了先判断上方的牌
        if tile.status == 1 and tile.rect.collidepoint(pos):
            tile.status = 2
            tiles.remove(tile)
            docks.append(tile)
            # 根据难度检查是否可以消除
            if DIFFICULTY == 'easy':
                # 简单模式下,一张相同的卡片就可以消除
                if len([t for t in docks if t.tag == tile.tag]) >= 1:
                    docks = [t for t in docks if t.tag != tile.tag]
            elif DIFFICULTY == 'normal':
                # 普通模式下,需要两张相同的卡片
                if len([t for t in docks if t.tag == tile.tag]) >= 2:
                    docks = [t for t in docks if t.tag != tile.tag]
            elif DIFFICULTY == 'hard':
                # 困难模式下,需要三张相同的卡片
                if len([t for t in docks if t.tag == tile.tag]) >= 3:
                    docks = [t for t in docks if t.tag != tile.tag]
            for down in tiles:
                if down.layer == tile.layer - 1 and down.rect.colliderect(tile.rect):
                    for up in tiles:
                        if up.layer == down.layer + 1 and up.rect.colliderect(down.rect):
                            break
                    else:
                        down.status = 1
            return

# 返回主菜单
def return_to_menu():
    global tiles, docks, DIFFICULTY, has_clear_item
    tiles.clear()
    docks.clear()
    DIFFICULTY = ''
    has_clear_item = True
    difficulty_select()  # 重新进入难度选择界面
    init_tile_group()  # 重新初始化牌组

# 游戏主循环

def main():
    difficulty_select()  # 调用难度选择界面
    init_tile_group()  # 初始化牌组
    has_clear_item = True  # 玩家开始时拥有清空道具
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                on_mouse_down(event.pos)
        draw()
        clock.tick(FPS)
    pygame.quit()

# 启动游戏
main()

3. 结合AIGC

子任务 何种AIGC技术 效果
难度设置 ChatGPT-4.0 成功设置了三种难度
音乐播放 使音乐能流畅 循环播放
道具 copilot 实现了将卡槽中的卡作为使用卡片
返回功能 ChatGPT-4.0 能在游戏中回到主界面

4. 扩展功能(附加分)

增加道具功能,使卡槽中的牌回到上方道具区以使用,以增加游戏趣味性。
提供多种难度模式,设置了三种不同的难度,为玩家提供选择。

5.AIGC表格

学习内容 具体技术/工具 心得体会
使用 Pygame 开发小游戏 Pygame, Python 理解了 Pygame 的事件处理、图像绘制等机制。
优化 UI 界面并提升用户体验 界面设计, 事件处理 学会了通过调整按钮布局和图片来优化用户体验。
处理项目中的图像加载和优化 Pygame, 图像处理 深刻认识到图像大小调整和加载速度对性能的影响。
实现音频播放与音效管理 Pygame, 音频处理 掌握了音频在游戏中的应用,理解了 Pygame 的音频模块。

6.PSP表格

PSP 阶段 任务内容 预估耗时(分钟) 实际耗时(分钟) 个人评价与改进建议
计划 明确项目需求与目标 30 40 在明确需求时花费时间比预期长,未来应更高效地与需求相关方沟通。
开发 初步设计方案 40 35 设计方案较为顺利,但部分细节需要进一步优化。
开发 编写程序主结构和初始化部分 60 75 编写过程中遇到一些逻辑问题,导致实际时间延长。未来应更关注初始结构设计。
开发 实现游戏界面、图像加载与处理功能 120 130 游戏界面设计符合需求,但图像加载速度可以进一步优化。需要考虑性能提升。
开发 音频处理与音效播放功能 90 85 音效实现符合预期,时间估算合理,但在音效管理上还可进一步优化。
开发 处理用户输入事件与交互逻辑 100 110 用户输入处理较为顺利,但交互逻辑复杂,未来需要在设计阶段预留更多时间。
测试 单元测试与调试 80 120 测试和调试阶段耗时较长,未来需要在编码阶段减少潜在错误。
改进 优化图像加载速度与整体性能 60 50 优化过程顺利,未来可以考虑更多自动化工具来加速调试过程。
总结 总结与项目文档撰写 30 40 项目总结耗时略长,未来应优化总结结构,提高总结效率。
总计 - 610 685 总体上时间预估偏差不大,但在调试和测试阶段需要加强自动化工具的使用。
posted @ 2024-09-18 23:17  TheonlyYan  阅读(10)  评论(0编辑  收藏  举报