从零到一:用纯JavaScript实现中国麻将游戏的实战分享

举报
yd_213866132 发表于 2026/10/08 18:07:25 2026/10/08
【摘要】 从零到一:用纯JavaScript实现中国麻将游戏的实战分享作者:Eddygit日期:2026-10-06项目地址:https://gitcode.com/Eddygit/mahjong-game 一、技术路线 1.1 为什么选择纯HTML/JS单文件方案?在项目立项阶段,我考虑过几种技术方案:方案优点缺点最终选择React/Vue + 构建工具组件化、状态管理清晰需要Node环境、构建复...

从零到一:用纯JavaScript实现中国麻将游戏的实战分享

作者:Eddygit
日期:2026-10-06
项目地址:https://gitcode.com/Eddygit/mahjong-game

一、技术路线

1.1 为什么选择纯HTML/JS单文件方案?

在项目立项阶段,我考虑过几种技术方案:

方案 优点 缺点 最终选择
React/Vue + 构建工具 组件化、状态管理清晰 需要Node环境、构建复杂 ❌
Canvas 2D 渲染 性能好、自定义程度高 开发量大、调试不便 ❌
纯HTML/CSS/JS单文件 零依赖、即开即玩、易分享 代码集中、维护稍难 ✅

最终选择单文件方案,核心考量是可分享性——一个HTML文件发给别人,双击就能玩,这是最纯粹的工具形态。

1.2 架构设计

index.html
├── HTML结构层(游戏界面布局)
├── CSS样式层(牌面设计 + 动画效果)
└── JavaScript逻辑层
    ├── 牌定义模块(createAllTiles)
    ├── 胡牌判定模块(canHu / checkSets / canHu7Pairs)
    ├── AI决策模块(aiChooseDiscard)
    ├── 渲染模块(render)
    └── 游戏流程模块(initGame / advanceTurn)

1.3 数据结构设计

每张牌用一个对象表示:

{ suit: 'wan', value: 3, id: 42 }
// suit: 花色(wan万/tiao条/tong筒/feng风/jian箭)
// value: 数值(1-9)
// id: 唯一标识(0-135)

这种设计的好处是:

  • sameTile(a, b) 判断简单:只比 suit 和 value
  • tileKey(t) 排序方便:suitOrder * 100 + value
  • id 用于追踪具体牌的流向

二、核心技术实现

2.1 胡牌判定算法——递归回溯

这是整个项目最核心也最有意思的部分。

基本思路:标准胡牌 = n个顺子/刻子 + 1个将(对子)

算法步骤:

  1. 遍历每种牌,尝试将其作为"将"(减去2张)
  2. 对剩余牌递归尝试分解为顺子或刻子
  3. 如果能全部分解,则可胡
function canHu(hand) {
  let counts = handToCounts(hand);
  for (let key of keys) {
    if (counts[key] >= 2) {
      counts[key] -= 2;           // 尝试做将
      if (checkSets(counts)) return true;  // 递归检查剩余
      counts[key] += 2;           // 回溯
    }
  }
  return false;
}

checkSets 递归逻辑:

  • 找到第一张还有剩余的牌
  • 尝试组成刻子(3张相同)→ 递归
  • 尝试组成顺子(3张连续数牌)→ 递归
  • 都不行则返回 false

七对子单独检测:14张牌恰好7组对子。

2.2 AI出牌策略

AI采用基于牌效率的简单评分策略:

function aiChooseDiscard(hand) {
  // 对每张牌计算"孤立度"分数
  // 分数越高 = 越该打出
  // - 字牌单张:+10(最该打)
  // - 数牌单张:+5
  // - 边张(1/9):+3
  // - 孤张(左右无邻牌):+2
}

这是一个非常简化的策略,真实麻将AI需要考虑:

  • 听牌检测(打出后能听哪些牌)
  • 对手弃牌推理(猜对方手牌)
  • 局势评估(攻守判断)

2.3 CSS牌面设计

不使用任何图片,纯CSS绘制麻将牌面:

.tile {
  width: 42px; height: 60px;
  background: linear-gradient(145deg, #fff, #e8e8e8);
  border: 2px solid #bbb;
  border-radius: 6px;
  /* ... */
}

万条筒用数字+汉字表示,风牌箭牌用汉字表示,不同花色用不同颜色区分。

三、实战经验

3.1 开发流程

  1. 先搭骨架:HTML结构 + 基础CSS,确认布局合理
  2. 再写逻辑:牌定义 → 发牌 → 出牌 → 胡牌判定
  3. 后做交互:碰杠胡按钮 → AI决策 → 渲染更新
  4. 最后优化:动画效果 → 提示文案 → 边界处理

3.2 踩过的坑

坑1:胡牌判定遗漏七对子
最初只实现了标准胡牌(顺子+刻子+将),测试时发现七对子无法胡。单独添加 canHu7Pairs 检测解决。

坑2:碰牌后忘记补牌
碰牌后手牌减少2张(加上别人的1张组成3张明刻),但手牌数量应保持13张。碰牌后需要轮到碰牌者出牌,而不是继续下一家。

坑3:AI出牌后忘记检查玩家操作
AI出牌后需要检查玩家是否能碰/杠/胡,最初直接 advanceTurn() 跳过了,导致玩家无法操作。

3.3 调试技巧

  • 用 Node.js 提取纯逻辑函数做单元测试(绕过DOM依赖)
  • console.log 打印手牌和游戏状态辅助调试
  • 先测胡牌判定(最复杂),再测游戏流程

四、存在问题与优化空间

4.1 当前不足

问题 严重程度 说明
无听牌提示 中 玩家不知道自己离胡牌还差什么
AI太简单 中 AI不会防守,不会推理对手手牌
无番种计算 中 胡牌后不计算具体番数和台数
无音效 低 缺少摸牌、出牌的音效反馈
无历史记录 低 不记录对局历史和胜率统计
无花牌 低 省略了春夏秋冬梅兰竹菊8张花牌

4.2 优化方向

短期优化:

  1. 添加听牌检测和提示
  2. 改进AI策略:加入听牌检测和基本防守
  3. 添加更多番种:清一色、一条龙、十三幺等
  4. 添加音效和动画

中期优化:

  1. 拆分为多文件项目(模块化)
  2. 添加在线对战功能(WebSocket)
  3. 实现完整四川麻将或国标麻将规则
  4. 添加排行榜和成就系统

长期优化:

  1. 用 Canvas/WebGL 重写渲染层
  2. 引入蒙特卡洛AI或深度学习AI
  3. 支持自定义规则和牌型
  4. 做成PWA支持离线游玩

4.3 技术反思

单文件 vs 模块化:
单文件方案在项目初期开发效率很高,但随着功能增加,代码维护难度上升。如果后续要添加更多功能,建议拆分为模块化项目。但对于一个"小工具"的定位,单文件的简洁性是无可替代的优势。

递归 vs 动态规划:
胡牌判定用递归回溯足够了(136张牌,手牌最多14张,递归深度有限)。如果要做更复杂的牌型分析(如计算所有可能的胡法),可以考虑动态规划优化。

五、总结

这个项目证明了:用最简单的技术栈,也能做出有趣的交互应用。纯HTML/CSS/JS不需要任何构建工具和环境配置,降低了使用门槛,提高了可分享性。

麻将游戏的复杂度主要在规则实现(胡牌判定、番种计算),而不在技术框架。选择合适的技术方案,比追求"先进"的技术更重要。


本文为原创技术分享,转载请注明出处。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。