软件工程课程第二次个人作业
软件工程 | https://edu.cnblogs.com/campus/fzu/SE2024 |
---|---|
作业要求 | https://edu.cnblogs.com/campus/fzu/SE2024/homework/13253 |
作业目标 | 做出一个类似“羊了个羊”的小游戏 |
学号 | 072208130 |
项目链接 | Starryship/cats (github.com) |
一、游戏设计
(一)页面设计
对于页面的设计,综合考虑了页面的美观性以及页面切换的合理性。设计了一个主页面,主页面有两个按钮,一个是游戏开始按钮,另一个是游戏记录按钮。点击游戏开始按钮可以进入游戏选关界面,一共设计了十二关,我这里把各选关按钮均匀排列,使页面排版更加合理美观。点击关卡按钮可以进入游戏页面,在游戏页面中,为了保证各小块图标的清晰度不被压缩,游玩区域就设计得足够大(为图中左侧区域),右侧区域则是我们的一些状态。右上方是计时按钮,记录游玩关卡的时间,右侧中间区域是我们的道具区,当我们在左侧游玩区是在是找不到卡关的解决方法时,我们可以点击道具区的方块解决我们的问题。右侧下方则是我们的状态(选快)区,我们从左侧选过来的方块都会放到这里。当我们胜利或者失败时,游戏会自动跳出来一个结果(result)页面,来表明你是成功的还是失败的,在该页面的左上角,我还设计了一个可以返回主页面的按钮还有一个退出游戏的按钮,充分考虑到了仍有游玩意愿或者想终止游戏的人。讲完了开始界面的start按钮,接下来要介绍的是record按钮,record按钮是我们的成功记录,里面按行将我们的通关记录展现了出来,包括关卡号还有通关时间。
页面的设计图如下:
(二)玩法设计
本项目游戏的玩法是类似于前段时间爆火的“羊了个羊”,不同的是,我更加丰富了其原本的玩法以及一些页面的优化。可以看到我们的关卡难度分为十二个等级,难度依次递增,其难度设计为随着关卡的增加,图片的种类和图片的数量会增加。其玩法是对于牌堆,从上到下可以依次点击,但是有被压住的牌不能进行点击,有三个相同的牌在卡槽里则这三张牌被消除,卡槽最大可容纳七张牌,如果我们左侧的牌全部消除完,则游戏胜利,如果不能消除的卡牌超过或等于卡槽的最大容量(本游戏设计的最大容量为7),则游戏失败。这就很考验我们合理规划,要找到一个最佳点击排队的顺序,以及合理利用道具,最终将左侧牌堆全部消除完。看起来确实是个很好玩的游戏呢!
二、详细介绍
(一)页面展示
首页
选关界面
游玩界面
这里展示了三个等级
这个很简单
这个会有点难
这个特别难
结束界面
胜利
失败
查看通关记录
动图展示(展示点击特效及关卡设置)
(二)玩法展示
通过点击上面的牌,将下面的牌拯救出来,三个牌九消去,知道左侧没有牌剩余
点击牌
简单玩法
速通玩法(并展示记录)
(三)技术实现
关卡选择
每一个level对应着相应的关卡。返回的第一个参数是层数,第二个参数是道具数量,第三个参数比如说list(range(1, kind+1)) * 48,range的终止条件减一的出来的结果就是图片种类数。
def level_select_s(level):
if level ==1:
return 6,5,list(range(1, 3)) * 48
elif level ==2:
return 7,4,list(range(1, 3)) * 72
elif level ==3:
return 3,1,list(range(1, 6)) * 3
elif level ==4:
return 7,4,list(range(1, 4)) * 48
elif level ==5:
return 6,4,list(range(1, 5)) * 24
elif level ==6:
return 7,4,list(range(1,5)) * 36
elif level ==7:
return 5,5,list(range(1, 6)) * 12
elif level ==8:
return 7,4,list(range(1,7)) * 24
elif level ==9:
return 6,5,list(range(1, 9)) * 12
elif level ==10:
return 5,5,list(range(1, 11)) * 6
elif level ==11:
return 4,3,list(range(1, 12)) * 3
elif level ==12:
return 7,4,list(range(1,13)) * 12
核心代码
该代码实现了每张牌之间的叠放和点击关系,只有点击上层下层才会被释放变得可点击,该代码就是利用该思想完成的这个点击关系。
def handle_mouse_click(pos):
global docks
if exit_button_rect.collidepoint(pos):
pygame.quit()
return
if len(docks) >= 7 or len(tiles) == 0: # 游戏结束时不处理点击
# game_over = True
return
for tile in reversed(tiles): # 从最上层的牌开始判断
rect = pygame.Rect(tile['pos'][0], tile['pos'][1], TILE_WIDTH, TILE_HEIGHT)
if tile['status'] == 1 and rect.collidepoint(pos):
tile['status'] = 2
tiles.remove(tile)
unmatched_tiles = [t for t in docks if t['tag'] != tile['tag']]
if len(docks) - len(unmatched_tiles) < 2:
docks.append(tile)
else:
docks = unmatched_tiles
for down_tile in tiles:
if down_tile['layer'] == tile['layer'] - 1 and pygame.Rect(down_tile['pos'][0], down_tile['pos'][1],
TILE_WIDTH, TILE_HEIGHT).colliderect(
pygame.Rect(tile['pos'][0], tile['pos'][1], TILE_WIDTH, TILE_HEIGHT)):
for up_tile in tiles:
if up_tile['layer'] == down_tile['layer'] + 1 and pygame.Rect(up_tile['pos'][0],
up_tile['pos'][1], TILE_WIDTH,
TILE_HEIGHT).colliderect(
pygame.Rect(down_tile['pos'][0], down_tile['pos'][1], TILE_WIDTH, TILE_HEIGHT)):
break
else:
down_tile['status'] = 1 # 如果没有其他牌覆盖,则变为可点击状态
break
驱动代码
这部分代码就是将各界面组织起来,并使程序完成相对应的点击任务,实现了绘图还有鼠标事件的关联驱动。
def main():
global level_select,tiles,selected_level,record,show_record
pygame.init()
clock = pygame.time.Clock()
running = True
game_started = False # 游戏是否开始标志
game_over = False # 游戏结束标志
start_time = None # 计时起始时间
# level_select = True
level_select = False
selected_level=0#选择关卡
tiles = make_titles(selected_level+1)
record=[]
elapsed_time2=0
time_record_control=False
show_record=False
# count=0
while running:
screen.fill(WHITE)
mouse_pos = pygame.mouse.get_pos()
is_clicked = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if game_over:
handle_game_over_click(event.pos)
game_over = False
game_started = False
level_select=True
elif not game_started and button_rect.collidepoint(event.pos):
game_started = True
level_select=True
elif not show_record and button_record.collidepoint(event.pos):
show_record = True
elif game_started and level_select:
handle_level_select_click(event.pos)
start_time = pygame.time.get_ticks() # 开始计时
elif level_select:
handle_record(event.pos)
elif game_started and not level_select:
handle_mouse_click(event.pos)
if len(docks) >= 7 or len(tiles) == 0:
level_select=False
game_over = True #
if len(tiles) == 0 and time_record_control:
record.append((selected_level, elapsed_time2))
time_record_control = False
if not game_started:
draw_start(screen, mouse_pos, is_clicked)
elif game_over:
draw_game_over_screen(screen, mouse_pos, is_clicked)
elif show_record:
draw_record(screen)
elif level_select:
draw_level_select_screen(screen, mouse_pos, is_clicked)
else:
draw(tiles) # 绘制游戏内容
time_record_control = True
elapsed_time = (pygame.time.get_ticks() - start_time) // 1000 # 计算经过时间
elapsed_time2 = round((pygame.time.get_ticks() - start_time) / 1000,3)
font = pygame.font.SysFont(None, 60)
time_text = font.render(f"Time: {elapsed_time}s", True, (0, 0, 0))
screen.blit(time_text, (1000, 50)) # 显示计时器
# print(record)
pygame.display.update()
clock.tick(60) # 控制帧率
pygame.quit()
(四)测试
三、制作过程
子任务 | 借助何种AIGC技术 | 实现了什么功能 | 效果如何 |
---|---|---|---|
游戏界面设计 | ChatGPT | 页面的设计 | 效果不太好,询问了好多次都没有能达到一个理想的结果,要么是回答太复杂了要么是不符合我的要求,后来是自己设计的页面。 |
pygame设计游戏的逻辑 | ChatGPT | 知识前提 | 使我了解到了该怎么设计游戏,以及各部分代码如何连接在一起 |
代码规范 | ChatGPT | 使代码可读性和可维护性增强 | 让我更加清楚每一部分衔接的逻辑,让我思路更加清晰 |
图片素材获取 | ChatGPT、豆包、Craiyon | 背景图还有按钮图的美化 | 效果非常好,图片精美程度从大到小依次是ChatGPT、豆包、Craiyon |
界面状态控制 | ChatGPT | 使用状态量控制页面的切换 | 效果还不错,虽然生成的代码会有一点小bug,但是给了我一条不错的思路 |
按钮点击反馈 | ChatGPT | 是点击到的逻辑块能正确做出相应的指示 | 这个还不错,知识缺少可以帮我补充这一部分知识 |
算法实现 | ChatGPT | 控制下层猫猫块不能被点击 | 效果不错,给了我一条很不错的思路,但是小bug改的很麻烦 |
数据存储 | ChatGPT | 记录每一个猫猫块的位置还有状态 | 效果很好,解决了我对于位置如何存储的疑惑 |
注释 | ChatGPT | 实现代码自动的注释 | 效果不太行,注释完的代码运行报错,它还修改了我的代码,可能是想让我的代码更加规范吧,但是改的效果不太好 |
问题解决(ChatGPT)
还有很多就不一一上传了
图片收集(ChatGPT)
图片收集(豆包)
图片收集(Craiyon)
图片汇总
本来时想用动物图的做图标的,但是后面发现图片尺寸缩小后失真特别严重,整体放上去特别难看,就改为猫猫图了
背景音乐
四、PSP表格
阶段 | 计划时间 (分钟) | 实际时间 (分钟) | 阶段总结 |
---|---|---|---|
计划 | 30 | 40 | 明确了项目的目标和需求,稍微超时 |
设计 | 120 | 300 | 设计不太顺利较为顺利,花了较多时间,主要是刚开始选错图片了 |
代码实现 | 300 | 600 | 遇到一些Bug,修复花费了较多时间,还有一些功能实现也占了比较多的时间 |
编译与测试 | 120 | 60 | 测试通过,基本没有问题 |
总结与报告撰写 | 240 | 240 | 总结分析了开发过程中的问题与经验 |
五、上传github
项目以及上传到github咯,有想玩的可以clone下来玩玩,不会玩的可以让作者来教你(我我)哦“ψ(`∇´)ψ
链接:Starryship/cats (github.com)
六、总结
感觉本次项目可以做的方向有很多,重点是发挥自己的想象力以及如何用好一些工具,但受时间限制,只能做一个简单的demo。本次项目的重点主要是在于界面的美化还有游戏的玩设计。对于本项目用到的所有图片均为AI生成,重点注意了美术设计,尽可能的让界面变得美观,使人看到这个游戏就有玩下去的欲望,虽然有些地方任然不够好看,但是大体上还是蛮不错的。然后就是游戏的玩法,玩法的核心其实就是“羊了个羊”的玩法,从上到下依次点击方块进行消除,趣味性还是蛮大的。但是对于原版“羊了个羊”,第二关就变得特别难,我就做了相应的调整,进行选关操作,难度层层递进,每攻下一关都有很大的成就感。如果闲着无聊的话,还可以开启竞速模式,对每一关或者某一关进行速通挑战,记录会存在电脑上,可以通过record按钮查看,对于你个人的反应还有规划的训练真是一个不错的选择,这么想想还真是不错呢!(≧∇≦)/
七、完整代码
import pygame
import random
# 初始化 Pygame
pygame.init()
# 缩放系数
SCALE_FACTOR = 1.6 # 1.5 倍放大
# 游戏属性常量定义
TITLE = '猫了个猫'
WIDTH, HEIGHT = (1500, 800)
TILE_WIDTH, TILE_HEIGHT = int(60 * SCALE_FACTOR), int(60 * SCALE_FACTOR)
# 设置屏幕
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
# 定义颜色
WHITE = (255, 255, 255)
# 加载资源并调整尺寸
background = pygame.transform.scale(pygame.image.load('image2/back.jpeg'), (WIDTH, HEIGHT))
mask = pygame.transform.scale(pygame.image.load('image2/mask.png'), (TILE_WIDTH, TILE_HEIGHT))
end_img = pygame.transform.scale(pygame.image.load('image2/lose.jpg'), (WIDTH, HEIGHT))
win_img = pygame.transform.scale(pygame.image.load('image2/win.jpg'), (WIDTH, HEIGHT))
exit_button = pygame.transform.scale(pygame.image.load('image2/tile2.png'), (50, 50)) # 退出按钮
f = pygame.transform.scale(pygame.image.load('image2/frame6.png'), (TILE_WIDTH+35, TILE_HEIGHT+35))
frame=pygame.transform.scale(pygame.image.load('image2/frame5.jpeg'), (TILE_WIDTH+30, TILE_HEIGHT+30))
cat_select=pygame.transform.scale(pygame.image.load('image2/cat_select2.jpeg'), (WIDTH, HEIGHT))
time_man=pygame.transform.scale(pygame.image.load('image2/tile.png'), (190, 100))
record_cat=pygame.transform.scale(pygame.image.load('image2/back22.jpg'), (WIDTH, HEIGHT))
#首页加载
back_start = pygame.transform.scale(pygame.image.load('image2/cat_sleep.jpeg'), (WIDTH, HEIGHT))
# 牌堆区域(DOCK)
# DOCK = pygame.Rect(int(90 * SCALE_FACTOR), int(564 * SCALE_FACTOR), TILE_WIDTH * 7, TILE_HEIGHT)
DOCK = pygame.Rect(600,600,TILE_WIDTH * 7, TILE_HEIGHT)
#按钮位置
exit_button_rect = exit_button.get_rect(topleft=(WIDTH - 50- 10, 10)) # 右上角
# exit_button_rect = exit_button.get_rect(topleft=(WIDTH , 10)) # 右上角
docks = []
def level_select_s(level):
if level ==3:
return 3,1,list(range(1, 6)) * 3
elif level ==1:
return 6,5,list(range(1, 3)) * 48
elif level ==2:
return 7,4,list(range(1, 3)) * 72
elif level ==4:
return 7,4,list(range(1, 4)) * 48
elif level ==5:
return 6,4,list(range(1, 5)) * 24
elif level ==6:
return 7,4,list(range(1,5)) * 36
elif level ==7:
return 5,5,list(range(1, 6)) * 12
elif level ==8:
return 7,4,list(range(1,7)) * 24
elif level ==9:
return 6,5,list(range(1, 9)) * 12
elif level ==10:
return 5,5,list(range(1, 11)) * 6
elif level ==11:
return 4,3,list(range(1, 12)) * 3
elif level ==12:
return 7,4,list(range(1,13)) * 12
def make_titles(level):
# 游戏中的所有牌及牌堆
tiles = []
# level=3
l,leftover,tile_numbers=level_select_s(level)
random.shuffle(tile_numbers)
# 创建牌
tile_index = 0
for layer in range(l): # 7层
for row in range(l - layer): # 每层行数递减
for col in range(l - layer):
tile_type = tile_numbers[tile_index]
tile_index += 1
tile = {
"image": pygame.transform.scale(pygame.image.load(f'cats/cat{tile_type}.jpg'), (TILE_WIDTH, TILE_HEIGHT)),
"pos": [int((20 + (layer * 0.5 + col) * 60) * SCALE_FACTOR), int((30 + (layer * 0.5 + row) * 66 * 0.9) * SCALE_FACTOR)], # 放大位置
"tag": tile_type,
"layer": layer,
"status": 1 if layer == l-1 else 0 # 最上层可点击
}
tiles.append(tile)
# 剩余的4张牌放置到牌堆底部
for i in range(leftover):
tile_type = tile_numbers[tile_index]
tile_index += 1
tile = {
"image": pygame.transform.scale(pygame.image.load(f'cats/cat{tile_type}.jpg'), (TILE_WIDTH, TILE_HEIGHT)),
"pos": [870+i*60*SCALE_FACTOR, 400], # 放大位置
"tag": tile_type,
"layer": 0,
"status": 1
}
tiles.append(tile)
return tiles
# 加载背景音乐
pygame.mixer.music.load('music/bgm.mp3')
pygame.mixer.music.play(-1) # 循环播放
# 游戏帧绘制函数
def draw(elapsed_time):
screen.blit(background, (0, 0)) # 绘制背景
# screen.blit(f, (750,DOCK.y))
for tile in tiles:
screen.blit(frame,(tile["pos"][0]-15,tile["pos"][1]-15))
screen.blit(tile['image'], tile['pos']) # 绘制牌
if tile['status'] == 0:
screen.blit(mask, tile['pos']) # 不可点击的牌显示遮罩
for i in range(7):
dock_pos = (755+i * (TILE_WIDTH+10),590)
screen.blit(pygame.transform.scale(f,(109,109)), dock_pos)
for i, tile in enumerate(docks):
# dock_pos = (DOCK.x + i * TILE_WIDTH+173, DOCK.y+6)
dock_pos = (i * (TILE_WIDTH+10)+755+15,590+15)
screen.blit(pygame.transform.scale(tile['image'],(80,80)), dock_pos)
screen.blit(time_man, (990, 30))
screen.blit(exit_button, exit_button_rect.topleft)
# 鼠标点击处理
def handle_mouse_click(pos):
global docks
if exit_button_rect.collidepoint(pos):
pygame.quit()
return
if len(docks) >= 7 or len(tiles) == 0: # 游戏结束时不处理点击
# game_over = True
return
for tile in reversed(tiles): # 从最上层的牌开始判断
rect = pygame.Rect(tile['pos'][0], tile['pos'][1], TILE_WIDTH, TILE_HEIGHT)
if tile['status'] == 1 and rect.collidepoint(pos):
tile['status'] = 2
tiles.remove(tile)
unmatched_tiles = [t for t in docks if t['tag'] != tile['tag']]
if len(docks) - len(unmatched_tiles) < 2:
docks.append(tile)
else:
docks = unmatched_tiles
for down_tile in tiles:
if down_tile['layer'] == tile['layer'] - 1 and pygame.Rect(down_tile['pos'][0], down_tile['pos'][1],
TILE_WIDTH, TILE_HEIGHT).colliderect(
pygame.Rect(tile['pos'][0], tile['pos'][1], TILE_WIDTH, TILE_HEIGHT)):
for up_tile in tiles:
if up_tile['layer'] == down_tile['layer'] + 1 and pygame.Rect(up_tile['pos'][0],
up_tile['pos'][1], TILE_WIDTH,
TILE_HEIGHT).colliderect(
pygame.Rect(down_tile['pos'][0], down_tile['pos'][1], TILE_WIDTH, TILE_HEIGHT)):
break
else:
down_tile['status'] = 1 # 如果没有其他牌覆盖,则变为可点击状态
break
BUTTON_WIDTH = 280
BUTTON_HEIGHT = 75
BUTTON_COLOR = (218, 198, 165) # 绿色按钮
BUTTON_TEXT_COLOR = (255, 255, 255) # 白色文字
# 定义按钮的矩形区域
button_rect = pygame.Rect((WIDTH // 2 - BUTTON_WIDTH // 2, HEIGHT // 2 - BUTTON_HEIGHT // 2-50), (BUTTON_WIDTH, BUTTON_HEIGHT))
button_record = pygame.Rect((WIDTH // 2 - BUTTON_WIDTH // 2, HEIGHT // 2 - BUTTON_HEIGHT // 2+40), (BUTTON_WIDTH, BUTTON_HEIGHT))
def draw_start(screen, mouse_pos, is_clicked):
screen.blit(back_start, (0, 0))
# 检查鼠标是否在按钮范围内
if button_rect.collidepoint(mouse_pos):
if is_clicked:
color1 = (100, 200, 100) # 点击时颜色
else:
color1 = (206, 179, 131) # 悬停时颜色
else:
color1 = BUTTON_COLOR # 正常按钮颜色
if button_record.collidepoint(mouse_pos):
if is_clicked:
color2 = (100, 200, 100) # 点击时颜色
else:
color2 = (206, 179, 131) # 悬停时颜色
else:
color2 = BUTTON_COLOR # 正常按钮颜色
# 绘制按钮的外边框
border_color = (0, 0, 0) # 边框颜色
pygame.draw.rect(screen, border_color, button_rect, border_radius=15) # 边框
# 绘制按钮本身(带圆角)
pygame.draw.rect(screen, color1, button_rect.inflate(-4, -4), border_radius=15) # -4让边框可见
# 创建并绘制按钮文字
font = pygame.font.SysFont(None, 36)
text = font.render("Start !", True, BUTTON_TEXT_COLOR)
text_rect = text.get_rect(center=button_rect.center)
screen.blit(text, text_rect) # 显示文字
# 绘制按钮的外边框
border_color = (0, 0, 0) # 边框颜色
pygame.draw.rect(screen, border_color, button_record, border_radius=15) # 边框
# 绘制按钮本身(带圆角)
pygame.draw.rect(screen, color2, button_record.inflate(-4, -4), border_radius=15) # -4让边框可见
# 创建并绘制按钮文字
font = pygame.font.SysFont(None, 36)
text = font.render("Record", True, BUTTON_TEXT_COLOR)
text_rect = text.get_rect(center=button_record.center)
screen.blit(text, text_rect) # 显示文字
# 定义按钮的宽度、高度和颜色
BUTTON_WIDTH_b = 200
BUTTON_HEIGHT_b = 60
BUTTON_COLOR2 = (0, 255, 0) # 按钮颜色
BUTTON_TEXT_COLOR2 = (255, 255, 255) # 文字颜色
# 回到主页按钮的位置
home_button_rect = pygame.Rect(10, 10, BUTTON_WIDTH_b, BUTTON_HEIGHT_b)
# 退出游戏按钮的位置
exit_button_rect2 = pygame.Rect(250, 10, BUTTON_WIDTH_b, BUTTON_HEIGHT_b)
def draw_game_over_screen(screen, mouse_pos, is_clicked):
# 游戏结束判定
if len(docks) >= 7:
screen.blit(end_img, (0, 0)) # 绘制游戏结束图
elif len(tiles) == 0:
screen.blit(win_img, (0, 0)) # 绘制胜利图
# 检查鼠标是否在按钮范围内
if home_button_rect.collidepoint(mouse_pos):
if is_clicked:
color = (100, 200, 100) # 点击时颜色
else:
color = (206, 179, 131) # 悬停时颜色
else:
color = BUTTON_COLOR # 正常按钮颜色
# 检查鼠标是否在按钮范围内
if exit_button_rect2.collidepoint(mouse_pos):
if is_clicked:
color2 = (100, 200, 100) # 点击时颜色
else:
color2 = (206, 179, 131) # 悬停时颜色
else:
color2 = BUTTON_COLOR # 正常按钮颜色
# 绘制回到主页按钮
pygame.draw.rect(screen, color, home_button_rect)
font = pygame.font.SysFont(None, 36)
home_text = font.render("Home", True, BUTTON_TEXT_COLOR2)
home_text_rect = home_text.get_rect(center=home_button_rect.center)
screen.blit(home_text, home_text_rect)
# 绘制退出游戏按钮
pygame.draw.rect(screen, color2, exit_button_rect2)
exit_text = font.render("Exit", True, BUTTON_TEXT_COLOR2)
exit_text_rect = exit_text.get_rect(center=exit_button_rect2.center)
screen.blit(exit_text, exit_text_rect)
def handle_game_over_click(pos):
global selected_level
if home_button_rect.collidepoint(pos):
# 玩家点击了回到主页按钮
reset_game(selected_level) # 重置游戏状态并返回主页
elif exit_button_rect2.collidepoint(pos):
# 玩家点击了退出游戏按钮
pygame.quit()
exit()
# 定义按钮的宽度、高度、颜色和关卡数
LEVEL_BUTTON_WIDTH_s = 200
LEVEL_BUTTON_HEIGHT_s = 60
BUTTON_COLOR3 = (0, 255, 0)
BUTTON_TEXT_COLOR3 = (255, 255, 255)
# 定义关卡数量
TOTAL_LEVELS_R = 3
TOTAL_LEVELS_C = 4
# 创建关卡按钮的矩形
level_buttons = []
for i in range(TOTAL_LEVELS_R):
for j in range(TOTAL_LEVELS_C):
# button_rect = pygame.Rect(WIDTH // 2 - LEVEL_BUTTON_WIDTH_s // 2, 200 + i * (LEVEL_BUTTON_HEIGHT_s + 20), LEVEL_BUTTON_WIDTH_s, LEVEL_BUTTON_HEIGHT_s)
button_rect3 = pygame.Rect(200+j*(LEVEL_BUTTON_WIDTH_s+100), 250 + i * (LEVEL_BUTTON_HEIGHT_s + 20), LEVEL_BUTTON_WIDTH_s, LEVEL_BUTTON_HEIGHT_s)
level_buttons.append(button_rect3)
def draw_level_select_screen(screen,mouse_pos, is_clicked):
screen.blit(cat_select, (0, 0))
font = pygame.font.SysFont(None, 36)
# 绘制每个关卡按钮
for i, button_rect in enumerate(level_buttons):
# 检查鼠标是否在按钮范围内
if button_rect.collidepoint(mouse_pos):
if is_clicked:
color = (100, 200, 100) # 点击时颜色
else:
color = (206, 179, 131) # 悬停时颜色
else:
color = BUTTON_COLOR # 正常按钮颜色
pygame.draw.rect(screen, color, button_rect)
level_text = font.render(f"Level {i + 1}", True, BUTTON_TEXT_COLOR)
text_rect = level_text.get_rect(center=button_rect.center)
screen.blit(level_text, text_rect)
def draw_record(screen):
screen.blit(record_cat, (0, 0)) # 绘制背景
font = pygame.font.SysFont(None, 50)
# 遍历 record 列表,逐行显示内容
for i, entry in enumerate(record):
text = font.render(f"record{i + 1} level:{entry[0]} time:{entry[1]}s", True, (255, 255, 255)) # 将 record 里的内容绘制成文字
screen.blit(text, (125, 100 + i * 40)) # 将每个记录绘制到不同的位置,避免重叠
def handle_level_select_click(pos):
global selected_level, level_select
for i, button_rect in enumerate(level_buttons):
if button_rect.collidepoint(pos):
selected_level = i + 1 # 玩家选择的关卡
reset_game(selected_level) # 重置游戏状态
# game_started = True # 开始游戏
level_select = False
# print(selected_level)
break
def handle_record(pos):
global show_record
if button_record.collidepoint(pos):
show_record = True
def reset_game(level):
global game_started, game_over, tiles, docks, start_time
game_started = False
game_over = False
tiles = make_titles(level) # 重新初始化游戏的牌组
docks = [] # 重置牌堆
start_time = None # 重置计时器
def main():
global level_select,tiles,selected_level,record,show_record
pygame.init()
clock = pygame.time.Clock()
running = True
game_started = False # 游戏是否开始标志
game_over = False # 游戏结束标志
start_time = None # 计时起始时间
# level_select = True
level_select = False
selected_level=0#选择关卡
tiles = make_titles(selected_level+1)
record=[]
elapsed_time2=0
time_record_control=False
show_record=False
# count=0
while running:
screen.fill(WHITE)
mouse_pos = pygame.mouse.get_pos()
is_clicked = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if game_over:
handle_game_over_click(event.pos)
game_over = False
game_started = False
level_select=True
elif not game_started and button_rect.collidepoint(event.pos):
game_started = True
level_select=True
elif not show_record and button_record.collidepoint(event.pos):
show_record = True
elif game_started and level_select:
handle_level_select_click(event.pos)
start_time = pygame.time.get_ticks() # 开始计时
elif level_select:
handle_record(event.pos)
elif game_started and not level_select:
handle_mouse_click(event.pos)
if len(docks) >= 7 or len(tiles) == 0:
level_select=False
game_over = True #
if len(tiles) == 0 and time_record_control:
record.append((selected_level, elapsed_time2))
time_record_control = False
if not game_started:
draw_start(screen, mouse_pos, is_clicked)
elif game_over:
draw_game_over_screen(screen, mouse_pos, is_clicked)
elif show_record:
draw_record(screen)
elif level_select:
draw_level_select_screen(screen, mouse_pos, is_clicked)
else:
draw(tiles) # 绘制游戏内容
time_record_control = True
elapsed_time = (pygame.time.get_ticks() - start_time) // 1000 # 计算经过时间
elapsed_time2 = round((pygame.time.get_ticks() - start_time) / 1000,3)
font = pygame.font.SysFont(None, 60)
time_text = font.render(f"Time: {elapsed_time}s", True, (0, 0, 0))
screen.blit(time_text, (1000, 50)) # 显示计时器
# print(record)
pygame.display.update()
clock.tick(60) # 控制帧率
pygame.quit()
if __name__ == "__main__":
main()