Python自我娱乐小游戏

一、效果图

​​​​

​​

二、代码

python 复制代码
# -*- coding:utf-8 -*-
"""
作者:杨桃清
日期: 2025年04日28  02:50:40
"""
import pygame
import tkinter as tk
from tkinter import ttk, messagebox
import random
import json
import os
import sys
from typing import Dict, List, Tuple, Optional

# 常量定义
SCREEN_SIZE = (400, 600)  # 统一使用游戏界面尺寸
RECORD_FILE = "flappy_bird_record.json"
DEFAULT_SETTINGS = {
    "gravity": 0.2,
    "bird_speed": -7,
    "pipe_frequency": 1500,
    "pipe_gap_min": 120,
    "pipe_gap_max": 200,
    "pipe_speed": 2,
    "difficulty_step": 0.1,
}


class GameSettingsWindow:
    """参数设置窗口"""

    def __init__(self, parent):
        self.top = tk.Toplevel(parent)
        self.top.title(" 游戏参数设置")
        self.top.resizable(False, False)
        self.settings = DEFAULT_SETTINGS.copy()
        self._create_widgets()

    def _create_widgets(self):
        # 重力设置
        ttk.Label(self.top, text="重力:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
        self.gravity_entry = ttk.Entry(self.top)
        self.gravity_entry.insert(0, str(self.settings["gravity"]))
        self.gravity_entry.grid(row=0, column=1, padx=5, pady=5)

        # 跳跃力度
        ttk.Label(self.top, text="跳跃力度:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
        self.bird_speed_entry = ttk.Entry(self.top)
        self.bird_speed_entry.insert(0, str(self.settings["bird_speed"]))
        self.bird_speed_entry.grid(row=1, column=1, padx=5, pady=5)

        # 柱子生成频率
        ttk.Label(self.top, text="柱子频率(ms):").grid(row=2, column=0, padx=5, pady=5, sticky="e")
        self.pipe_freq_entry = ttk.Entry(self.top)
        self.pipe_freq_entry.insert(0, str(self.settings["pipe_frequency"]))
        self.pipe_freq_entry.grid(row=2, column=1, padx=5, pady=5)

        # 柱子间隙
        ttk.Label(self.top, text="柱子最小间隙:").grid(row=3, column=0, padx=5, pady=5, sticky="e")
        self.pipe_gap_min_entry = ttk.Entry(self.top)
        self.pipe_gap_min_entry.insert(0, str(self.settings["pipe_gap_min"]))
        self.pipe_gap_min_entry.grid(row=3, column=1, padx=5, pady=5)

        ttk.Label(self.top, text="柱子最大间隙:").grid(row=4, column=0, padx=5, pady=5, sticky="e")
        self.pipe_gap_max_entry = ttk.Entry(self.top)
        self.pipe_gap_max_entry.insert(0, str(self.settings["pipe_gap_max"]))
        self.pipe_gap_max_entry.grid(row=4, column=1, padx=5, pady=5)

        # 柱子移动速度
        ttk.Label(self.top, text="柱子速度:").grid(row=5, column=0, padx=5, pady=5, sticky="e")
        self.pipe_speed_entry = ttk.Entry(self.top)
        self.pipe_speed_entry.insert(0, str(self.settings["pipe_speed"]))
        self.pipe_speed_entry.grid(row=5, column=1, padx=5, pady=5)

        # 难度增幅
        ttk.Label(self.top, text="难度增幅:").grid(row=6, column=0, padx=5, pady=5, sticky="e")
        self.difficulty_step_entry = ttk.Entry(self.top)
        self.difficulty_step_entry.insert(0, str(self.settings["difficulty_step"]))
        self.difficulty_step_entry.grid(row=6, column=1, padx=5, pady=5)

        # 按钮区域
        btn_frame = ttk.Frame(self.top)
        btn_frame.grid(row=7, column=0, columnspan=2, pady=10)

        ttk.Button(btn_frame, text="保存", command=self._save_settings).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="恢复默认", command=self._reset_defaults).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="取消", command=self.top.destroy).pack(side=tk.RIGHT, padx=5)

    def _save_settings(self):
        """保存参数设置"""
        try:
            self.settings = {
                "gravity": float(self.gravity_entry.get()),
                "bird_speed": float(self.bird_speed_entry.get()),
                "pipe_frequency": int(self.pipe_freq_entry.get()),
                "pipe_gap_min": int(self.pipe_gap_min_entry.get()),
                "pipe_gap_max": int(self.pipe_gap_max_entry.get()),
                "pipe_speed": int(self.pipe_speed_entry.get()),
                "difficulty_step": float(self.difficulty_step_entry.get()),
            }
            self.top.destroy()
        except ValueError:
            messagebox.showerror(" 错误", "请输入有效的数值参数")

    def _reset_defaults(self):
        """恢复默认设置"""
        self.settings = DEFAULT_SETTINGS.copy()
        self.gravity_entry.delete(0, tk.END)
        self.gravity_entry.insert(0, str(self.settings["gravity"]))
        self.bird_speed_entry.delete(0, tk.END)
        self.bird_speed_entry.insert(0, str(self.settings["bird_speed"]))
        self.pipe_freq_entry.delete(0, tk.END)
        self.pipe_freq_entry.insert(0, str(self.settings["pipe_frequency"]))
        self.pipe_gap_min_entry.delete(0, tk.END)
        self.pipe_gap_min_entry.insert(0, str(self.settings["pipe_gap_min"]))
        self.pipe_gap_max_entry.delete(0, tk.END)
        self.pipe_gap_max_entry.insert(0, str(self.settings["pipe_gap_max"]))
        self.pipe_speed_entry.delete(0, tk.END)
        self.pipe_speed_entry.insert(0, str(self.settings["pipe_speed"]))
        self.difficulty_step_entry.delete(0, tk.END)
        self.difficulty_step_entry.insert(0, str(self.settings["difficulty_step"]))

    def get_settings(self) -> Dict:
        """获取当前设置"""
        return self.settings


class MainMenu:
    """游戏主菜单"""

    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode(SCREEN_SIZE)
        pygame.display.set_caption("Flappy  Bird")
        self.clock = pygame.time.Clock()
        self.settings = DEFAULT_SETTINGS.copy()
        self._load_resources()

    def _load_resources(self):
        """加载游戏资源"""
        # 字体
        try:
            self.title_font = pygame.font.SysFont("simhei", 48)
            self.menu_font = pygame.font.SysFont("simhei", 36)
            self.info_font = pygame.font.SysFont("simhei", 24)
        except:
            self.title_font = pygame.font.SysFont(None, 48)
            self.menu_font = pygame.font.SysFont(None, 36)
            self.info_font = pygame.font.SysFont(None, 24)

        # 颜色
        self.colors = {
            "background": (240, 240, 240),
            "title": (30, 30, 30),
            "button": (70, 130, 180),
            "button_hover": (100, 160, 210),
            "text": (255, 255, 255),
            "shadow": (100, 100, 100),
        }

        # 加载记录
        self.max_distance = self._load_record()

    def _load_record(self) -> float:
        """加载历史记录"""
        if os.path.exists(RECORD_FILE):
            try:
                with open(RECORD_FILE, "r") as f:
                    return json.load(f).get("max_distance", 0)
            except:
                return 0
        return 0

    def _draw_menu(self):
        """绘制主菜单"""
        self.screen.fill(self.colors["background"])

        # 绘制标题
        title = self.title_font.render("Flappy  Bird", True, self.colors["title"])
        title_rect = title.get_rect(center=(SCREEN_SIZE[0] // 2, 120))
        self.screen.blit(title, title_rect)

        # 绘制按钮
        button_data = [
            ("开始游戏", (SCREEN_SIZE[0] // 2, 250)),
            ("退出游戏", (SCREEN_SIZE[0] // 2, 350)),
            ("设置", (SCREEN_SIZE[0] - 80, 40)),
        ]

        self.buttons = []
        mouse_pos = pygame.mouse.get_pos()

        for text, pos in button_data:
            if text == "设置":
                button_rect = pygame.Rect(0, 0, 80, 40)
            else:
                button_rect = pygame.Rect(0, 0, 200, 60)

            button_rect.center = pos

            # 按钮状态
            hover = button_rect.collidepoint(mouse_pos)
            color = self.colors["button_hover"] if hover else self.colors["button"]

            # 绘制按钮
            pygame.draw.rect(self.screen, color, button_rect, border_radius=10)
            pygame.draw.rect(self.screen, self.colors["shadow"], button_rect, 2, border_radius=10)

            # 绘制按钮文字
            if text == "设置":
                text_surf = self.info_font.render(text, True, self.colors["text"])
            else:
                text_surf = self.menu_font.render(text, True, self.colors["text"])
            text_rect = text_surf.get_rect(center=button_rect.center)
            self.screen.blit(text_surf, text_rect)

            self.buttons.append((text, button_rect))

        # 显示最高记录
        if self.max_distance > 0:
            record_text = self.info_font.render(f" 最远记录: {int(self.max_distance)} 米", True, self.colors["title"])
            record_rect = record_text.get_rect(midleft=(20, SCREEN_SIZE[1] - 30))
            self.screen.blit(record_text, record_rect)

        pygame.display.flip()

    def run(self):
        """运行主菜单循环"""
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

                if event.type == pygame.MOUSEBUTTONDOWN:
                    mouse_pos = pygame.mouse.get_pos()

                    for text, rect in self.buttons:
                        if rect.collidepoint(mouse_pos):
                            if text == "开始游戏":
                                self._start_game()
                            elif text == "退出游戏":
                                running = False
                            elif text == "设置":
                                self._open_settings()

            self._draw_menu()
            self.clock.tick(60)

        pygame.quit()
        sys.exit()

    def _open_settings(self):
        """打开设置窗口"""
        root = tk.Tk()
        root.withdraw()  # 隐藏主窗口

        settings_window = GameSettingsWindow(root)
        root.wait_window(settings_window.top)

        if hasattr(settings_window, 'settings'):
            self.settings = settings_window.settings

    def _start_game(self):
        """启动游戏"""
        game = GameEngine(self.settings)
        game.run()

        # 更新记录
        new_record = self._load_record()
        if new_record > self.max_distance:
            self.max_distance = new_record


class GameEngine:
    """游戏引擎"""

    def __init__(self, settings: Dict):
        self.settings = settings
        self.screen = pygame.display.set_mode(SCREEN_SIZE)
        pygame.display.set_caption("Flappy  Bird - 游戏中")
        self.clock = pygame.time.Clock()

        # 游戏状态
        self.bird = pygame.Rect(100, 250, 30, 30)
        self.bird_speed = 0
        self.pipes = []
        self.distance = 0
        self.score = 0
        self.last_pipe_time = 0
        self.max_distance = self._load_record()

        # 资源加载
        self._load_resources()

    def _load_resources(self):
        """加载游戏资源"""
        # 字体
        try:
            self.font = pygame.font.SysFont("simhei", 24)
            self.large_font = pygame.font.SysFont("simhei", 36)
        except:
            self.font = pygame.font.SysFont(None, 24)
            self.large_font = pygame.font.SysFont(None, 36)

        # 颜色
        self.colors = {
            "background": (255, 255, 255),
            "bird": (0, 0, 255),
            "pipe": (0, 180, 0),
            "text": (0, 0, 0),
            "game_over_bg": (240, 240, 240),
            "button": (70, 130, 180),
            "button_hover": (100, 160, 210),
            "button_text": (255, 255, 255),
            "overlay": (0, 0, 0, 180),
        }

    def _load_record(self) -> float:
        """加载历史记录"""
        if os.path.exists(RECORD_FILE):
            try:
                with open(RECORD_FILE, "r") as f:
                    return json.load(f).get("max_distance", 0)
            except:
                return 0
        return 0

    def _save_record(self):
        """保存新记录"""
        if self.distance > self.max_distance:
            self.max_distance = self.distance
            with open(RECORD_FILE, "w") as f:
                json.dump({"max_distance": self.max_distance}, f)

    def _handle_events(self) -> bool:
        """处理游戏事件"""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    self.bird_speed = self.settings["bird_speed"]
        return True

    def _update_game_state(self):
        """更新游戏状态"""
        # 更新小鸟位置
        self.bird_speed += self.settings["gravity"]
        self.bird.y += self.bird_speed
        self.distance += 0.05

        # 更新记录
        if self.distance > self.max_distance:
            self._save_record()

        # 动态难度调整
        difficulty = 1 + self.settings["difficulty_step"] * (self.distance // 100)
        pipe_freq = max(500, self.settings["pipe_frequency"] / difficulty)
        gap_min = max(80, self.settings["pipe_gap_min"] - (difficulty - 1) * 10)
        gap_max = max(150, self.settings["pipe_gap_max"] - (difficulty - 1) * 15)
        pipe_speed = min(5, self.settings["pipe_speed"] + (difficulty - 1) * 0.5)

        # 生成新柱子
        current_time = pygame.time.get_ticks()
        if current_time - self.last_pipe_time > pipe_freq:
            gap = random.randint(int(gap_min), int(gap_max))
            height = random.randint(50, SCREEN_SIZE[1] - gap - 50)
            self.pipes.append([
                pygame.Rect(SCREEN_SIZE[0], 0, 50, height),
                pygame.Rect(SCREEN_SIZE[0], height + gap, 50, SCREEN_SIZE[1] - height - gap)
            ])
            self.last_pipe_time = current_time

        # 移动柱子
        for pipe in self.pipes[:]:
            pipe[0].x -= pipe_speed
            pipe[1].x -= pipe_speed
            if pipe[0].x < -50:
                self.pipes.remove(pipe)
                self.score += 1

    def _check_collisions(self) -> bool:
        """检测碰撞"""
        if self.bird.y <= 0 or self.bird.y >= SCREEN_SIZE[1]:
            return True
        return any(
            self.bird.colliderect(pipe[0]) or self.bird.colliderect(pipe[1])
            for pipe in self.pipes
        )

    def _draw_game(self):
        """绘制游戏画面"""
        self.screen.fill(self.colors["background"])

        # 绘制小鸟
        pygame.draw.rect(self.screen, self.colors["bird"], self.bird)

        # 绘制柱子
        for pipe in self.pipes:
            pygame.draw.rect(self.screen, self.colors["pipe"], pipe[0])
            pygame.draw.rect(self.screen, self.colors["pipe"], pipe[1])

        # 显示游戏信息
        info = [
            f"得分: {self.score}",
            f"距离: {int(self.distance)} 米",
            f"记录: {int(self.max_distance)} 米"
        ]
        for i, text in enumerate(info):
            text_surface = self.font.render(text, True, self.colors["text"])
            self.screen.blit(text_surface, (10, 10 + i * 30))

        pygame.display.flip()

    def _show_game_over(self) -> bool:
        """显示结束界面并返回是否重玩"""
        overlay = pygame.Surface(SCREEN_SIZE, pygame.SRCALPHA)
        overlay.fill(self.colors["overlay"])
        self.screen.blit(overlay, (0, 0))

        # 游戏结果文本
        texts = [
            ("游戏结束!", 36, (SCREEN_SIZE[0] // 2, 150)),
            (f"本次得分: {self.score}", 30, (SCREEN_SIZE[0] // 2, 220)),
            (f"飞行距离: {int(self.distance)} 米", 30, (SCREEN_SIZE[0] // 2, 270)),
            (f"最远记录: {int(self.max_distance)} 米", 30, (SCREEN_SIZE[0] // 2, 320))
        ]

        for text, size, pos in texts:
            text_surface = self.large_font.render(text, True, (255, 255, 255))
            text_rect = text_surface.get_rect(center=pos)
            self.screen.blit(text_surface, text_rect)

        # 创建按钮
        buttons = [
            ("重新开始", (SCREEN_SIZE[0] // 2 - 110, 400)),
            ("返回菜单", (SCREEN_SIZE[0] // 2 + 110, 400))
        ]
        button_rects = []

        mouse_pos = pygame.mouse.get_pos()

        for text, pos in buttons:
            rect = pygame.Rect(0, 0, 150, 50)
            rect.center = pos

            # 按钮悬停效果
            btn_color = self.colors["button_hover"] if rect.collidepoint(mouse_pos) else self.colors["button"]

            pygame.draw.rect(self.screen, btn_color, rect, border_radius=10)
            pygame.draw.rect(self.screen, (255, 255, 255), rect, 2, border_radius=10)

            text_surface = self.font.render(text, True, self.colors["button_text"])
            text_rect = text_surface.get_rect(center=rect.center)
            self.screen.blit(text_surface, text_rect)

            button_rects.append((text, rect))

        pygame.display.flip()

        # 处理按钮点击
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return False

                if event.type == pygame.MOUSEBUTTONDOWN:
                    pos = pygame.mouse.get_pos()
                    for text, rect in button_rects:
                        if rect.collidepoint(pos):
                            if text == "重新开始":
                                return True
                            else:
                                return False

            self.clock.tick(60)

    def run(self):
        """运行游戏主循环"""
        running = True
        while running:
            self.clock.tick(60)

            if not self._handle_events():
                break

            self._update_game_state()

            if self._check_collisions():
                if self._show_game_over():
                    # 重新开始游戏
                    self.__init__(self.settings)
                    continue
                else:
                    break

            self._draw_game()

        pygame.display.set_caption("Flappy  Bird")
        return False


if __name__ == "__main__":
    menu = MainMenu()
    menu.run()

相关推荐
love530love14 分钟前
Windows避坑部署CosyVoice多语言大语言模型
人工智能·windows·python·语言模型·自然语言处理·pycharm
prinrf('千寻)1 小时前
MyBatis-Plus 的 updateById 方法不更新 null 值属性的问题
java·开发语言·mybatis
m0_555762901 小时前
Qt缓动曲线详解
开发语言·qt
掘金-我是哪吒2 小时前
分布式微服务系统架构第132集:Python大模型,fastapi项目-Jeskson文档-微服务分布式系统架构
分布式·python·微服务·架构·系统架构
揽你·入怀2 小时前
数据结构:ArrayList简单实现与常见操作实例详解
java·开发语言
AA-代码批发V哥2 小时前
Math工具类全面指南
java·开发语言·数学建模
xhdll2 小时前
egpo进行train_egpo训练时,keyvalueError:“replay_sequence_length“
python·egpo
Nobkins3 小时前
2021ICPC四川省赛个人补题ABDHKLM
开发语言·数据结构·c++·算法·图论
Cchaofan3 小时前
lesson01-PyTorch初见(理论+代码实战)
人工智能·pytorch·python
网络小白不怕黑3 小时前
Python Socket编程:实现简单的客户端-服务器通信
服务器·网络·python