网页小游戏开发

02复刻 memory.toys:翻牌动画、洗牌和运气分

约 25 分钟

先玩两把:记忆翻牌 · 完整源码

这游戏复刻的是 memory.toys 的 Classic Memory:翻牌找对子,统计时间、命中率,还有个好玩的指标——“蒙对几次”。玩法抄思路,代码和图案全部自己写。

一、翻牌用 CSS 3D,不用 JS 算

每张牌是一个两面结构:

<div class="card">
  <div class="inner">
    <div class="face back">?</div>
    <div class="face front">🐶</div>
  </div>
</div>
.card .inner {
  transform-style: preserve-3d;
  transition: transform 0.28s ease;
}
.card.flip .inner { transform: rotateY(180deg); }
.face {
  backface-visibility: hidden;   /* 关键属性 */
}
.face.front { transform: rotateY(180deg); }

正面一开始就转了 180 度背对着你,backface-visibility: hidden 保证朝后的那一面不可见。JS 要做的只有一件事:切换 .flip 类。动画、缓动、透视全是 CSS 的事。

不要用 JS 定时器一帧帧改角度——那和上一课“鼹鼠冒头”是同一个道理:能用 transition 表达的位移和旋转,都交给 CSS。

二、洗牌和发牌

function shuffle(a) {
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

这是 Fisher–Yates 洗牌,O(n),每个位置等概率。别用 sort(() => Math.random() - 0.5)——分布不均匀,而且慢。

牌堆的生成:faces.flatMap(f => [f, f]) 把选出的图案复制成对,再洗一次。

三、防连点:一个 lock 就够

翻牌游戏最常见的 bug:玩家手快,第三张牌在“前两张翻回去”的动画期间被翻开,状态就乱了。

let first = null;   // 已翻开的第一张
let lock = false;   // 翻回动画期间锁输入

if (first.face === c.face) {
  // 配对成功:标记 done,不用锁
} else {
  lock = true;
  setTimeout(() => { /* 翻回去 */ lock = false; }, 700);
}

规则很简单:

  • 同一时间最多允许两张 up,第三张只有在配对成功(瞬间完成)之后才能再翻
  • 配对失败要播 700ms 的“给你看一眼再翻回去”动画,期间锁死输入

这比给每张牌单独加状态检查省心得多——锁的是输入,不是牌。

四、accuracy 和 lucky:两个容易混的统计

原版网站除了命中率还统计“lucky guesses”(运气好蒙对)。自己实现时要定义清楚:

  • 命中率 = 配对成功次数 ÷ 翻开第二张牌的次数。注意分母不是翻牌总次数(第一张牌没有对错可言)
  • 运气 = 第二张牌翻到的图案,之前累计翻开次数 ≤ 2(也就是你只见过它一次或根本没见过,就一次配对成功)

这两个指标方向相反:命中率奖励“记得住”,运气分奖励“敢猜”。两个都要,游戏才同时适合仔细型玩家和手快型玩家。

五、难度曲线不用改机制

三档难度唯一的区别是 cols × rows:4×4(8 对)、4×5(10 对)、5×6(15 对)。牌多了,记忆负担自然上升,不需要任何额外机制——这正好反面印证了上一课打地鼠的做法:先想清楚参数化什么,难度曲线往往只是改参数。

配套的“挑战徽章”(零失误、限时内完成)也是纯展示层:通关时按条件打 ★☆,不影响主流程。

结课的坑

  • pointerdown 绑在 board 上、牌再 closest('.card') 查归属——和打地鼠同一套事件委托,别给每张牌单独绑
  • 翻回动画用 setTimeout(700),期间用户如果点了“再来一局”重建牌堆,要顺手 clearTimeout,否则旧定时器会操作已经不在 DOM 里的牌(本实现里新局会重置 lock,但旧引用还在,稳妥起见应清理)
  • backface-visibility 记得写 -webkit- 前缀,Safari 需要

练习

  1. 加“总翻牌数”统计,和命中率一起显示在结算面板
  2. 连续配对成功三次加一个 ★ 连击提示
  3. 把 emoji 换成自己的 SVG 图标集,保持每对视觉差异明显