20254304 实验四 综合实践《Python程序设计》实验报告

20254304 2025-2026-2 《Python程序设计》 综合实践报告

课程:《Python程序设计》
班级: 2543
姓名: 田奇
学号:20254304
实验教师:王志强
实验日期:2026年5月25日
必修/选修: 公选课

1.实验内容

1.1 实验任务

Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
注:在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
评分标准:
(1)程序能运行,功能丰富(至少5个功能)。(需求提交源代码,并建议录制程序运行的视频)
(2)综合实践报告,要体现实验分析、设计、实现过程、结果等信息,格式规范,逻辑清晰,结构合理。
(3)在实践报告中,需要对全课进行总结,并写课程感想体会、意见和建议等。

1.2 选题

Python 的小游戏开发因其趣味性强、反馈直观、逻辑清晰,成为检验程序设计能力与算法思维的重要实践方式。且本学期上了python课,加之大模型的发展,都创造了有利条件。经典小游戏如《贪吃蛇》《扫雷》《俄罗斯方块》等在计算机发展史上具有重要地位,其规则简单但逻辑严密,但传统实现往往停留在单一玩法的层面,缺乏个性化设计与功能扩展。因此,这次综合实践,我想选取一款经典python小游戏“跑酷”作为基础原型,在保留其基本玩法的前提下,进行优化,例如引入多难度模式、单人选关闯关、加一些buff或者机制等功能。

1.3 实验工具

操作系统:Windows
开发工具:PyCharm
第三方库:pygame

2. 实验过程及结果

2.1 实验分析

(1)可行性
Python 在游戏开发领域同样具有较强的适用性。借助 Pygame​ 库,可以快速实现图形渲染、事件处理、碰撞检测等功能。结合本学期所学的 Python 基础知识(函数、面向对象)以及大模型辅助编程能力,开发一个结构完整的单机跑酷游戏在技术上是可行的。
(2)需求
本实验要求实现一个功能不少于5个、可运行的代码。跑酷游戏满足该要求:
账号注册与登录;多状态游戏控制(登录、菜单、游戏、暂停、结束等);玩家角色具备跳跃、滑铲、冲刺等动作;多关卡系统,难度随关卡递增;金币收集、道具系统与商店系统;碰撞检测与生命值管理;界面信息清晰,交互友好。
(3)方法
本实验采用面向对象程序设计思想进行开发,将游戏中的实体抽象为类,降低模块间耦合度。
(4)难点
例如,
游戏状态切换问题​,游戏在不同阶段(如暂停、死亡、通关)之间容易出现逻辑混乱。因此定义统一的游戏状态常量,由主循环集中控制状态切换。
碰撞检测准确性问题​,玩家滑铲、跳跃时碰撞体积发生变化。因此根据玩家状态动态调整碰撞矩形。
数据一致问题​,道具需要在程序重启后仍然存在。
(5)创新点
例如,相比于传统单一玩法的python单机跑酷游戏,本实验在以下方面进行优化:引入账号系统,支持多用户独立存档;设计不同难度关卡,在“高手关”后提供无限闯关模式;增加道具与商店系统;使用粒子特效与动态背景,提升视觉表现力。
(6)预期
完成一个可在 Windows 平台稳定运行、功能完整的 Python 跑酷游戏,并通过这次综合实践报告展示实验分析、设计思路、实现过程与运行结果。

2.2 实验功能设计

下面是较为完整的跑酷小游戏功能设计:
(1)账号系统(注册 / 登录 / 存档)
(2)游戏状态管理(LOGIN登录 / 注册;MENU主菜单 / 关卡选择;PLAYING游戏进行中;PAUSED暂停;GAMEOVER游戏失败;LEVEL_CLEAR关卡通关;END_CHOICE最终通关选择)
(3)UI 界面(展示游戏信息、提供操作入口并增强交互体验。在代码中,UI 包括分数与金币显示、关卡信息、按钮菜单、相关提示以及商店界面,使玩家能够直观地了解游戏状态并进行控制。)
(4)界面背景(粒子、爱心树等)
(5)玩家(各种动作/shop购买道具应用于玩家/玩家与障碍的碰撞检测、金币检测)
(6)障碍物、关卡系统(难度递增。新手关,速度慢,障碍少;进阶关,宽/矮障碍;高手关,空中障碍+位置不定+速度快;三关通过之后也可选择继续无限闯关)
(7)金币、道具、商店系统(不同大小金币代表不同分值和数量;道具包含模拟磁铁(其实就是扩大范围,减缓金币速度)、护盾(碰到障碍物不扣次数,否则碰到三次就gameover)等;shop拿金币coins购买道具)

2.3 实现过程

(1)生成python游戏,先检查pycharm有没有安装过相应的库

屏幕截图 2026-05-27 204621

(2)刚开始不知从何着手,先输入相关的想法,进行第一轮对话,让大模型生成基本代码框架

代码框架:

import pygame
import math
import random

WIDTH, HEIGHT = 1200, 600
FPS = 60

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("NEON PARKOUR · LEVEL EDITION")
clock = pygame.time.Clock()

TITLE_FONT = pygame.font.Font(None, 72)
UI_FONT = pygame.font.Font(None, 32)
SMALL_FONT = pygame.font.Font(None, 24)

BG = (8, 8, 18)
NEON_BLUE = (0, 220, 255)
NEON_PINK = (255, 80, 180)
NEON_GREEN = (0, 255, 170)
COIN_COLOR = (255, 215, 0)
UI_BG = (20, 20, 35, 180)

PLAYER_COLOR = (0, 200, 255)
SLIDE_COLOR = (0, 255, 200)
DASH_COLOR = (255, 255, 0)
STATE_MENU = 0
STATE_PLAYING = 1
STATE_PAUSED = 2
STATE_GAMEOVER = 3

game_state = STATE_MENU
class Player:
    def __init__(self):
        self.x = 140
        self.base_y = 420
        self.y = self.base_y
        self.width = 30
        self.height = 50

        self.velocity_y = 0
        self.gravity = 0.8
        self.jump_power = -14

        self.jumps_left = 2
        self.is_sliding = False
        self.slide_timer = 0

        self.is_dashing = False
        self.dash_timer = 0

        self.base_speed = 6
        self.speed = self.base_speed

        self.coins = 0
        self.magnet = False
        self.shield = False
        self.powerup_timers = {}
        self.color_flash = 0

    def jump(self):
        if self.jumps_left > 0:
            self.velocity_y = self.jump_power
            self.jumps_left -= 1

    def dash(self):
        if not self.is_dashing:
            self.is_dashing = True
            self.dash_timer = 20
            self.speed = self.base_speed * 2

    def slide(self):
        if not self.is_sliding and self.on_ground():
            self.is_sliding = True
            self.slide_timer = 15

    def apply_powerup(self, name):
        self.powerup_timers[name] = 600

    def on_ground(self):
        return self.y + self.height >= 470

    def update(self):
        self.velocity_y += self.gravity
        self.y += self.velocity_y

        if self.on_ground():
            self.y = 470 - self.height
            self.velocity_y = 0
            self.jumps_left = 2

        if self.is_sliding:
            self.slide_timer -= 1
            if self.slide_timer <= 0:
                self.is_sliding = False

        if self.is_dashing:
            self.dash_timer -= 1
            if self.dash_timer <= 0:
                self.is_dashing = False
                self.speed = self.base_speed

        for k in list(self.powerup_timers):
            self.powerup_timers[k] -= 1
            if self.powerup_timers[k] <= 0:
                del self.powerup_timers[k]

        self.magnet = "magnet" in self.powerup_timers
        self.shield = "shield" in self.powerup_timers

        if self.color_flash > 0:
            self.color_flash -= 1

    @property
    def rect(self):
        if self.is_sliding:
            return pygame.Rect(self.x, self.y + self.height // 2,
                               self.width, self.height // 2)
        return pygame.Rect(self.x, int(self.y), self.width, self.height)

    def draw(self, surf):
        color = (255, 100, 100) if self.color_flash > 0 else (
            DASH_COLOR if self.is_dashing else (
                SLIDE_COLOR if self.is_sliding else PLAYER_COLOR))
        pygame.draw.rect(surf, color, self.rect)

class Coin:
    def __init__(self, x):
        self.x = x
        self.y = random.randint(320, 420)
        self.radius = 8

    def update(self, speed):
        self.x -= speed

    def draw(self, surf):
        pygame.draw.circle(surf, COIN_COLOR, (int(self.x), self.y), self.radius)

class PowerUp:
    def __init__(self, x, kind):
        self.x = x
        self.y = 300
        self.kind = kind
        self.radius = 12

    def draw(self, surf):
        colors = {
            "magnet": (0, 255, 255),
            "shield": (255, 100, 100),
            "double": (180, 100, 255)
        }
        pygame.draw.circle(surf, colors[self.kind], (int(self.x), self.y), self.radius)
class Shop:
    def __init__(self):
        self.open = False

    def toggle(self):
        self.open = not self.open

    def draw(self, surf):
        if not self.open:
            return
        pygame.draw.rect(surf, (30, 30, 45), (400, 120, 400, 260))
        texts = [
            "Magnet  - 50 coins",
            "Shield  - 80 coins",
            "Double Jump+  - 100 coins"
        ]
        y = 150
        for t in texts:
            surf.blit(UI_FONT.render(t, True, (255, 255, 255)), (440, y))
            y += 60

class Star:
    def __init__(self):
        self.x = random.randint(0, WIDTH)
        self.y = random.randint(0, HEIGHT)
        self.size = random.uniform(0.5, 2.5)
        self.speed = random.uniform(0.2, 1.2)
        self.brightness = random.randint(120, 255)

    def update(self):
        self.x -= self.speed
        if self.x < 0:
            self.x = WIDTH
            self.y = random.randint(0, HEIGHT)

    def draw(self, surf):
        color = (self.brightness, self.brightness, self.brightness)
        pygame.draw.circle(surf, color, (int(self.x), int(self.y)), int(self.size))

class GlowParticle:
    def __init__(self):
        self.x = random.randint(0, WIDTH)
        self.y = random.randint(0, HEIGHT)
        self.vx = random.uniform(-0.3, 0.3)
        self.vy = random.uniform(-0.1, 0.1)
        self.radius = random.uniform(1, 3)
        self.color = random.choice([NEON_BLUE, NEON_PINK, NEON_GREEN])

    def update(self):
        self.x += self.vx
        self.y += self.vy
        if self.x < 0 or self.x > WIDTH:
            self.vx *= -1

    def draw(self, surf):
        pygame.draw.circle(surf, (*self.color, 50),
                           (int(self.x), int(self.y)), int(self.radius))

class Particle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.vx = random.uniform(-2, 2)
        self.vy = random.uniform(-2, 2)
        self.life = 20
        self.color = color

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.life -= 1

    def alive(self):
        return self.life > 0

    def draw(self, surf):
        pygame.draw.circle(surf, self.color, (int(self.x), int(self.y)), 3)

player = Player()
coins = []
powerups = []
particles = []
obstacles = []
shop = Shop()

stars = [Star() for _ in range(120)]
glows = [GlowParticle() for _ in range(40)]

spawn_timer = 0
score = 0
level = 1
LEVEL_TARGET = 300

def draw_hud(surf, player, score, level):
    panel = pygame.Surface((340, 140), pygame.SRCALPHA)
    panel.fill(UI_BG)
    surf.blit(panel, (20, 20))

    surf.blit(UI_FONT.render(f"LEVEL   {level}", True, NEON_PINK), (35, 30))
    surf.blit(UI_FONT.render(f"SCORE   {score}", True, NEON_BLUE), (35, 65))
    surf.blit(UI_FONT.render(f"COINS   {player.coins}", True, COIN_COLOR), (35, 100))

def draw_controls(surf):
    hints = [
        "[SPACE] Jump",
        "[SHIFT] Dash",
        "[CTRL] Slide",
        "[P] Pause",
        "[G] Shop",
        "[R] Restart"
    ]
    y = HEIGHT - 160
    for h in hints:
        surf.blit(SMALL_FONT.render(h, True, (160, 160, 160)), (WIDTH - 220, y))
        y += 24

def draw_menu(surf):
    surf.fill(BG)
    title = TITLE_FONT.render("NEON PARKOUR", True, NEON_BLUE)
    surf.blit(title, (WIDTH // 2 - title.get_width() // 2, 160))
    surf.blit(UI_FONT.render("[ENTER] START", True, NEON_GREEN), (WIDTH // 2 - 120, 300))
    surf.blit(UI_FONT.render("[ESC] QUIT", True, NEON_PINK), (WIDTH // 2 - 110, 350))

def draw_pause(surf):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 160))
    surf.blit(overlay, (0, 0))

    text = TITLE_FONT.render("PAUSED", True, NEON_PINK)
    surf.blit(text, (WIDTH // 2 - text.get_width() // 2, 200))

    tips = [
        "SPACE  Jump",
        "SHIFT  Dash",
        "CTRL   Slide",
        "G      Shop",
        "R      Restart",
        "P      Resume"
    ]
    y = 300
    for t in tips:
        surf.blit(UI_FONT.render(t, True, NEON_GREEN), (WIDTH // 2 - 100, y))
        y += 40

def draw_gameover(surf, score, coins):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 200))
    surf.blit(overlay, (0, 0))

    surf.blit(TITLE_FONT.render("GAME OVER", True, (255, 80, 80)),
              (WIDTH // 2 - 210, 180))
    surf.blit(UI_FONT.render(f"Score: {score}", True, NEON_BLUE),
              (WIDTH // 2 - 90, 280))
    surf.blit(UI_FONT.render(f"Coins: {coins}", True, COIN_COLOR),
              (WIDTH // 2 - 90, 330))
    surf.blit(UI_FONT.render("Press [R] to Restart", True, NEON_GREEN),
              (WIDTH // 2 - 160, 460))

running = True
while running:
    clock.tick(FPS)
    screen.fill(BG)

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

        if event.type == pygame.KEYDOWN:
            if game_state == STATE_MENU:
                if event.key == pygame.K_RETURN:
                    game_state = STATE_PLAYING

            elif game_state == STATE_PLAYING:
                if event.key == pygame.K_SPACE:
                    player.jump()
                if event.key == pygame.K_LSHIFT:
                    player.dash()
                if event.key == pygame.K_LCTRL:
                    player.slide()
                if event.key == pygame.K_p:
                    game_state = STATE_PAUSED
                if event.key == pygame.K_g:
                    shop.toggle()

            elif game_state == STATE_PAUSED:
                if event.key == pygame.K_p:
                    game_state = STATE_PLAYING

            elif game_state == STATE_GAMEOVER:
                if event.key == pygame.K_r:
                    player = Player()
                    coins.clear()
                    powerups.clear()
                    obstacles.clear()
                    particles.clear()
                    score = 0
                    level = 1
                    game_state = STATE_PLAYING

    if game_state == STATE_PLAYING:
        player.update()
        player.speed = player.base_speed + level

        for star in stars:
            star.update()
        for glow in glows:
            glow.update()

        spawn_timer += 1
        if spawn_timer > max(40, 90 - level * 5):
            obstacles.append(pygame.Rect(WIDTH, 420,
                          random.randint(30, 50),
                          random.randint(40, 70)))
            coins.append(Coin(WIDTH + random.randint(0, 100)))
            if random.random() < 0.05:
                powerups.append(PowerUp(WIDTH + 100,
                             random.choice(["magnet", "shield", "double"])))
            spawn_timer = 0

        for c in coins:
            c.update(player.speed)
            if player.magnet:
                dx = player.rect.centerx - c.x
                dy = player.rect.centery - c.y
                d = math.hypot(dx, dy)
                if d:
                    c.x += dx / d * 4

        for p in powerups:
            p.x -= player.speed

        for o in obstacles:
            o.x -= player.speed

        for c in coins[:]:
            if c.x < player.rect.right and c.x > player.rect.left:
                coins.remove(c)
                player.coins += 1
                score += 10
                particles.append(Particle(c.x, c.y, COIN_COLOR))

        for pu in powerups[:]:
            if pu.x < player.rect.right and pu.x > player.rect.left:
                player.apply_powerup(pu.kind)
                powerups.remove(pu)

        for o in obstacles[:]:
            if o.colliderect(player.rect):
                if player.shield:
                    obstacles.remove(o)
                    player.shield = False
                else:
                    player.color_flash = 10
                    game_state = STATE_GAMEOVER

        for p in particles[:]:
            p.update()
            if not p.alive():
                particles.remove(p)

        if score >= level * LEVEL_TARGET:
            level += 1

    if game_state == STATE_MENU:
        draw_menu(screen)

    elif game_state == STATE_PLAYING:
        for star in stars:
            star.draw(screen)
        for glow in glows:
            glow.draw(screen)

        player.draw(screen)
        for o in obstacles:
            pygame.draw.rect(screen, (255, 80, 80), o)
        for c in coins:
            c.draw(screen)
        for pu in powerups:
            pu.draw(screen)
        for p in particles:
            p.draw(screen)

        pygame.draw.line(screen, (80, 80, 80), (0, 470), (WIDTH, 470), 4)
        pygame.draw.line(screen, NEON_BLUE, (0, 470), (WIDTH, 470), 2)
        pygame.draw.line(screen, NEON_PINK, (0, 472), (WIDTH, 472), 1)

        draw_hud(screen, player, score, level)
        draw_controls(screen)
        shop.draw(screen)

    elif game_state == STATE_PAUSED:
        draw_pause(screen)

    elif game_state == STATE_GAMEOVER:
        draw_gameover(screen, score, player.coins)

    pygame.display.flip()

pygame.quit()

运行结果:

到这里,基本的单机“跑酷游戏”就已经可以通过运行代码玩了
当然,最最原始的版本其实是下面这个,是第一次直接给我的,特别简陋,而且问题很多。在第一轮对话中,已经多提一些指令,升级过一些基本的要素,如背景、按键提示、一段跳变成二段跳(可调整)等

(3)接下来就是把自己的一些新想法不断喂给大模型,根据其提出的更改建议,并结合自身实践,对最开始的代码框架进行修改、调整、补充,以丰富程序功能。

具体包括,例如:
增加不同难度的关卡和自主选关,以及失败后可以点击鼠标返回而不是直接弹出来,重新运行代码或者重新进。这一过程中还涉及相关界面的各种元素的调整改进。
对于游戏内容进行优化,例如,大模型后面突然给的代码,运行后显示不出来字体,呈现的是一堆方框而不是汉字,那就要重新调整,比如我选择计算机C盘自带的字体;再比如修改一些存在的问题,初版代码运行后,玩家碰到障碍物竟然加分,这需要调整,以及什么时候算做失败,达到什么门槛可以进入下一关,都可以调整。还有游戏本身的内容,如障碍物的位置、移动速度、出现频率,玩家碰到的加分金币的轨迹,其他加分方式等也可以不断补充。

(4)不断发现代码的问题和瑕疵,比如shop功能不能用(初版代码给的纯摆设),比如在修改代码过程中出现菜单页面显示“返回主菜单”,比如金币不能累计且还存在一些问题,等等,以及完善页面灵活切换、鼠标或键盘灵活点击、页面文字排版布局、内容优化等。总之,继续不断修改,直至形成最终版代码

下面,举一些例子,例如:

  • 引入账号与存档系统,金币coins和个人道具保留,但这里设定关卡进度再打开游戏会重置
  • 游戏状态更多样完善,在MENU / PLAY / PAUSE / GAMEOVER的基础上新增 LOGIN / LEVEL_CLEAR / END_CHOICE
  • 初版只有level += 1 spawn_timer -= level * 5,无明确关卡概念,改动后体现“游戏难度渐进设计”,增强挑战性,并在最后可选无限闯关。
  • 障碍物重构(多态),支持高 / 矮 / 宽 /空中障碍等
  • 金币种类由1 种增加至3 种(small / normal / big),对应不同价值,位置随机
  • 商店系统升级,初版,无法购买,无实际消耗,无库存,大模型给的代码纯摆设;新版:商店 UI,金币消费,库存,快捷键使用(1 / 2 / 3)并产生对应效果功能
  • 交互与UI体验提升(鼠标、键盘相对应的按键或按钮);
  • 背景与特效增强(发光粒子、心跳树等);对一些页面排版布局(文字,颜色等)、过关分数门槛、怎么加分数、过关金币奖励、game over触发条件、玩家速度、难度、道具价格等参数进行调整修改,这些参数在代码中均可人为改动

2.4 实验最终结果

  • Gitee上的代码

  • 代码运行视频:
    备注:为了录视频,将过关分数门槛和道具价格,以及触发coin的奖励等游戏参数,调得比较夸张,
    另外,登录状态下,调整为英文输入法 输入灵敏,中文状态小写字母不好输入

  • 最终代码以及模块划分
import pygame
import math
import random
import json
import os
import time

ACCOUNT_FILE = "accounts.json"
def load_accounts():
    if not os.path.exists(ACCOUNT_FILE):
        return {}
    with open(ACCOUNT_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_accounts(data):
    with open(ACCOUNT_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

accounts = load_accounts()
current_user = None
total_coins = 0

SAVE_FILE = "save.json"
def load_progress():
    if not os.path.exists(SAVE_FILE):
        return {
            "0": {"unlocked": True},
            "1": {"unlocked": False},
            "2": {"unlocked": False}
        }
    with open(SAVE_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_progress(data):
    with open(SAVE_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

# 基础设置
WIDTH, HEIGHT = 1200, 600
FPS = 60

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("NEON PARKOUR · LEVEL EDITION")
clock = pygame.time.Clock()
                       # 字体
TITLE_FONT = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 72)
UI_FONT = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 32)
SMALL_FONT = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 24)

                          # 颜色
BG = (8, 8, 18)
NEON_BLUE = (0, 220, 255)
NEON_PINK = (255, 80, 180)
NEON_GREEN = (0, 255, 170)
COIN_COLOR = (255, 215, 0)
UI_BG = (20, 20, 35, 180)

PLAYER_COLOR = (0, 200, 255)
SLIDE_COLOR = (0, 255, 200)
DASH_COLOR = (255, 255, 0)
#-------------------------------------------------------------------

COIN_TYPES = {
    "small": {"radius": 6, "value": 1},
    "normal": {"radius": 8, "value": 2},
    "big": {"radius": 12, "value": 5}
}
OBSTACLE_TYPES = {
    "block": {},
    "tall": {"height": 100},
    "short": {"height": 30},
    "wide": {"width": 80},
    "air": {"y": 320},          # 空中障碍
    "spike": {"shape": "spike"}
}
# 不同的游戏状态
STATE_LOGIN = 0
STATE_MENU = 1
STATE_PLAYING = 2
STATE_PAUSED = 3
STATE_GAMEOVER = 4
STATE_LEVEL_CLEAR = 5
STATE_END_CHOICE = 6

game_state = STATE_LOGIN
accounts = load_accounts()
input_user = ""
input_pass = ""
input_focus = "user"
login_step = "user"  # "user"输账号,"pass"输密码
account_box = pygame.Rect(520, 220, 300, 36)
password_box = pygame.Rect(520, 280, 300, 36)
login_error = ""
current_user = None
total_coins = 0

# 玩家
class Player:
    def __init__(self):
        self.reset_stats()
        self.coins = 0

    def reset_stats(self):
        self.x = 140
        self.base_y = 420
        self.y = self.base_y
        self.width = 30
        self.height = 50

        self.velocity_y = 0
        self.gravity = 0.8
        self.jump_power = -14

        self.jumps_left = 2
        self.is_sliding = False
        self.slide_timer = 0

        self.is_dashing = False
        self.dash_timer = 0

        self.base_speed = 6
        self.speed = self.base_speed

        self.magnet = False
        self.shield = False
        self.powerup_timers = {}

        self.max_hp = 3
        self.hp = self.max_hp
        self.invincible_timer = 0

        self.color_flash = 0

    def jump(self):
        max_jumps = 3
        if "double" in self.powerup_timers:
            max_jumps = 5

        if self.jumps_left > 0 and self.jumps_left <= max_jumps:
            self.velocity_y = self.jump_power
            self.jumps_left -= 1

    def dash(self):
        if not self.is_dashing:
            self.is_dashing = True
            self.dash_timer = 20
            self.speed = self.base_speed * 2

    def slide(self):
        if not self.is_sliding and self.on_ground():
            self.is_sliding = True
            self.slide_timer = 15

    def on_ground(self):
        return self.y + self.height >= 470

    def update(self):
        self.velocity_y += self.gravity
        self.y += self.velocity_y

        if self.on_ground():
            self.y = 470 - self.height
            self.velocity_y = 0
            self.jumps_left = 2

        if self.is_sliding:
            self.slide_timer -= 1
            if self.slide_timer <= 0:
                self.is_sliding = False

        if self.is_dashing:
            self.dash_timer -= 1
            if self.dash_timer <= 0:
                self.is_dashing = False
                self.speed = self.base_speed

        for k in list(self.powerup_timers):
            self.powerup_timers[k] -= 1
            if self.powerup_timers[k] <= 0:
                del self.powerup_timers[k]

        self.magnet = "magnet" in self.powerup_timers
        self.shield = "shield" in self.powerup_timers

        if self.color_flash > 0:
            self.color_flash -= 1

        if self.invincible_timer > 0:
            self.invincible_timer -= 1

        if hasattr(self, "magnet_timer"):
            self.magnet_timer -= 1
            if self.magnet_timer <= 0:
                self.magnet = False

        if hasattr(self, "shield_timer"):
            self.shield_timer -= 1
            if self.shield_timer <= 0:
                self.shield = False

    def hurt(self):
        if self.invincible_timer > 0:
            return False
        self.hp -= 1
        self.invincible_timer = 30
        self.color_flash = 10
        return self.hp <= 0

    @property
    def rect(self):
        if self.is_sliding:
            return pygame.Rect(self.x, self.y + self.height // 2,
                               self.width, self.height // 2)
        return pygame.Rect(self.x, int(self.y), self.width, self.height)

    def draw(self, surf):
        color = (255, 100, 100) if self.color_flash > 0 else (
            DASH_COLOR if self.is_dashing else (
                SLIDE_COLOR if self.is_sliding else PLAYER_COLOR))

        # 身
        pygame.draw.rect(surf, color, self.rect)

        # 头
        head_radius = 10
        head_x = self.rect.centerx
        head_y = self.rect.top - head_radius
        pygame.draw.circle(surf, color, (head_x, head_y), head_radius)

        # 眼
        eye_offset = 3
        pygame.draw.circle(surf, (255, 255, 255), (head_x - eye_offset, head_y - 2), 3)
        pygame.draw.circle(surf, (255, 255, 255), (head_x + eye_offset, head_y - 2), 3)

    def activate_powerup(self, name, duration=300):
        self.powerup_timers[name] = duration

class Obstacle:
    def __init__(self, x, kind="block"):
        self.kind = kind
        self.x = x

        cfg = OBSTACLE_TYPES.get(kind, {})

        self.y = cfg.get("y", 410)
        self.width = cfg.get("width", random.randint(30, 50))
        self.height = cfg.get("height", random.randint(40, 70))
        self.shape = cfg.get("shape", "rect")

        self.attack_timer = 0

    def rect(self):
        return pygame.Rect(int(self.x), self.y, self.width, self.height)

    def update(self, speed):
        self.x -= speed
    def draw(self, surf):
        if self.shape == "spike":
            # 尖刺
            points = [
                (self.x, self.y + self.height),
                (self.x + self.width // 2, self.y),
                (self.x + self.width, self.y + self.height)
            ]
            pygame.draw.polygon(surf, (255, 60, 60), points)
        else:
            pygame.draw.rect(surf, (255, 80, 80), self.rect())


#金币
class Coin:
    def __init__(self, x, y=None, kind=None):
        self.x = x
        self.kind = kind or random.choice(list(COIN_TYPES.keys()))

        cfg = COIN_TYPES[self.kind]
        self.radius = cfg["radius"]
        self.value = cfg["value"]

        if y is not None:
            self.y = y
        else:
            self.y = random.randint(300, 420)

    def update(self, speed):
        self.x -= speed

    def draw(self, surf):
        pygame.draw.circle(
            surf,
            COIN_COLOR,
            (int(self.x), int(self.y)),
            self.radius
        )
# 道具 (初版那个摆设删了,啥用没有)

# 商店
class Shop:
    def __init__(self):
        self.open = False
        self.items = [
            {"name": "magnet", "display": "磁铁(按1使用)", "price": 1},
            {"name": "shield", "display": "护盾(按2使用)", "price": 1},
            {"name": "double", "display": "多次跳(按3使用)", "price": 1},
        ]

    def toggle(self):
        self.open = not self.open

    def draw(self, surf, player):
        if not self.open:
            return

        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 180))
        surf.blit(overlay, (0, 0))

        #  面板
        panel_w, panel_h = 540, 400
        panel_x = WIDTH // 2 - panel_w // 2
        panel_y = HEIGHT // 2 - panel_h // 2

        pygame.draw.rect(surf, (30, 30, 45), (panel_x, panel_y, panel_w, panel_h), border_radius=16)
        pygame.draw.rect(surf, NEON_BLUE, (panel_x, panel_y, panel_w, panel_h), 2, border_radius=16)

        # 标题
        title = UI_FONT.render("商 店", True, NEON_PINK)
        surf.blit(title, (panel_x + panel_w // 2 - title.get_width() // 2, panel_y + 20))

        #字体(重调大小)
        item_font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 26)  # 道具名 
        info_font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 20)  # 价格 / 库存

        # 商品列表
        y = panel_y + 85
        item_height = 68
        padding_x = 40

        for item in self.items:
            rect = pygame.Rect(
                panel_x + padding_x,
                y,
                panel_w - padding_x * 2,
                item_height
            )

            affordable = player.coins >= item["price"]
            color = (255, 255, 255) if affordable else (110, 110, 110)

            pygame.draw.rect(surf, (40, 40, 60), rect, border_radius=12)
            pygame.draw.rect(surf, NEON_BLUE, rect, 2, border_radius=12)

            # 道具名
            name_surf = item_font.render(item["display"], True, color)
            surf.blit(name_surf, (rect.x + 22, rect.y + 10))

            # 价格
            price_text = f"价格:{item['price']} 金币"
            price_surf = info_font.render(price_text, True, (200, 200, 200))
            surf.blit(price_surf, (rect.x + 22, rect.y + 38))

            # 玩家现在有多少
            count = accounts[current_user]["inventory"].get(item["name"], 0)
            count_text = info_font.render(f"拥有:{count}", True, NEON_GREEN)
            surf.blit(count_text, (rect.right - 120, rect.y + 22))

            y += item_height + 12

        # 关闭按钮
        close_rect = pygame.Rect(panel_x + panel_w - 40, panel_y + 14, 26, 26)
        pygame.draw.rect(surf, (255, 80, 80), close_rect, border_radius=6)
        close_txt = SMALL_FONT.render("X", True, (255, 255, 255))
        surf.blit(close_txt, close_txt.get_rect(center=close_rect.center))
# 背景-----------------------------------------------------
class Star:
    def __init__(self):
        self.x = random.randint(0, WIDTH)
        self.y = random.randint(0, HEIGHT)
        self.size = random.uniform(0.5, 2.5)
        self.speed = random.uniform(0.2, 1.2)
        self.brightness = random.randint(120, 255)

    def update(self):
        self.x -= self.speed
        if self.x < 0:
            self.x = WIDTH
            self.y = random.randint(0, HEIGHT)

    def draw(self, surf):
        color = (self.brightness, self.brightness, self.brightness)
        pygame.draw.circle(surf, color, (int(self.x), int(self.y)), int(self.size))


class HeartTree:
    def __init__(self):
        self.start_time = time.time()

    def beat(self):
        t = time.time() - self.start_time
        # 模拟节拍
        return 1 + 0.06 * math.sin(t * 4)

    def clamp_color(self, r, g, b, a=None):
        r = max(0, min(255, r))
        g = max(0, min(255, g))
        b = max(0, min(255, b))
        if a is not None:
            a = max(0, min(255, a))
            return (r, g, b, a)
        return (r, g, b)

    def draw_branch(self, surf, x, y, length, angle, depth):
        if depth == 0:
            return

        x2 = x + length * math.cos(angle)
        y2 = y + length * math.sin(angle)

        width = max(1, depth)
        glow = width + 2

        #
        pygame.draw.line(
            surf,
            self.clamp_color(255, 80, 120, 60),
            (int(x), int(y)),
            (int(x2), int(y2)),
            glow
        )

        pygame.draw.line(
            surf,
            self.clamp_color(255, 80 + depth * 15, 120),
            (int(x), int(y)),
            (int(x2), int(y2)),
            width
        )

        self.draw_branch(surf, x2, y2, length * 0.75, angle - 0.45, depth - 1)
        self.draw_branch(surf, x2, y2, length * 0.75, angle + 0.45, depth - 1)

    def draw(self, surf):
        scale = self.beat()
        cx, cy = WIDTH // 2, HEIGHT - 80
        length = 120 * scale

        self.draw_branch(surf, cx, cy, length, -math.pi / 2, 9)


        for i in range(0, 360, 2):
            t = math.radians(i)
            x = 16 * math.sin(t) ** 3
            y = -(13 * math.cos(t) - 5 * math.cos(2 * t)
                  - 2 * math.cos(3 * t) - math.cos(4 * t))

            pygame.draw.circle(
                surf,
                self.clamp_color(255, 50, 100, 200),
                (int(cx + x * 6), int(cy - 140 + y * 6)),
                1
            )


class Petal:
    def __init__(self):
        self.reset()

    def reset(self):
        self.x = random.randint(WIDTH // 2 - 120, WIDTH // 2 + 120)
        self.y = random.randint(60, 200)
        self.vy = random.uniform(0.3, 0.8)
        self.vx = random.uniform(-0.2, 0.2)
        self.size = random.uniform(2, 4)
        self.alpha = random.randint(160, 230)
        self.color = random.choice([
            (255, 180, 220),
            (255, 140, 200),
            (220, 180, 255)
        ])

    def update(self):
        self.x += self.vx
        self.y += self.vy
        if self.y > HEIGHT:
            self.reset()

    def draw(self, surf):
        s = pygame.Surface((10, 10), pygame.SRCALPHA)
        pygame.draw.circle(
            s,
            (*self.color, self.alpha),
            (5, 5),
            int(self.size)
        )
        surf.blit(s, (int(self.x), int(self.y)))

class GlowParticle:
    def __init__(self):
        self.x = random.randint(0, WIDTH)
        self.y = random.randint(0, HEIGHT)
        self.vx = random.uniform(-0.3, 0.3)
        self.vy = random.uniform(-0.1, 0.1)
        self.radius = random.uniform(1, 3)
        self.color = random.choice([NEON_BLUE, NEON_PINK, NEON_GREEN])

    def update(self):
        self.x += self.vx
        self.y += self.vy
        if self.x < 0 or self.x > WIDTH:
            self.vx *= -1

    def draw(self, surf):
        pygame.draw.circle(surf, (*self.color, 50),
                           (int(self.x), int(self.y)), int(self.radius))


# 粒子 
class Particle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.vx = random.uniform(-2, 2)
        self.vy = random.uniform(-2, 2)
        self.life = 20
        self.color = color

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.life -= 1

    def alive(self):
        return self.life > 0

    def draw(self, surf):
        pygame.draw.circle(surf, self.color, (int(self.x), int(self.y)), 3)

#=================
class Button:
    def __init__(self, x, y, w, h, text):
        self.rect = pygame.Rect(x, y, w, h)
        self.text = text
        self.scale = 1.0
        self.target_scale = 1.0
        self.color = NEON_PINK
        self.hovered = False

    def update(self, mouse_pos):
        self.hovered = self.rect.collidepoint(mouse_pos)

        if self.hovered:
            self.target_scale = 1.12
        else:
            self.target_scale = 1.0

        self.scale += (self.target_scale - self.scale) * 0.15

    def draw(self, surf):
        rect = self.rect.copy()
        rect.width = int(rect.width * self.scale)
        rect.height = int(rect.height * self.scale)
        rect.center = self.rect.center
        # hover 时颜色变亮
        draw_color = self.color
        if self.hovered:
            draw_color = tuple(min(255, c + 40) for c in self.color)  # 提亮

        if self.hovered:
            glow = pygame.Surface((rect.width + 20, rect.height + 20), pygame.SRCALPHA)
            pygame.draw.rect(glow, (*self.color, 60),
                             glow.get_rect(), border_radius=16)
            surf.blit(glow, glow.get_rect(center=rect.center))

        pygame.draw.rect(surf, (25, 25, 40), rect, border_radius=14)
        pygame.draw.rect(surf, self.color, rect, 2, border_radius=14)

        txt = UI_FONT.render(self.text, True, (255, 255, 255))
        surf.blit(txt, txt.get_rect(center=rect.center))
# 账号系统
ACCOUNT_FILE = "accounts.json"

def load_accounts():
    if not os.path.exists(ACCOUNT_FILE):
        return {}
    with open(ACCOUNT_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_accounts(data):
    with open(ACCOUNT_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

accounts = load_accounts()
current_user = None
total_coins = 0
# 给所有账号补 inventory
for acc in accounts.values():
    if "inventory" not in acc:
        acc["inventory"] = {
            "magnet": 0,
            "shield": 0,
            "double": 0
        }

# 保证 total_coins 一定存在
for acc in accounts.values():
    if "total_coins" not in acc:
        acc["total_coins"] = 0

save_accounts(accounts)
# 登录输入
input_user = ""
input_pass = ""
login_error = ""

# 对象
player = Player()
coins = []
powerups = []
particles = []
obstacles = []
shop = Shop()
heart_tree = HeartTree()
petals = [Petal() for _ in range(60)]
stars = [Star() for _ in range(120)]
glows = [GlowParticle() for _ in range(40)]

exit_button = Button(WIDTH // 2, 0, 240, 60, "退出游戏")
back_button = Button(WIDTH // 2 - 120, 520, 240, 60, "返回主菜单")
resume_button = Button(WIDTH // 2 - 120, 460, 240, 60, "继续游戏")
continue_button = Button(WIDTH // 2 - 120, 370, 240, 60, "继续挑战")
end_menu_button = Button(WIDTH // 2 - 140, 340, 260, 60, "返回主菜单")

spawn_timer = 0
score = 0
level = 1
LEVEL_TARGET = 200

LEVELS = {
    0: {
        "name": "新手关",
        "speed": 6,
        "types": ["block"],
        "spawn_rate": 90
    },
    1: {
        "name": "进阶关",
        "speed": 8,
        "types": ["block", "short", "wide"],
        "spawn_rate": 70
    },
    2: {
        "name": "高手关",
        "speed": 10,
        "types": ["block", "tall", "air", "spike"],
        "spawn_rate": 50
    }
}

progress = load_progress()
selected_level = 0
current_level = 0
hovered_level = None


# UI 
def draw_hud(surf, player, score, level):
    panel = pygame.Surface((340, 140), pygame.SRCALPHA)
    panel.fill(UI_BG)
    surf.blit(panel, (20, 20))

    surf.blit(UI_FONT.render(f"LEVEL   {level}", True, NEON_PINK), (35, 30))
    surf.blit(UI_FONT.render(f"Score   {score}", True, NEON_BLUE), (35, 65))
    surf.blit(UI_FONT.render(f"Coins   {player.coins}", True, COIN_COLOR), (35, 100))
    draw_inventory(screen)

def draw_controls(surf):
    hints = [
        "[按空格键]宝贝,跳!",
        "[Shift]加速Dash",
        "[Ctrl] Slide",
        "[P] 暂停Pause",
        "[G] 商店Shop",
        "[R] 重开Restart"
    ]
    y = HEIGHT - 160
    for h in hints:
        surf.blit(SMALL_FONT.render(h, True, (160, 160, 160)), (WIDTH - 220, y))
        y += 24

def is_unlocked(lv_id):
    return progress.get(str(lv_id), {}).get("unlocked", False)

def draw_login(surf):
    surf.fill(BG)

    title = TITLE_FONT.render("登录 / 注册", True, NEON_PINK)
    surf.blit(title, (WIDTH // 2 - title.get_width() // 2, 120))

    # 账号
    surf.blit(UI_FONT.render("账号:", True, NEON_BLUE), (400, 220))
    cursor = "_" if int(time.time() * 2) % 2 == 0 else " "
    if input_focus == "user":
        surf.blit(UI_FONT.render(input_user + cursor, True, (255, 255, 255)), (520, 220))
    else:
        surf.blit(UI_FONT.render(input_user, True, (180, 180, 180)), (520, 220))

    # 密码
    surf.blit(UI_FONT.render("密码:", True, NEON_BLUE), (400, 280))
    if input_focus == "pass":
        surf.blit(UI_FONT.render("*" * len(input_pass) + cursor, True, (255, 255, 255)), (520, 280))
    else:
        surf.blit(UI_FONT.render("*" * len(input_pass), True, (180, 180, 180)), (520, 280))

    if login_error:
        err = SMALL_FONT.render(login_error, True, (255, 80, 80))
        surf.blit(err, (WIDTH // 2 - err.get_width() // 2, 350))
    step_text = "请输入账号" if login_step == "user" else "请输入密码"
    surf.blit(SMALL_FONT.render(step_text, True, NEON_GREEN), (WIDTH // 2 - 80, 380))
    tip = SMALL_FONT.render("Enter 登录 / 不存在则自动注册", True, (160, 160, 160))
    surf.blit(tip, (WIDTH // 2 - tip.get_width() // 2, 420))


def draw_level_select(surf):
    global hovered_level

    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((8, 8, 18, 180))
    surf.blit(overlay, (0, 0))


    title = TITLE_FONT.render("   欢迎你,我的宝贝 (坏笑)", True, NEON_PINK)
    sub = SMALL_FONT.render("单机跑酷闯关游戏(Enter或鼠标点击均可进入)", True, NEON_BLUE)


    breath = 1 + 0.008 * math.sin(time.time() * 3)
    tw = int(title.get_width() * breath)
    th = int(title.get_height() * breath)
    title_scaled = pygame.transform.smoothscale(title, (tw, th))

    surf.blit(title_scaled, (WIDTH // 2 - tw // 2, 30))  # 稍微往下一点
    surf.blit(sub, (WIDTH // 2 - sub.get_width() // 2, 120))

    y = 200
    item_spacing = 80

    for i in LEVELS:
        unlocked = is_unlocked(i)
        selected = (i == selected_level)
        if not unlocked:
            color = (100, 60, 60)
            text = f"{i + 1}. {LEVELS[i]['name']} (LOCKED)"
        else:
            color = (255, 255, 255) if selected else (180, 180, 180)
            text = f"{i + 1}. {LEVELS[i]['name']}"

        txt = UI_FONT.render(text, True, color)
        text_x = WIDTH // 2 - txt.get_width() // 2

        if selected and unlocked:
            box_w = txt.get_width() + 44
            box_h = 52
            box_x = WIDTH // 2 - box_w // 2
            box_y = y - 6

            glow = pygame.Surface((box_w + 24, box_h + 24), pygame.SRCALPHA)
            pygame.draw.rect(glow, (*NEON_BLUE, 70), glow.get_rect(), border_radius=12)
            surf.blit(glow, (box_x - 12, box_y - 12))

            pygame.draw.rect(surf, (20, 20, 40, 180), (box_x, box_y, box_w, box_h), border_radius=10)
            pygame.draw.rect(surf, NEON_BLUE, (box_x, box_y, box_w, box_h), 2, border_radius=10)

        surf.blit(txt, (text_x, y))
        y += item_spacing


    exit_button.rect.centerx = WIDTH // 2
    exit_button.rect.top = y + 40


    btn_rect = exit_button.rect
    draw_color = exit_button.color
    if exit_button.hovered:
        draw_color = tuple(min(255, c + 40) for c in exit_button.color)

    if exit_button.hovered:
        glow = pygame.Surface((btn_rect.width + 20, btn_rect.height + 20), pygame.SRCALPHA)
        pygame.draw.rect(glow, (*exit_button.color, 60), glow.get_rect(), border_radius=16)
        surf.blit(glow, glow.get_rect(center=btn_rect.center))

    pygame.draw.rect(surf, (25, 25, 40), btn_rect, border_radius=14)
    pygame.draw.rect(surf, draw_color, btn_rect, 2, border_radius=14)


    txt_surf = UI_FONT.render(exit_button.text, True, (255, 255, 255))
    surf.blit(txt_surf, txt_surf.get_rect(center=btn_rect.center))


    if game_state == STATE_MENU:
        tip_text = "ESC 退出游戏"
    else:
        tip_text = "ESC 返回主菜单"

    tip = SMALL_FONT.render(tip_text, True, (140, 140, 160))
    tip.set_alpha(180)
    surf.blit(tip, (WIDTH // 2 - tip.get_width() // 2, HEIGHT - 50))  # 固定在底部


def draw_pause(surf, back_button):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 160))
    surf.blit(overlay, (0, 0))

    text = TITLE_FONT.render("PAUSED", True, NEON_PINK)
    surf.blit(text, (WIDTH // 2 - text.get_width() // 2, 40))


    hint_title = UI_FONT.render("给我的宝宝的小提示:", True, NEON_BLUE)
    alpha = 180 + 40 * math.sin(time.time() * 3)
    hint_title.set_alpha(int(alpha))
    surf.blit(hint_title, (WIDTH // 2 - hint_title.get_width() // 2, 130))


    # =========================
    tips = [
        ("ESC", "返回主菜单"),
        ("空格键", "跳跃!"),
        ("Shift", "加速 Dash"),
        ("Ctrl", "滑铲"),
        ("G","商店可以买东西哦")
    ]

    start_y = 200
    line_height = 48

    for key, desc in tips:
        key_text = UI_FONT.render(key, True, NEON_GREEN)
        desc_text = UI_FONT.render(desc, True, (220, 220, 220))

        # 右对齐排列
        surf.blit(key_text, (WIDTH // 2 - 120, start_y))
        surf.blit(desc_text, (WIDTH // 2 + 20, start_y))

        start_y += line_height


    back_button.rect.centerx = WIDTH // 2
    back_button.rect.top = start_y + 30
    back_button.draw(surf)
    resume_button.rect.centerx = WIDTH // 2
    resume_button.rect.top = start_y + 100
    resume_button.draw(surf)
    shop.draw(surf, player)

def draw_gameover(surf, score, coins, back_button):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 200))
    surf.blit(overlay, (0, 0))

    surf.blit(TITLE_FONT.render("GAME OVER", True, (255, 80, 80)),
              (WIDTH // 2 - 210, 180))
    surf.blit(UI_FONT.render(f"Score: {score}", True, NEON_BLUE),
              (WIDTH // 2 - 90, 280))
    surf.blit(UI_FONT.render(f"Coins: {coins}", True, COIN_COLOR),
              (WIDTH // 2 - 90, 330))

    back_button.draw(surf)


def draw_level_clear(surf, level):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 180))
    surf.blit(overlay, (0, 0))

    # 文字动画
    global level_clear_scale
    level_clear_scale += (1.2 - level_clear_scale) * 0.08
    scale = int(64 * level_clear_scale)  # 稍小一点

    center_x = WIDTH // 2
    start_y = 160  # 整体下移

    # 标题
    title_font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", scale)
    title = title_font.render("关卡通关!", True, NEON_GREEN)
    surf.blit(title, (center_x - title.get_width() // 2, start_y))

    # 解锁提示
    sub_font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 28)

    if level >= 2:  # 第 3 关及以后
        sub = sub_font.render("还要继续挑战吗?", True, NEON_BLUE)
    else:
        sub = sub_font.render(f"已解锁第 {level + 2} 关", True, NEON_BLUE)

    surf.blit(sub, (center_x - sub.get_width() // 2, start_y + 110))

    # 过一关给的金币奖励
    reward_font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 26)
    reward_text = reward_font.render(
        f"获得金币:{level_clear_reward}", True, COIN_COLOR
    )
    surf.blit(reward_text, (center_x - reward_text.get_width() // 2, start_y + 195))

    # ===== 粒子 =====
    for p in clear_particles:
        pygame.draw.circle(surf, p["color"], (int(p["x"]), int(p["y"])), 4)

    # 继续按钮 =====
    continue_button.rect.centerx = center_x
    continue_button.rect.top = start_y + 265

    if level >= 2:
        continue_button.text = "继续无限闯关"
    else:
        continue_button.text = "继续挑战"

    continue_button.draw(surf)

def draw_end_choice(surf):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 200))
    surf.blit(overlay, (0, 0))

    # 标题
    title = TITLE_FONT.render("恭喜通关!", True, NEON_GREEN)
    surf.blit(title, (WIDTH // 2 - title.get_width() // 2, 160))

    # 提示
    hint = UI_FONT.render("接下来你想无限闯关吗?", True, NEON_BLUE)
    surf.blit(hint, (WIDTH // 2 - hint.get_width() // 2, 240))
def spawn_clear_particles():
    global clear_particles
    clear_particles = []
    for _ in range(120):
        clear_particles.append({
            "x": WIDTH // 2,
            "y": HEIGHT // 2,
            "vx": random.uniform(-6, 6),
            "vy": random.uniform(-6, 6),
            "color": random.choice([NEON_GREEN, NEON_BLUE, NEON_PINK])
        })
def draw_inventory(surf):
    if not current_user:
        return

    inv = accounts[current_user].get("inventory")
    if not inv:
        return

    x = WIDTH - 200
    y = 20

    for name, count in inv.items():
        text = f"{name}: {count}"
        surf.blit(SMALL_FONT.render(text, True, NEON_GREEN), (x, y))
        y += 26
# 主循环
level_clear_scale = 1.0
level_clear_timer = 120
level_clear_reward = 0
running = True
while running:
    clock.tick(FPS)
    screen.fill(BG)
    mouse_pos = pygame.mouse.get_pos()

    for event in pygame.event.get():

            # 退出 ===
        if event.type == pygame.QUIT:
            running = False

                #  键盘 ===
        if event.type == pygame.KEYDOWN:

                # -------- 登录 --------
            if game_state == STATE_LOGIN:
                if event.key == pygame.K_TAB:
                    input_focus = "pass" if input_focus == "user" else "user"

                elif event.key == pygame.K_BACKSPACE:
                    if input_focus == "user":
                        input_user = input_user[:-1]
                    else:
                        input_pass = input_pass[:-1]

                elif event.key == pygame.K_RETURN:
                    if login_step == "user":
                        if input_user.strip() == "":
                            login_error = "账号不能为空"
                        else:
                            login_step = "pass"
                            input_focus = "pass"
                            login_error = ""
                    else:
                        if input_user not in accounts:
                            accounts[input_user] = {
                                "password": input_pass,
                                "total_coins": 0,
                                "inventory": {
                                    "magnet": 0,
                                    "shield": 0,
                                    "double": 0
                                    }
                                }
                            save_accounts(accounts)

                        if accounts[input_user]["password"] != input_pass:
                            login_error = "密码错误"
                        else:
                            current_user = input_user
                            player.coins = accounts[current_user]["total_coins"]
                            game_state = STATE_MENU
                            login_error = ""

                else:
                    if event.unicode:
                        if input_focus == "user":
                            input_user += event.unicode
                        else:
                            input_pass += event.unicode

                # -------- 菜单 --------
            elif game_state == STATE_MENU:
                if event.key == pygame.K_UP:
                    selected_level = max(0, selected_level - 1)
                if event.key == pygame.K_DOWN:
                    selected_level = min(len(LEVELS) - 1, selected_level + 1)
                if event.key == pygame.K_RETURN and is_unlocked(str(selected_level)):
                    current_level = selected_level
                    player.reset_stats()
                    coins.clear()
                    powerups.clear()
                    obstacles.clear()
                    particles.clear()
                    score = 0
                    spawn_timer = 0

                    game_state = STATE_PLAYING

                if event.key == pygame.K_ESCAPE:
                    running = False

                # -------- 游戏中 --------
            elif game_state == STATE_PLAYING:
                if event.key == pygame.K_SPACE:
                    player.jump()
                if event.key == pygame.K_LSHIFT:
                    player.dash()
                if event.key == pygame.K_LCTRL:
                    player.slide()
                if event.key == pygame.K_p:
                    game_state = STATE_PAUSED
                if event.key == pygame.K_g:
                    shop.toggle()
                if event.key == pygame.K_ESCAPE:
                    game_state = STATE_PAUSED
                    # --------使用道具 --------
                # 磁铁
                if event.key == pygame.K_1:
                    if accounts[current_user]["inventory"]["magnet"] > 0:
                        accounts[current_user]["inventory"]["magnet"] -= 1
                        player.activate_powerup("magnet", 300)

                # 护盾
                if event.key == pygame.K_2:
                    if accounts[current_user]["inventory"]["shield"] > 0:
                        accounts[current_user]["inventory"]["shield"] -= 1
                        player.activate_powerup("shield", 300)

                # 多次跳
                if event.key == pygame.K_3:
                    if accounts[current_user]["inventory"]["double"] > 0:
                        accounts[current_user]["inventory"]["double"] -= 1
                        player.activate_powerup("double", 300)
                # -------- 暂停 --------
            elif game_state == STATE_PAUSED:
                if event.key == pygame.K_p:
                    game_state = STATE_PLAYING
                if event.key == pygame.K_g:
                    shop.toggle()

                # -------- Game over --------
            elif game_state == STATE_GAMEOVER:
                if current_user:
                    accounts[current_user]["total_coins"] = player.coins
                    save_accounts(accounts)
                if event.key == pygame.K_r:
                    player.reset_stats()
                    coins.clear()
                    powerups.clear()
                    obstacles.clear()
                    particles.clear()
                    score = 0
                    spawn_timer = 0

                    game_state = STATE_MENU
           # 鼠标 -------------------------
        if event.type == pygame.MOUSEBUTTONDOWN:
            mx, my = event.pos

            if game_state == STATE_LOGIN:
                if account_box.collidepoint(mx, my):
                    login_step = "user"
                    input_focus = "user"
                elif password_box.collidepoint(mx, my):
                    login_step = "pass"
                    input_focus = "pass"
            if shop.open:
                panel_w, panel_h = 540, 400
                panel_x = WIDTH // 2 - panel_w // 2
                panel_y = HEIGHT // 2 - panel_h // 2

                y = panel_y + 85
                item_height = 68
                padding_x = 40

                for item in shop.items:
                    rect = pygame.Rect(
                        panel_x + padding_x,
                        y,
                        panel_w - padding_x * 2,
                        item_height
                    )

                    if rect.collidepoint(mx, my):
                        if player.coins >= item["price"]:
                            player.coins -= item["price"]
                            accounts[current_user]["inventory"][item["name"]] += 1
                            save_accounts(accounts)

                    y += item_height + 12

                # 关闭按钮
                close_rect = pygame.Rect(panel_x + panel_w - 40, panel_y + 14, 26, 26)
                if close_rect.collidepoint(mx, my):
                    shop.open = False

            if game_state == STATE_MENU:
                if exit_button.rect.collidepoint((mx, my)):
                    running = False

                y = 220
                for i in LEVELS:
                    rect = pygame.Rect(WIDTH // 2 - 120, y, 240, 50)
                    if rect.collidepoint(mx, my) and is_unlocked(i):
                        selected_level = i
                        current_level = i

                        player.reset_stats()
                        coins.clear()
                        powerups.clear()
                        obstacles.clear()
                        particles.clear()
                        score = 0
                        spawn_timer = 0

                        game_state = STATE_PLAYING

                    y += 70

            if game_state == STATE_GAMEOVER:

                if back_button.rect.collidepoint(event.pos):
                    player.reset_stats()
                    coins.clear()
                    powerups.clear()
                    obstacles.clear()
                    particles.clear()
                    score = 0
                    spawn_timer = 0

                    game_state = STATE_MENU

            if game_state == STATE_LEVEL_CLEAR:
                if continue_button.rect.collidepoint(mx, my):
                    current_level += 1
                    player.reset_stats()
                    coins.clear()
                    obstacles.clear()
                    particles.clear()
                    clear_particles.clear()
                    score = 0
                    spawn_timer = 0
                    game_state = STATE_PLAYING

            if game_state == STATE_PAUSED:
                if back_button.rect.collidepoint(event.pos):
                    game_state = STATE_MENU
                if resume_button.rect.collidepoint(event.pos):
                    game_state = STATE_PLAYING
            if game_state == STATE_END_CHOICE:
                if end_menu_button.rect.collidepoint(mx, my):
                    game_state = STATE_MENU
                    player.reset_stats()
                    coins.clear()
                    obstacles.clear()
                    particles.clear()
                    score = 0

    #更新
    if game_state == STATE_MENU:
        exit_button.update(mouse_pos)
    if game_state == STATE_PAUSED:
        back_button.update(mouse_pos)
    elif game_state == STATE_GAMEOVER:
        back_button.update(mouse_pos)
    if game_state == STATE_LEVEL_CLEAR:
        level_clear_timer -= 1

        for p in clear_particles:
            p["x"] += p["vx"]
            p["y"] += p["vy"]

    if game_state == STATE_PLAYING:
        player.update()
        player.speed = player.base_speed + current_level

        for star in stars:
            star.update()
        for glow in glows:
            glow.update()

        level_cfg = LEVELS[min(current_level, 2)]

        spawn_timer += 1
        if spawn_timer > level_cfg["spawn_rate"]:
            # 障碍物
            kind = random.choice(level_cfg["types"])
            obstacles.append(Obstacle(WIDTH + 80, kind))

            # 金币类型
            coin_kind = random.choice(list(COIN_TYPES.keys()))

            if random.random() < 0.2:
                # 空中金币
                coins.append(Coin(WIDTH + random.randint(60, 160), 300, coin_kind))
            else:
                # 地面金币
                coins.append(Coin(WIDTH + random.randint(60, 160), None, coin_kind))


            spawn_timer = 0

        for c in coins[:]:
            # 磁铁吸附
            if player.magnet:
                dx = player.rect.centerx - c.x
                dy = player.rect.centery - c.y
                dist = math.hypot(dx, dy)
                if dist != 0:
                    c.x += dx / dist * 6

            coin_rect = pygame.Rect(
                int(c.x) - c.radius,
                c.y - c.radius,
                c.radius * 2,
                c.radius * 2
            )

            if coin_rect.colliderect(player.rect):
                coins.remove(c)
                player.coins += c.value
                score += 20 * c.value
                particles.append(Particle(c.x, c.y, COIN_COLOR))

            c.update(player.speed)

        for o in obstacles:
            o.update(player.speed)


        for o in obstacles[:]:
            if o.rect().colliderect(player.rect):
                if player.shield:
                    obstacles.remove(o)
                    player.shield = False
                elif player.hurt():
                    game_state = STATE_GAMEOVER

        for p in particles[:]:
            p.update()
            if not p.alive():
                particles.remove(p)

        if score >= (current_level + 1) * LEVEL_TARGET:
            if current_level == 2:  # 高手关
                game_state = STATE_END_CHOICE
                level_clear_scale = 1.0
                spawn_clear_particles()
            else:
                next_level = current_level + 1
                if str(next_level) not in progress:
                    progress[str(next_level)] = {"unlocked": True}
                    save_progress(progress)
            if str(next_level) not in progress:
                progress[str(next_level)] = {"unlocked": True}
                save_progress(progress)
            reward = (current_level + 1) * 50
            player.coins += reward
            level_clear_reward = reward

            game_state = STATE_LEVEL_CLEAR
            level_clear_timer = 300
            level_clear_scale = 1.0
            spawn_clear_particles()

    # 绘制
    if game_state == STATE_LOGIN:
        draw_login(screen)

    if game_state == STATE_MENU:
        screen.fill(BG)

        for p in petals:
            p.update()
            p.draw(screen)

        heart_tree.draw(screen)
        draw_level_select(screen)
        exit_button.draw(screen)

    elif game_state == STATE_PLAYING:
        for star in stars:
            star.draw(screen)
        for glow in glows:
            glow.draw(screen)

        player.draw(screen)
                     #护盾特效
        if player.shield:
            pygame.draw.rect(
                screen,
                (255, 80, 80),
                player.rect.inflate(12, 12),
                3
            )
                    # 磁铁特效
        if player.magnet:
            pygame.draw.circle(
                screen,
                (0, 255, 255, 80),
                player.rect.center,
                130,
                2
                )
        for o in obstacles:
            pygame.draw.rect(screen, (255, 80, 80), o.rect())
        for c in coins:
            c.draw(screen)

        for p in particles:
            p.draw(screen)

        pygame.draw.line(screen, (80, 80, 80), (0, 470), (WIDTH, 470), 4)
        pygame.draw.line(screen, NEON_BLUE, (0, 470), (WIDTH, 470), 2)
        pygame.draw.line(screen, NEON_PINK, (0, 472), (WIDTH, 472), 1)

        draw_hud(screen, player, score, current_level + 1)
        draw_controls(screen)
        shop.draw(screen, player)

    elif game_state == STATE_PAUSED:
        draw_pause(screen, back_button)
    elif game_state == STATE_END_CHOICE:
        draw_end_choice(screen)
        end_menu_button.draw(screen)


    elif game_state == STATE_GAMEOVER:
        draw_gameover(screen, score, player.coins, back_button)
    elif game_state == STATE_LEVEL_CLEAR:
        draw_level_clear(screen, current_level)
    pygame.display.flip()

pygame.quit()

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

问题1:在 Windows 中文输入法下,登录时键盘输入,小写字母经常不触发或触发延迟;另外,代码报错

  • 解决方案:特别注意中英文状态,就像双引号、括号,中文状态输入会报错,得切换英文状态

问题2: 输完代码运行好几次报错,尤其是涉及长代码

  • 解决方案:注意代码缩进细节和代码放的位置!

问题3:shop界面和道具,最开始就是纯摆设

  • 解决方案:借助大模型的帮助,不断改代码,同时要在界面上有交互提示以及道具要真正体现功能

问题4:字体问题导致报错或者运行显示一堆方框(界面看上去反正是方框)

  • 解决方案:找系统C盘自己的字体

屏幕截图 2026-06-11 130332

其他(感悟、思考等)

(一)关于这次综合实践的一些感想

这次综合实践,有很多切身感受。所以在全课总结之前,先写一下这次实验的一些感悟。
最主要的就是大模型运用的问题。

前三次实验总有一个实验环节,利用大模型生成实验相关代码,并与自写代码比较优缺。而我在前三次实验过程中,都是把自写代码给AI,让它一次性根据要求生成代码,我觉得这一过程反而是前三次实验最省时的环节,因为大模型几秒钟出结果,我直接拿过来运行然后截图即可。这次综合实践同样借助了大模型,但是和前三次实验截然不同。前三次实验每次都有自写代码作为依托,且我只需要对话一次就得到我所需要的。这次综合实践没有了现成的依托,需要把详细的目的指令告诉AI,指令要尽量细致、全面。例如,我第一次只是简单告诉它,帮我生成一款python小游戏代码,结果生成的根本不是我想要的。此外,只有大模型的话,想要一次性就生成超长代码,还是比较困难的,大模型最多给一个框架。

在与大模型对话过程中也出现了一些问题。大模型生成的代码运行可能存在问题、对话不到位可能导致代码覆盖之前的功能,越改越乱、还有其他我在上面“2.3实验过程”提到的那些问题。我想说,AI 遇强则强,一定要把想法细致明确的告诉他,同时还是要具备一些基本知识,不然想依赖大模型都不是很顺利。

此外就是在初版代码框架基础上,不断补充、修改、完善的过程,包括将我自己的想法告诉大模型,包括自己调整一些参数,这个过程同样收获很多。

(二)全课总结、感想体会、意见建议

1.全课总结与感想

学到了啥
  • ch1.第一节课对python初步认识了解
    a.知道python 是什么?跨平台、解释型语言、动态数据类型、高级程序语言。 与C语言区别:C语言是编译型语言,python解释型语言(老师举例:蛋炒饭、盖浇饭)
    b.python3.x 支持中文字符串
    c.python开发环境:自带IDLE、第三方工具 如pycharm、VScode,并完成python开发环境的搭建
    d.了解python的一些应用场景:Web 开发、大数据处理、人工智能、爬虫、游戏开发等
    e.博客园、Gitee相关技能,包括pycharm上面怎么接入自己的Gitee

  • ch2.接着学习了python一些基本语法
    a. python与C语言、Java的注释的区别,代码缩进(!!!),编码规范,注意与C语言的区别
    b. 标识符、变量、常量
    c. 基本数据类型:数字类型、字符串类型、布尔类型、数据类型转换(这部分类比C语言)
    e. 运算符:算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符(单目运算符 ~ 、+ 、- 取反、正号和负号;算术运算符 * 、/ 、% 、// 乘、除、求余;位运算符 << 、>> 左移、右移 ,& 按位与, ^ 按位异或, | 按位或;比较运算符 < 、<= 、> 、>= 、!= 、==), 注意优先级!
    f. python的输入输出,input、print,跟着老师在课上敲基础代码

  • ch3.流程控制语句
    顺序、分支、循环结构
    学习各种语句,选择语句、循环语句、空语句
    了解条件表达式,如三目操作符 条件 ? 表达式1 : 表达式2

  • 通过学习ch1-ch3的知识,编写猜数字、剪刀石头布小游戏,基于猜数字游戏学会重要的断点调试技能(例如,通过调试提前知道系统随机生成的数字),学会打包.exe

  • ch4~ch5.序列、字符串
    a.什么是序列 ,列表 + 元组 + 集合 +字典
    b.列表list1 = [1,2,3,4,5] 元组tuple1=(1,2,3,4,5)字典dictionary1 = dict(zip(list1 , tuple1))={1: 1, 2: 2, 3: 3, 4: 4, 5:5} 集合set1 ={1,2,3,4,5} = {1,1,1,1,2,3,4,5}
    c.学习序列的特性。索引,序列中每个元素的编号;切片,切片操作是访问序列中元素的另外一种方法,可以访问一定范围内的元素。
    e.序列相加,用+把两个序列合并,相同的元素不会被去掉;序列乘法,乘以n相当于n个序列相加;集合不能相加,不能相乘;
    f.学习各数据结构对应的创建、删除操作,对里面元素的增、删、查、改操作,推导式,四者的区别等。
    g.学习关于字符串的相关知识、操作和处理方法,在字符串的基础上学习了正则表达式,以及一些查找、替换等操作的方法,正则表达式的字符,模式匹配等等

  • ch6~7.函数、面向对象、模块
    a.区别函数的实参、形参,了解函数传参功能
    b.对象:某一个具体的事物、实体或事例;类:具有相同属性和行为的一些对象的统称或集合;类的实例化:把(类)变成(对象)的过程。
    c. 包和模块:模块就是一个.py文件,包就是一个装着多个.py文件的文件夹
    d. 面向对象三要素:封装、继承、多态
    e. 类的方法和函数的区别:

def print_name(student):#普通函数:独立存在,想传啥参数自己定
    print(student.name)
class Student:#类的方法:属于Student类,第一个参数是self
    def __init__(self, name):
        self.name = name
    def print_name(self):   # 自带self,不用额外传student
        print(self.name)
stu = Student("张三")    #调用区别:
print_name(stu)      # 函数要手动传对象
stu.print_name()     # 方法直接用对象调,self自动传

f. 学习了解__init()__是什么:Python当中类的构造方法,初始化方法;有啥用:给新创建的对象初始化属性。
例如:

class Student:
    def __init__(self, name, score):  # 初始化姓名和成绩
        self.name = name
        self.score = score
创建对象时自动执行 __init__
stu = Student("张三", 100)
print(stu.name)  # 张三
  • 基于前面的知识,结合后面的文件操作,编写图书管理系统程序、进行设计计算器实验

  • ch8.异常处理调试、文件操作、数据库
    a.处理异常的四种方式:try-except;try-except-else;try-except-finally; raise
    b.学习了解冯诺依曼的体系架构;理解了内存与外存的区别
    c.学习了文件的操作,如open、write、seek、close等等,实现文本及二进制文件的系列操作
    d.了解结构化数据和非结构化数据;MySQL;SQLite;
    f.数据库操作4个基本操作命令:增删改查

  • ch9.爬虫、socket通信等对我来说已经比较高阶一点的知识
    a.了解啥是个爬虫;还有反爬虫;不要随便乱爬虫!
    b.初步了解了爬虫的基本原理与工作流程(爬取 + 解析)。常见的爬取类库,例如:requests;httpx;。常见的解析模块,例如,BeautifulSoup; Selenium
    c.反爬虫要使用一个Headers; robots.txt 告诉爬虫,哪些页面能爬,哪些不能爬。

···

  • 还有就是socket实验,改IP,双人加密通信,并尝试文件读写。

  • ch last.其他
    撰写实验报告、Gitee技能
    大模型的合理运用
    还有在实验2和实验3中学习使用华为云服务器
    英语打卡

感想

这一学期的Python课程结束了,发觉时间过得真快啊。王老师上课氛围很活跃,讲解到位,也比较幽默,和同学们互动得特别好,解答耐心。上完这一学期的课,我对于编程方面的知识又增加了,对于python的基本知识也有了系统了解(尽管后期确实有些跟不上了),而且在课程中也掌握了很多其他方面的东西。比如,之前没有接触过的Gitee,博客园,还有用的不是很熟的华为云服务器。在学习过程中也克服了很多困难,比如刚开始,Gitee始终接不上,实验三那些改IP、加密解密不太会等,通过请教老师、学长或是借助大模型,这些过程我认为是更加珍贵的。
总之,感恩相遇blackwall!

2.意见和建议

(1)课前签到改为课中不定时签到,增加签退(坏笑)
(2)我感觉老师讲的有点快,尤其到了后面的实验三,还有爬虫等等,有点吸收不了,可能是我编程基础一般,再加上课时有限的问题。我觉得学习通的那些各种视频资料可以选取精华当成平时任务,布置成任务点,或者大家有额外学习或实践,都可以加分,(主要鼓励大家根据自己水平去学,跟上课程)
(3)考虑增加小组合作类型的作业或者实践
(4)可以考虑在期末增加一个综合项目展示环节(不强制答辩,可自愿报名),让大家有机会展示自己或小组的作品,增强成就感和学习动力

参考资料

posted @ 2026-06-06 17:40  Python2026  阅读(31)  评论(0)    收藏  举报