Appearance
实战案例:记忆翻牌游戏开发实录
难度:⭐⭐ 进阶 | 工具:Codex / Claude Code | 耗时:约 25 分钟 | 技术栈:HTML + CSS + JS
项目背景
记忆翻牌游戏是一个逻辑性较强的项目——涉及状态管理(翻牌、匹配、锁定)、动画(3D 翻转)、游戏规则(计时、计步、通关判定)。非常适合用来练习如何向 AI 描述复杂的交互逻辑,也很适合结合 理解 AI 的输出:代码审查与调试 一起练。
第一阶段:需求规划
MVP:4×4 网格、翻牌匹配、通关提示 第二版:计时、计步、最佳记录 第三版:难度选择、音效、庆祝动画
第二阶段:MVP 版本
提示词 #1:核心游戏逻辑
text
帮我创建一个记忆翻牌(Memory Card Game)网页游戏。
这是 MVP 版本,专注于核心游戏逻辑:
【游戏规则——务必严格实现】
1. 4×4 网格,共 16 张卡片(8 对不同的 emoji 图案)
2. 游戏开始时,所有卡片背面朝上
3. 玩家每次可以翻开 1 张卡片(点击翻开)
4. 翻开第 2 张后:
- 如果两张图案相同 → 匹配成功,两张卡片保持正面朝上,不可再点击
- 如果两张图案不同 → 匹配失败,等待 0.8 秒后两张都翻回背面
5. 匹配判定期间(等待 0.8 秒期间),不能翻开第 3 张牌(要锁定点击)
6. 已经匹配成功的卡片不能再次点击
7. 当所有 8 对都匹配完成 → 显示"恭喜通关!"的提示
8. 有一个"重新开始"按钮,点击后重新洗牌、重置游戏
【洗牌逻辑】
使用 Fisher-Yates 洗牌算法打乱卡片数组。
emoji 图案使用:🍎🍊🍋🍇🍓🍑🍒🥝
【卡片翻转动画】
- 使用 CSS 3D transform 实现翻牌效果
- 每张卡片由两层组成:正面(显示 emoji)和背面(显示 ?)
- 翻转动画:rotateY(180deg),duration 0.5s,ease-in-out
- 需要设置 perspective 在父容器上
- 卡片需要 transform-style: preserve-3d
- 正面和背面分别用 backface-visibility: hidden
【界面设计】
- 页面居中
- 顶部标题 "记忆翻牌" + 重新开始按钮
- 4×4 网格,间距 10px
- 每张卡片 80×80px(手机端自适应缩小)
- 卡片背面:蓝紫渐变 #667eea→#764ba2,圆角 12px,中间显示白色 ? 号
- 卡片正面:白色背景,圆角 12px,显示大号 emoji
- 整体深色背景 #0f172a
【技术要求】
- 纯 HTML + CSS + JavaScript,单文件
- 响应式:手机端卡片尺寸自适应
注意:游戏逻辑的正确性是第一优先级。请确保状态管理没有 bug(尤其是锁定机制和匹配判定)。
请生成完整代码。重点测试
这个项目的测试比前面几个更重要,因为游戏逻辑 bug 体验很差:
| 测试场景 | 预期 | 实际 | 状态 |
|---|---|---|---|
| 翻开第 1 张 | 显示正面 | ✅ | |
| 翻开第 2 张(匹配) | 两张保持正面 | ✅ | |
| 翻开第 2 张(不匹配) | 0.8s 后翻回 | ✅ | |
| 在等待翻回期间快速点第 3 张 | 不应该翻开 | ⚠️ 偶尔能翻 | 需修复 |
| 点击已匹配的卡片 | 不应该有反应 | ✅ | |
| 点击当前已翻开的卡片 | 不应该触发匹配 | ⚠️ 会触发 | 需修复 |
| 16 张全部匹配 | 显示通关 | ✅ | |
| 重新开始 | 洗牌重置 | ✅ |
提示词 #2:修复游戏逻辑 bug
text
游戏逻辑有两个 bug 需要修复:
Bug 1:匹配判定期间的锁定不够严格
- 问题:在两张不匹配的牌等待翻回的 0.8 秒期间,快速连续点击有时能翻开第 3 张牌
- 原因分析:锁定变量(isLocked 或类似的)可能在某些时机没有正确设置
- 修复要求:
- 定义一个 isLocked 变量
- 在翻开第 2 张牌后立即设置 isLocked = true
- 在所有 click 事件处理器最开始检查 if (isLocked) return
- 匹配成功或翻回结束后才设置 isLocked = false
Bug 2:点击同一张已翻开的卡片触发了匹配
- 问题:翻开一张牌后,再次点击同一张牌,被当作翻开了第2张,触发匹配判定(自己和自己匹配了)
- 修复要求:
- 在 click 事件中检查:如果这张卡已经翻开(正面朝上),直接 return
- 可以通过检查卡片的 CSS class 或者一个 isFlipped 属性来判断
其他功能不变,只修这两个 bug。💡 关键经验:游戏类项目的 bug 描述一定要包含复现步骤和预期 vs 实际行为。AI 修 bug 最怕你只说"有 bug"。
第三阶段:计时计步 + 最佳记录
提示词 #3
text
添加游戏统计功能:
1. 计时器:
- 从翻开第一张牌开始计时
- 格式 MM:SS,显示在顶部标题右侧
- 通关时停止计时
- 重新开始时归零
2. 步数计数:
- 每翻开第2张牌算1步(不管匹配不匹配)
- 显示在计时器旁边:"步数: X"
- 重新开始时归零
3. 通关弹窗:
- 所有卡片匹配后,弹出一个居中的模态框
- 显示:🎉 恭喜通关!
- 用时:XX:XX
- 步数:XX 步
- 最佳记录:XX:XX / XX 步(如果是新记录加 🏆 标记)
- "再来一局"按钮
4. 最佳记录:
- 保存到 localStorage
- 分别记录最佳时间和最少步数
- 页面底部小字显示当前最佳记录
不要改动已有的游戏逻辑和翻牌动画。第四阶段:难度选择 + 音效 + 庆祝动画
提示词 #4
text
添加最后一批功能:
1. 难度选择:
- 在标题下方添加三个难度按钮:简单(3×4=6对)、普通(4×4=8对)、困难(5×4=10对)
- 当前难度高亮显示
- 切换难度时重新开始游戏
- 网格列数随难度变化(简单4列、普通4列、困难5列)
- 最佳记录按难度分别保存
- 困难模式需要更多 emoji:追加 🍌🍍🥭🫐
2. 音效(Web Audio API,不用音频文件):
- 翻牌:短促的 "翻开" 音(高频 600Hz,0.05s)
- 匹配成功:欢快上升音(400Hz→800Hz,0.2s)
- 匹配失败:低沉下降音(400Hz→200Hz,0.15s)
- 通关:简短胜利旋律(连续 5 个升调音符,每个 0.1s)
- 右上角加一个 🔊/🔇 图标切换静音,默认开启
- 静音设置保存 localStorage
3. 通关庆祝动画:
- 通关后所有已匹配的卡片依次做一个弹跳动画(每张延迟0.05s)
- 页面顶部飘下彩色纸片(用 CSS @keyframes 实现)
- 生成 30 个小方块,不同颜色,从顶部不同位置飘落
- 有旋转和左右摇摆的效果
- 动画持续 3 秒后自动清除
保持所有已有功能不变。踩坑记录
这轮的主要问题是困难模式的网格布局不对——5列×4行的网格,但 CSS Grid 模板列还是 repeat(4, 1fr)。
text
困难模式的网格列数不对。现在还是4列。
请根据当前难度动态设置 grid-template-columns:
- 简单/普通:repeat(4, 1fr)
- 困难:repeat(5, 1fr)
切换难度时同时更新网格 CSS。
卡片尺寸在困难模式下也要适当缩小(70×70px),避免超出屏幕。开发总结
时间花费
| 阶段 | 耗时 | 对话轮次 |
|---|---|---|
| 需求规划 | 2 分钟 | - |
| MVP + 核心逻辑 | 5 分钟 | 1 轮 |
| 修复游戏 bug | 5 分钟 | 1 轮 |
| 计时计步 + 记录 | 5 分钟 | 1 轮 |
| 难度+音效+庆祝 | 8 分钟 | 2 轮(1轮生成+1轮修网格) |
| 总计 | ~25 分钟 | 5 轮对话 |
关键经验
- 游戏逻辑要描述得极其精确:每个状态、每个条件、每个时机都要写清楚
- 第一版一定要重点测试边界情况:快速点击、重复点击、同时操作
- Bug 报告要有复现步骤:不要说"有时候会出bug",要说"快速连续点击3张牌"
- 状态变量的管理要明确说清楚:什么时候设 true、什么时候设 false
- CSS 3D 翻转有固定套路:perspective + transform-style + backface-visibility,告诉 AI 这几个关键词
踩坑汇总
| 问题 | 原因 | 解决方式 |
|---|---|---|
| 快速点击可以翻第3张 | 锁定变量设置时机不对 | 明确描述锁定逻辑的开关时机 |
| 同一张牌自己匹配自己 | 没检查是否是同一张卡 | 加 isFlipped 状态检查 |
| 困难模式网格列数不对 | CSS 没随难度动态更新 | 明确要求动态设置 grid-template-columns |
| 音效在 iOS Safari 不响 | 需要用户交互才能播放音频 | 第一次点击时初始化 AudioContext |
