三天从零做一个宝可梦问答小程序:数据管线、题库算法与云开发判分全记录

做了十年前端,最近想做个能写进简历的小程序作品。选题盯上了宝可梦:图鉴查询类在国内已经卷成红海,但"问答 / 猜宝可梦"这种带互动和成瘾机制的玩法,在微信小程序端还是空白。版权上我给自己划了线——只用 PokeAPI 的纯文字数据(中文名、属性、种族值、描述),不上任何官方立绘,类目也避开"游戏"走工具 / 休闲,规避风险。

这篇文章是我这一版从立项到首版上线的真实复盘。

数据管线:1025 条一次抓完

数据源是 PokeAPI。脚本用 Node 原生 fetch,并发 16,把 species 和 pokemon 各 1025 条一次抓下来,36 秒零失败,输出一个 636K 的 pokemon.json:

// fetch.js 核心:并发 16 抓取
const CONCURRENCY = 16;
async function fetchAll(ids) {
  const out = [];
  for (let i = 0; i < ids.length; i += CONCURRENCY) {
    const batch = ids.slice(i, i + CONCURRENCY);
    const res = await Promise.all(batch.map((id) => fetchOne(id)));
    out.push(...res);
  }
  return out;
}

两个坑值得记。第一,语言标签。PokeAPI 文档示例写的是 zh-Hans,但实测拿不到中文数据,正确标签是小写 zh-hans——大小写不匹配不报错只返回空,最耗调试时间。第二,数据质量。1025 只里只有 722 只含有效中文图鉴描述,九世代新宠(如 #1010 铁斑叶)根本没有中文描述,所以"图鉴描述猜"题型的题池上限就是 722。描述里还混着全角空格和全角数字("1400米"),出题前必须统一清洗。

题库生成:固定种子保证可复现

题库生成用固定随机种子(20260904),保证每次跑出来的题目完全一致。这对测试和线上稳定性很重要——不会因为一次重跑就把题全换了。

初版 1212 题,三类题型:图鉴描述猜 800、之最排行 240、属性克制 172。质量上做了硬约束:之最题答案经独立审计严格大于全部干扰项;图鉴题干扰项强制同世代;克制题优先非 ×1 关系(把无信息量的题从 68 道压到 2 道)。属性克制表抽查 12 项标准关系,确认妖精攻龙是 ×2 这类易错点没搞反。

干扰项算法:从"送分题"到真动脑

首版题库我自己试做,发现很多题一眼就能排除。用户也反馈了同样问题。于是重做干扰项生成,按相似度打分:

  • 同主属性 +4
  • 同副属性 +2
  • 同世代 +2
  • 老世代高人气 +1
  • 体型接近 +1

最终 94% 的图鉴题干扰项和答案同主属性;之最题干扰项数值不低于答案的 82%(均值比 0.87);克制题错误选项取倍率最近的,并优先抗性属性。这一版题库从 1212 收敛到 1209 题(flavor 800 / superlative 237 / typechart 172),校验零错误。区别很明显:之前是送分,现在是真要动脑。

小程序与云开发判分

小程序我选了微信原生 + 云开发。首版四个页面:今日挑战、答题(每题 15 秒、一次 5 题)、成绩(含 Canvas 分享卡和解析)、战绩。两个云函数:getDailyQuiz 用日期做种子取当日题,submitResult 服务端判分、记连胜、算百分位。

防作弊是关键:答案不下发客户端,只收当日提交,服务端判分;同日题干去重(我修过一个 bug——同一天出了两道"攻击种族值最高",根因是取题逻辑没对题干去重)。还有一个坑是 EPOCH 常量在两处都要同步,漏一处日期种子就对不上。

复盘

三天从零做出一个能跑通的问答小程序,最核心的三件事:数据管线要"可复现 + 清洗到位";题库的壁垒在干扰项算法,不在出题;判分放服务端,既防作弊也方便做排行榜。这类"数据驱动 + 算法 + 前端"的组合,很适合当简历项目。

完整代码与题库生成脚本见项目仓库;更多工程复盘记录在 blog.alleria.cn。