🥚 故事从一次“远程会议”开始……

“各位,我们下个季度的目标要更具挑战性!”
“虽然资金紧张,但我们要相信团队的力量!”
“目前工资暂时不动,但后续会有绩效激励!”

听着老板在会议里激情澎湃地画着饼,我默默关掉摄像头,打开了……egg_tapper.py

“工资不动?那我就把这颗蛋敲裂!”


💡 项目简介

这是一个由 Python + pygame 打造的 解压小游戏 ——《敲鸡蛋:怒气值爆表版》。

它不仅能模拟真实敲击,还有 爆裂粒子、怒气条成长、吐槽弹幕、成就系统,一边敲蛋一边爽快吐槽,极大缓解打工精神内耗!


🌟 核心功能亮点

功能项 说明
✅ 鸡蛋敲击反馈 每点一下,粒子飞溅、功德+1
🎧 背景音乐 自动播放本地 mp3/ogg 文件,可静音
🐣 鸡蛋成长机制 每敲 50 下变黑变大,视觉反馈拉满
📈 怒气值条 右侧实时展示鸡蛋“发黑程度”
🗯️ 吐槽老板金句 每敲一下弹一句爆笑吐槽,24 句循环
📊 历史记录保存 保留最高敲击记录(显示于左上角)
🖱️ 快捷键控制 空格/鼠标敲、M 静音、S 截图、Q 退出

🧪 如何运行

1. 安装依赖

pip install pygame

2. 资源准备(可选)

将以下文件放到脚本同目录下:

  • egg.png:鸡蛋图片(建议 512×512)

  • crack.wav:敲击音效(每次点击触发)

  • bgm.mp3 / bgm.ogg:背景音乐

  • font.ttf:支持中文的字体文件(推荐 NotoSansCJK)

3. 启动游戏

python egg_tapper.py


⌨️ 快捷键说明

快捷键 功能
鼠标点击 / 空格键 敲蛋!
M 开/关背景音乐
S 截图保存
Q / Esc 退出游戏

项目结构

egg_tapper/
├── egg_tapper.py              # 主程序(核心逻辑代码)
├── assets/                    # 资源目录(建议集中管理)
│   ├── egg.png                # 鸡蛋图片(可替换)
│   ├── crack.wav             # 敲击音效(每次点击)
│   ├── bgm.mp3               # 背景音乐(支持 mp3 / ogg)
│   └── font.ttf              # 中文字体(避免乱码)
├── egg_save.json             # 本地保存文件(自动生成)

egg_tapper.py  的完整代码

"""
敲鸡蛋 (Egg Tapper) — v5 美化·持久纪录版
========================================

本版亮点
--------
1. **历史最高分持久化** (`egg_save.json`)
   * 每次启动 `本局` 计数从 0 开始;`最高` 会读取并刷新本地最高纪录。
2. **界面排版优化**
   * 统计区左右居中、怒气尺标题完整、吐槽条移至底部居中。
3. 原有功能全保留:怒气升级、颜色变深、体积增大、音效、弹幕、粒子、背景音乐、截图等。

运行
----
```bash
pip install pygame
python egg_tapper.py
```
"""

from __future__ import annotations
import json, math, random, sys
from pathlib import Path
from dataclasses import dataclass
from typing import List
import pygame

# -------------------- 基础常量 -------------------- #
BASE_W, BASE_H = 560, 720             # 窗口尺寸
GAUGE_W        = 60                   # 右侧怒气尺宽度
GAUGE_X        = BASE_W - GAUGE_W
SAVE_FILE      = Path("egg_save.json")
WHITE, BLACK   = (255, 255, 255), (0, 0, 0)
DEFAULT_COLOR  = (237, 229, 216)      # 蛋壳初始 RGB
PARTICLE_COLS  = [(255,199,44), (255,82,82), (69,210,252), (138,201,38)]
MAX_PARTICLES  = 600                  # 控制粒子总量

# -------------------- 文本资源 -------------------- #
PHRASES = [
    "越敲越强!", "功不唐捐", "鸡你太美", "再来亿下", "蛋·行不行", "我裂开了", "狂敲不止",
    "蛋定如山", "燃烧吧小宇宙", "再来一打!",
]

BOSS_LINES = [
    "老板总喊着冲刺目标,工资却像蜗牛在爬道",
    "老板画的大饼飘啊飘,钱包瘪瘪心发焦",
    "老板说要一起向前跑,自己却在后方享逍遥",
    "老板口才赛过脱口秀,兑现承诺却没个头",
    "老板天天喊着要突破,员工钱包原地没挪窝",
    "老板描绘前景似仙境,实际收入让人泪满襟",
    "老板决策好似捉迷藏,员工跟着晕头又转向",
    "老板豪言壮志震天响,工资待遇悄悄往下放",
    "老板总说公司是大家,可好处都往自己抓",
    "老板指挥若定有方略,员工累成陀螺钱不多",
    "老板谈起理想滔滔不绝,说到加薪哑口无言",
    "老板规划未来头头是道,当下工资让人想吐槽",
    "老板说要共享胜利果,最后只有苦劳没收获",
    "老板总把困难轻松说,员工加班加点没处躲",
    "老板描绘愿景美如画,工资现实惨不忍察",
    "老板决策频繁像翻书,员工适应无力心添堵",
    "老板鼓励大家要拼搏,奖励却像星星一颗颗",
    "老板说着公司前景阔,自己钱包鼓得像秤砣",
    "老板夸赞员工都很棒,升职加薪却没影光",
    "老板总是信心超级满,员工待遇原地打转转",
    "老板规划宏图似星汉,工资涨幅细若游丝般",
    "老板强调团队力量强,利益分配却不均当",
    "老板说要一起创辉煌,员工只剩疲惫心迷茫",
    "老板说起未来眼放光,工资现状不如从前样",
]

# -------------------- 字体加载 -------------------- #
CANDIDATE_FONTS = [
    "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Songti SC",
    "STHeiti", "Arial Unicode MS", "AppleGothic",
]

def load_font(size: int) -> pygame.font.Font:
    """优先使用同目录 font.ttf,其次系统常见中文字体。"""
    if Path("font.ttf").exists():
        return pygame.font.Font("font.ttf", size)
    for name in CANDIDATE_FONTS:
        path = pygame.font.match_font(name)
        if path:
            return pygame.font.Font(path, size)
    return pygame.font.SysFont(None, size)

# -------------------- 工具函数 -------------------- #

def load_img(path: str, scale: float = 1.0):
    if not Path(path).exists():
        return None
    try:
        img = pygame.image.load(path).convert_alpha()
        if scale != 1.0:
            w, h = img.get_size()
            img = pygame.transform.smoothscale(img, (int(w * scale), int(h * scale)))
        return img
    except Exception:
        return None


def play_sound(path: str):
    if Path(path).exists():
        try:
            pygame.mixer.Sound(path).play()
        except pygame.error:
            pass

# -------- 持久化:只保存最高纪录 -------- #

def load_best() -> int:
    if SAVE_FILE.exists():
        try:
            return json.loads(SAVE_FILE.read_text()).get("best", 0)
        except Exception:
            pass
    return 0


def save_best(best: int):
    try:
        SAVE_FILE.write_text(json.dumps({"best": best}))
    except Exception:
        pass

# -------------------- 数据类 -------------------- #
@dataclass
class Particle:
    pos: pygame.Vector2
    vel: pygame.Vector2
    r: float
    col: tuple[int, int, int]
    life: int = 40

    def update(self):
        self.pos += self.vel
        self.vel *= 0.92
        self.r *= 0.96
        self.life -= 1

    def draw(self, surf):
        if self.life > 0 and self.r > 0:
            pygame.draw.circle(surf, self.col, self.pos, max(1, int(self.r)))


@dataclass
class FloatingText:
    txt: str
    pos: pygame.Vector2
    font: pygame.font.Font
    alpha: int = 255

    def update(self):
        self.pos.y -= 1
        self.alpha -= 3

    def draw(self, surf):
        if self.alpha <= 0:
            return
        im = self.font.render(self.txt, True, BLACK)
        im.set_alpha(self.alpha)
        surf.blit(im, self.pos)


@dataclass
class Egg:
    base_rect: pygame.Rect
    rect: pygame.Rect
    image: pygame.Surface | None
    color: tuple[int, int, int] = DEFAULT_COLOR
    growth: int = 0  # 怒气值 / 成长次数
    scale: float = 1.0
    pulse_dir: int = 1

    def draw(self, surf):
        if self.image:
            surf.blit(self.image, self.rect)
        else:
            pygame.draw.ellipse(surf, self.color, self.rect)

    def pulse(self):
        self.scale, self.pulse_dir = 1.2, -1

    def animate(self):
        if self.scale == 1.0:
            return
        self.scale += 0.02 * self.pulse_dir
        if self.scale <= 0.9:
            self.scale, self.pulse_dir = 0.9, 1
        if abs(self.scale - 1.0) < 0.02:
            self.scale = 1.0
        self._apply_transform()

    def level_up(self):
        self.growth += 1
        # 壳色加深
        self.color = tuple(max(0, int(c * 0.85)) for c in self.color)
        # 尺寸放大
        self.base_rect.inflate_ip(int(self.base_rect.w * 0.1), int(self.base_rect.h * 0.1))
        if self.image:
            self.image = pygame.transform.smoothscale(self.image, self.base_rect.size)
        self._apply_transform()

    def _apply_transform(self):
        w0, h0 = self.base_rect.size
        w, h = max(2, int(w0 * self.scale)), max(2, int(h0 * self.scale))
        self.rect.size = (w, h)
        self.rect.center = self.base_rect.center

# -------------------- 主游戏类 -------------------- #
class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((BASE_W, BASE_H))
        pygame.display.set_caption("敲鸡蛋 — 吐槽老板 🐣")

        self.big_font = load_font(36)
        self.small_font = load_font(24)

        img = load_img("egg.png", 0.7)
        center_x = (BASE_W - GAUGE_W) // 2
        base_rect = (img.get_rect(center=(center_x, BASE_H // 2 - 60)) if img else
                     pygame.Rect(center_x - 80, BASE_H // 2 - 120, 160, 200))
        self.egg = Egg(base_rect.copy(), base_rect.copy(), img)

        # 背景音乐
        self.music_on = False
        for mf in ("bgm.mp3", "bgm.ogg"):
            if Path(mf).exists():
                pygame.mixer.music.load(mf)
                pygame.mixer.music.set_volume(0.6)
                pygame.mixer.music.play(-1)
                self.music_on = True
                break

        # 计数器
        self.taps = 0                   # 本局计数
        self.best = load_best()         # 历史最佳

        self.particles: List[Particle] = []
        self.texts: List[FloatingText] = []
        self.boss_idx = 0
        self.boss_line = ""

        self.clock = pygame.time.Clock()
        self.running = True

    # ---------- 输入处理 ---------- #
    def tap(self):
        self.taps += 1
        if self.taps > self.best:
            self.best = self.taps
            save_best(self.best)

        play_sound("crack.wav")
        self.egg.pulse()

        # 粒子效果
        center = self.egg.rect.center
        if len(self.particles) < MAX_PARTICLES:
            for _ in range(10):
                ang = random.uniform(0, 2 * math.pi)
                spd = random.uniform(2, 6)
                self.particles.append(
                    Particle(
                        pygame.Vector2(center),
                        pygame.Vector2(math.cos(ang) * spd, math.sin(ang) * spd),
                        random.uniform(3, 6),
                        random.choice(PARTICLE_COLS)
                    )
                )

        # 弹幕文字
        msg = random.choice(PHRASES)
        w, _ = self.small_font.size(msg)
        self.texts.append(FloatingText(msg, pygame.Vector2(center[0] - w // 2, center[1] - 40), self.small_font))

        # 底部吐槽
        self.boss_line = BOSS_LINES[self.boss_idx]
        self.boss_idx = (self.boss_idx + 1) % len(BOSS_LINES)

        # 怒气升级
        if self.taps % 50 == 0:
            self.egg.level_up()

    def handle_events(self):
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                self.running = False
            elif e.type == pygame.MOUSEBUTTONDOWN and self.egg.rect.collidepoint(e.pos):
                self.tap()
            elif e.type == pygame.KEYDOWN:
                if e.key in (pygame.K_ESCAPE, pygame.K_q):
                    self.running = False
                elif e.key == pygame.K_SPACE:
                    self.tap()
                elif e.key == pygame.K_m:
                    if self.music_on:
                        pygame.mixer.music.pause()
                    else:
                        pygame.mixer.music.unpause()
                    self.music_on = not self.music_on
                elif e.key == pygame.K_s:
                    pygame.image.save(self.screen, "screenshot.png")
                    self.texts.append(FloatingText("📸 已截图", pygame.Vector2(BASE_W // 2 - 40, BASE_H - 80), self.small_font))

    # ---------- 更新 ---------- #
    def update(self):
        self.egg.animate()

        for p in self.particles[:]:
            p.update()
            if p.life <= 0 or p.r <= 0:
                self.particles.remove(p)

        for t in self.texts[:]:
            t.update()
            if t.alpha <= 0:
                self.texts.remove(t)

    # ---------- 绘制 ---------- #
    def draw_gauge(self):
        seg_h = 40
        levels = 10
        y0 = 140
        x_inner = GAUGE_X + 15

        # 标题
        title = self.small_font.render(f"怒气值 {self.egg.growth}", True, BLACK)
        self.screen.blit(title, (GAUGE_X + (GAUGE_W - title.get_width()) // 2, 90))

        for lv in range(levels):
            col = tuple(max(0, int(c * (0.85 ** lv))) for c in DEFAULT_COLOR)
            rect = pygame.Rect(x_inner, y0 + lv * seg_h, GAUGE_W - 30, seg_h - 2)
            pygame.draw.rect(self.screen, col, rect)
            if lv == self.egg.growth:
                pygame.draw.rect(self.screen, BLACK, rect, 2)

    def render(self):
        self.screen.fill(WHITE)

        # 主蛋 + 粒子
        self.egg.draw(self.screen)
        for p in self.particles:
            p.draw(self.screen)
        for t in self.texts:
            t.draw(self.screen)

        # 顶部统计
        sess_txt = self.small_font.render(f"本局: {self.taps}", True, BLACK)
        best_txt = self.small_font.render(f"最高: {self.best}", True, BLACK)
        self.screen.blit(sess_txt, (10, 10))
        self.screen.blit(best_txt, (10, 35))

        # 中央标题
        cnt_surface = self.big_font.render(f"敲蛋数: {self.taps}", True, BLACK)
        self.screen.blit(cnt_surface, ((BASE_W - GAUGE_W) // 2 - cnt_surface.get_width() // 2, 40))

        # 底部吐槽
        if self.boss_line:
            bl_surf = self.small_font.render(self.boss_line, True, BLACK)
            self.screen.blit(bl_surf, (((BASE_W - GAUGE_W) // 2) - bl_surf.get_width() // 2, BASE_H - 70))

        # 怒气计量尺
        self.draw_gauge()

        pygame.display.flip()

    # ---------- 主循环 ---------- #
    def run(self):
        while self.running:
            self.handle_events()
            self.update()
            self.render()
            self.clock.tick(60)
        pygame.quit()
        sys.exit()

# -------------------- 入口 -------------------- #
if __name__ == "__main__":
    Game().run()

❤️ 彩蛋 · 吐槽弹幕(部分节选)

老板画的大饼飘啊飘,钱包瘪瘪心发焦
老板谈起理想滔滔不绝,说到加薪哑口无言
老板总说公司是大家,可好处都往自己抓
老板豪言壮志震天响,工资待遇悄悄往下放
老板夸赞员工都很棒,升职加薪却没影光


🧠 结语

写这个小游戏其实一开始只是为了吐槽。但做到后面,敲蛋、变色、怒气条……它成了我一种情绪调节的出口。

如果你也在被 KPI、需求、996 压得喘不过气,不妨敲几个蛋,放一首歌,笑一笑:

“人生不是打工打到底,也可以是……把蛋敲裂,然后重启。”


如果你喜欢这个项目,欢迎评论、点赞、收藏,或者来 GitHub 一起玩花样!😉
如需源码打包、版本拓展或移植 App,我也很乐意继续开发~

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐