核心数据结构设计
麻将牌对象建模

public enum MahjongTile {
// 万子(1-9)
CHARACTER_1, CHARACTER_2, CHARACTER_3, CHARACTER_4, CHARACTER_5, CHARACTER_6, CHARACTER_7, CHARACTER_8, CHARACTER_9,
// 筒子
DOT_1, DOT_2, DOT_3, DOT_4, DOT_5, DOT_6, DOT_7, DOT_8, DOT_9,
// 索子
BAMBOO_1, BAMBOO_2, BAMBOO_3, BAMBOO_4, BAMBOO_5, BAMBOO_6, BAMBOO_7, BAMBOO_8, BAMBOO_9,
// 风牌
EAST, SOUTH, WEST, NORTH,
// 箭牌
RED_DRAGON, GREEN_DRAGON, WHITE_DRAGON;
}
玩家类与游戏状态
public class Player {
private List<MahjongTile> handTiles = new ArrayList<>(); // 手牌
private List<MahjongTile> discardedTiles = new ArrayList<>(); // 弃牌
private boolean isReady;
}
public class GameState {
private List<MahjongTile> wallTiles = new ArrayList<>(); // 牌墙
private Player[] players = new Player[4];
private int currentPlayerIndex;
}
关键逻辑实现
洗牌与初始化
public void initializeGame() {
// 创建136张牌
List<MahjongTile> allTiles = new ArrayList<>();
for (MahjongTile tile : MahjongTile.values()) {
// 每种牌添加4张(除特殊规则)
for (int i = 0; i < 4; i++) {
allTiles.add(tile);
}
}
// Fisher-Yates洗牌算法
Collections.shuffle(allTiles);
// 初始化牌墙
gameState.setWallTiles(allTiles);
}
发牌逻辑
public void dealTiles() {
for (int round = 0; round < 3; round++) {
for (Player player : players) {
for (int i = 0; i < 4; i++) {
player.drawTile(wallTiles.remove(0));
}
}
}
// 庄家多摸一张
players[0].drawTile(wallTiles.remove(0));
}
胡牌算法(核心)
public boolean checkWin(List<MahjongTile> hand) {
// 1. 将手牌按类型分组
Map<MahjongTile, Integer> tileCount = new HashMap<>();
for (MahjongTile tile : hand) {
tileCount.put(tile, tileCount.getOrDefault(tile, 0) + 1);
}
// 2. 检查七对子特殊牌型
if (checkSevenPairs(tileCount)) return true;
// 3. 标准胡牌:1对将 + 4组顺子/刻子
return standardWinCheck(tileCount);
}
private boolean standardWinCheck(Map<MahjongTile, Integer> tiles) {
// 递归移除将牌和顺子/刻子组合
// ...
// 详细实现参考麻将规则状态机
}
网络通信架构
基于Netty的通信框架

// 消息协议
public class MahjongMessage {
private int msgType; // 1:摸牌 2:打牌 3:碰 4:杠 5:胡
private MahjongTile tile;
private int playerId;
}
// Netty处理器
public class MahjongServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
MahjongMessage request = (MahjongMessage) msg;
switch (request.getMsgType()) {
case 1: handleDrawTile(ctx, request); break;
case 2: handleDiscard(ctx, request); break;
// ...其他操作
}
}
}
性能优化方案
-
胡牌算法加速
- 使用预生成胡牌模式库
- 位运算表示牌型组合(如用int的二进制位表示特定牌的数量)
-
状态同步策略
- 采用增量更新:仅同步变动牌信息
- 客户端预测机制:提前计算可能的操作
-
防作弊设计
- 牌墙状态仅存在服务端
- 关键操作需服务端二次验证
- 采用种子随机数保证洗牌可验证
测试要点
-
牌型验证覆盖率
- 覆盖常见胡牌牌型(平胡、碰碰胡、清一色等)
- 特殊规则测试(国标/日麻/川麻差异)
-
并发压力测试

- 模拟1000房间同时进行游戏
- 网络延迟波动测试(200ms-2s延迟)
-
异常处理测试
- 断线重连数据一致性
- 非法操作拦截(如无效碰牌)
实战建议:开发初期优先实现核心判胡算法,建议采用”状态机+递归回溯”混合方案,对于网络模块,建议使用Protobuf定义通信协议以保证跨平台兼容性,在日麻等变种规则中,需特别注意役种判定与符数计算的复杂度。
您在开发过程中遇到最棘手的技术问题是什么?是胡牌算法的性能瓶颈,还是网络同步的延迟处理?欢迎在评论区分享您的实战经验或技术疑问!
首发原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/30738.html
评论列表(3条)
这篇文章讲Java麻将胡牌算法挺实在的,尤其那个牌型用枚举的设计,确实能避免很多低级错误,代码看着也清爽。作为喜欢琢磨并发的人,我脑子里忍不住在想实战场景:这递归回溯的胡牌算法,如果真放到线上麻将平台,四人同时点炮胡牌,压力可不小啊。 递归虽然思路清晰,但层层调用在并发时有点吃性能,尤其胡大牌型(比如清一色)可能要遍历的组合太多。我琢磨着能不能把牌型拆解的任务并行化?比如把“找顺子”和“找刻子”独立成小任务扔进线程池试试。不过难点在牌的组合有依赖关系,分任务时共享状态的同步得小心处理,搞不好反而更慢。 作者用的枚举在并发里倒是个亮点——天生不可变对象,安全省心。要是换用对象实例表示牌,多线程同时修改状态就头疼了。不过递归过程中的临时集合(比如拆分出的顺子组)如果没处理好线程隔离,容易串数据。或许能用ThreadLocal存当前线程的计算状态?或者直接走无共享思路,每次胡牌计算深拷贝一份牌数据?虽然占内存但简单粗暴。 其实这类规则固定的算法,预编译可能更狠。比如把所有胡牌牌型哈希值缓存进ConcurrentHashMap,查胡牌变O(1)操作。不过预处理的时间空间成本得权衡,适合长驻内存的服务端场景。总体感觉思路不错,但要上线还得针对并发场景打磨下性能优化和状态隔离。
@心robot614:哈哈,你说到点子上了!递归在并发时确实吃性能,你提的并行化思路很有趣。做优化时,真该配个性能监控图表实时看耗时变化,火焰图看调用栈深度特别直观。预编译缓存那招在服务端肯定香。
作为分布式架构师,我觉得这个算法设计很巧妙。如果扩展到多节点处理在线麻将游戏,能更好地应对并发验证问题,感谢分享源码!