#推荐
红中房卡麻将游戏开发技术文档(百游麻将实战篇)

2026-08-06 1,910

这段时间给几个合伙人做百游红中房卡麻将的二次开发,项目跑起来之后抽空把核心代码和踩坑点捋了一遍,干脆整成一份技术文档分享出来。网上关于红中麻将的算法文章不少,但多数只给个伪代码,真到用的时候一堆边界条件处理不了。下面我会把整个项目拆开,重点讲红中赖子胡牌判定、房卡房间状态机、客户端手牌排序与出牌校验,全上真实代码片段。


一、红中麻将胡牌算法:递归+贪心拆解

红中麻将最大的特点就是红中可以充当任何牌,这使得普通麻将的“分牌型再判定”方式完全失效,必须改造算法。我们最终采用“先移除红中再回溯匹配”的策略,核心逻辑如下:

1. 数据结构

手牌用一个长度为34的数组表示(万条筒字,不含花牌),索引0-8万,9-17条,18-26筒,27-33字牌。红中定义为索引31(字牌中的“中”),实际规则中红中是万能牌,不算入手牌数量限制。

// c++版(服务端验证用)
bool IsHu(int cards[34], int hongzhong_count) {
    // 递归回溯,每次尝试将红中当作缺的那张牌
    return BacktrackHu(cards, hongzhong_count);
}

2. 递归检测函数

思路:如果手牌总数(已排除红中)模3不等于2,直接false(麻将胡牌必须14张,红中不占手数,这里实际是检查有效牌数)。然后尝试移除一对将牌,剩余部分看是否能全部分解为刻子或顺子。红中可以在任何一步当作缺失的牌参与组合。

bool BacktrackHu(int cards[34], int hongzhong) {
    // 先检查是否全部为0
    bool all_zero = true;
    for (int i = 0; i < 34; i++) if (cards[i] > 0) { all_zero = false; break; }
    if (all_zero) return true;

    // 尝试找一个将牌(一对),这里红中也可以参与组成将牌的一部分
    for (int i = 0; i < 34; i++) {
        if (cards[i] >= 1 && hongzhong >= 1) {
            // 一张牌+一个红中当将
            cards[i]--;
            hongzhong--;
            if (ProcessRemaining(cards, hongzhong)) return true;
            cards[i]++;
            hongzhong++;
        }
        if (cards[i] >= 2) {
            // 直接一对将
            cards[i] -= 2;
            if (ProcessRemaining(cards, hongzhong)) return true;
            cards[i] += 2;
        }
    }
    // 也可以用两个红中直接当将
    if (hongzhong >= 2) {
        hongzhong -= 2;
        if (ProcessRemaining(cards, hongzhong)) return true;
        hongzhong += 2;
    }
    return false;
}

ProcessRemaining负责把剩余手牌全部分解为顺子或刻子,红中同样可以补位。这部分代码会遍历每张牌,优先移除刻子,再尝试顺子。遇到无法移除的情况,如果还有红中,就用一个红中来凑成刻子或顺子。具体实现较长,之后可以贴到git上。

3. 性能优化

实测递归深度最大才14层,加上红中最多4个,复杂度完全可控。服务端Java版本我们改成了动态规划+记忆化剪枝,压测每秒能判定5万手牌,远高于实际需求。

红中房卡麻将游戏开发技术文档(百游麻将实战篇)


二、房卡模式服务器:房间状态机 + 操作锁

百游房卡麻将的服务端用Java(Netty)+ Redis + MySQL,核心是房间服务。每个房间本质上是一个状态机,包含等待、准备、发牌、出牌、杠碰胡、结算等状态,这里给出状态机骨架代码。

1. 状态定义与转换

public enum RoomState {
    WAITING,      // 等待玩家加入
    READY,        // 准备阶段
    DEALING,      // 发牌
    PLAYER_ACTION,// 玩家操作:出牌、杠碰胡
    SETTLE        // 结算
}

public class MahjongRoom {
    private RoomState state;
    public synchronized void nextState(RoomState newState) {
        // 状态转换合法性校验
        if (!isValidTransition(state, newState)) {
            throw new IllegalStateException("非法状态跳转");
        }
        this.state = newState;
        onStateEnter(newState);
    }
}

2. 并发控制

房间内操作全部串行化处理。我们用玩家uid的hash取模分配到单线程EventLoop,确保同一房间内的所有消息都按顺序执行,避免复杂的锁竞争。核心代码:

EventLoopGroup roomGroup = new NioEventLoopGroup(8);
// 根据房间ID取模绑定线程
int index = roomId.hashCode() & 7;
roomGroup.register(channel, index);

3. 红中百搭实时计算

每次摸牌或出牌,服务端都要重新计算当前玩家的听牌列表。我们维护一个TingCalculator类,内部缓存了所有可能手牌的听牌结果。利用位运算压缩手牌,使得红中百搭下的听牌计算也能在1ms内完成。

public Set<Integer> calcTing(long[] handPacked, int hongzhongCount) {
    // handPacked是三个long,分别存储万条筒,每个位1代表有一张
    Set<Integer> tingSet = new HashSet<>();
    for (int i = 0; i < 34; i++) {
        // 假设摸进第i张牌,加入手牌后是否胡牌
        handPacked[i/10] |= (1L << (i%10));
        if (IsHu(handPacked, hongzhongCount)) {
            tingSet.add(i);
        }
        handPacked[i/10] &= ~(1L << (i%10));
    }
    return tingSet;
}

红中房卡麻将游戏开发技术文档(百游麻将实战篇)


三、客户端关键逻辑:手牌排序与出牌校验

客户端用的是Cocos Creator 2.4.x + TypeScript。红中麻将的牌面展示特殊点在于“红中”高亮金框,且不能打出。

1. 手牌智能排序

排序规则:万、条、筒、字,每类内部按点数,红中永远放最右侧。考虑到玩家可能频繁插入牌,我们用插入排序优化局部变更。

sortHandCards(cards: number[]): number[] {
    const wan = [], tiao = [], tong = [], zi = [];
    for (let v of cards) {
        if (v < 9) wan.push(v);
        else if (v < 18) tiao.push(v);
        else if (v < 27) tong.push(v);
        else zi.push(v);
    }
    // 红中始终排最后
    zi.sort((a,b) => {
        if (a === 31) return 1;
        if (b === 31) return -1;
        return a - b;
    });
    return [...wan.sort((a,b)=>a-b), ...tiao.sort((a,b)=>a-b), ...tong.sort((a,b)=>a-b), ...zi];
}

2. 出牌校验与动画

玩家点击一张手牌时,先本地校验该牌是否红中(禁止打出),然后向服务器发送出牌指令。收到服务器确认后才真正播放牌飞向牌桌的动画,避免因网络延迟导致客户端状态错乱。

onCardTouch(cardIndex: number) {
    const cardValue = this.handCards[cardIndex];
    if (cardValue === 31) { // 红中
        this.showTip("红中不能打出");
        return;
    }
    this.sendPlayCard(cardValue); // 先发协议
    this.setCardPending(cardIndex); // 该牌置灰,等待服务器应答
}

3. 碰杠胡操作面板

当别人打出一张牌时,服务器下发可操作列表。客户端用这个列表生成“碰/杠/胡/过”按钮。红中不能点杠,需特殊处理:

showActions(actions: string[]) {
    this.btnPeng.active = actions.includes("peng");
    this.btnGang.active = actions.includes("gang") && this.lastDiscardTile !== 31; // 别人打的不是红中才能杠
    this.btnHu.active = actions.includes("hu");
    this.btnPass.active = true;
}

红中房卡麻将游戏开发技术文档(百游麻将实战篇)


四、百游房卡麻将防作弊与热更新细节

房卡模式房间号有规律易被遍历,我们在房间ID生成上使用了雪花算法,并加入校验位,防止脚本批量加入。同时客户端与服务端双向校验时间戳,消息签名用HMAC-SHA256,防止篡改。

另外,为了通过微信小游戏审核,我们移除了所有积分排名和金币显示,只保留个人战绩折线图,并把“红中麻将”的玩法说明写进《游戏用户协议》。

红中麻将的代码量不小,这里只挑了核心逻辑。如果你正在做百游麻将或其它地方房卡棋牌,遇到算法、架构或者过审问题,可以直接扫下面的码找我聊。

客服微信二维码
收藏 打赏

感谢您的支持,我会继续努力的!

打开USDT(trc-20)扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

Ts:本站所有内容均为互联网收集整理和网友上传。仅限于学习研究,请必须在24小时内删除。否则由此引发的法律纠纷及连带责任本站概不承担。

如侵犯到您的合法权益,请联系我们删除侵权资源!

韩仔技术 技术文档 红中房卡麻将游戏开发技术文档(百游麻将实战篇) https://www.hanzijs.com/zixue/wendang/8634.html

相关文章

发表评论
暂无评论