文章发布于:2024年4月28日

用 Figjam 画了一个斗地主的界面布局,大致琢磨了一下实现难度,发现没有特别复杂的地方,感觉如果做的话,问题不大。

不过我并不打算真的做。如果说做在线五子棋让我了解“游戏服务器”的概念,那做斗地主有什么目的呢?想来想去,找不到一个说服自己的理由。

不过要说理由,有一个勉强的借口,就是可以通过做斗地主,来学习如何玩斗地主。是的,我不会玩斗地主。在做的过程中,要了解规则,特别是需要计算一家的牌是否大于另一家的牌,有多少种合法的组合等等,做完以后还需要测试,这样搞下来,我应该能学会玩斗地主。。。


后续:

写了一个牌型检测的功能,标准根据《竞技二打一扑克竞赛规则》。


牌型:

1. 单张 | 3
2. 对子 | 33
3. 王炸 | 大王小王
4. 炸弹 | 3333
5. 顺子 | 34567
6. 连对 | 334455
7. 三不带,三带一,三带对 | 333,333A,333AA
8. 四带二,四带一对,四带两对| 3333AK,3333AA,3333AAKK
9. 飞机,飞机单带,飞机双带,组合飞机(规律和普通飞机相似) | 333444,333444AK,333444AAKK,333444555AAKK22

//3 4 5 6 7 8 9 X J  Q  K  A  2  L  M
//3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

const cards_type_check = {

    equal_2: function (str) {
        return str[0] === str[1];
    },

    equal_3: function (str) {
        return str[0] === str[1] && str[1] === str[2]
    },

    equal_4: function (str) {
        return str[0] === str[1] && str[1] === str[2] && str[2] === str[3];
    },

    flat_arr: function (arr) {
        let rst = [];
        for (const str of arr) {
            if (Array.isArray(str)) {
                rst = rst.concat(this.flat_arr(str))
            } else {
                rst.push(str);
            }
        }
        return rst;
    },

    group: function (cards) {

        const obj = {};

        for (const card of cards) {
            if (!obj[card]) {
                obj[card] = [card];
            } else {
                obj[card].push(card);
            }
        }

        const type_list = Object.keys(obj).sort((a, b) => (+a) - (+b));

        let arr = [];

        type_list.forEach(tp => {
            arr.push(obj[tp])
        })

        arr = arr.sort((a, b) => b.length - a.length);

        return this.flat_arr(arr);
    },

    left_str: function (str, len) {
        return str.slice(0, len);
    },

    right_str: function (str, len) {
        return str.slice(-len);
    },

    slice_str: function (str, start, len) {
        return str.slice(start - 1, start + len - 1);
    },

    straight: function (cards) {
        if (cards[cards.length - 1] > 14) {
            return false;
        }
        let tmp;
        for (const card of cards) {
            if (!tmp) {
                tmp = card;
            } else {
                if (card - tmp !== 1) {
                    return false;
                } else {
                    tmp = card;
                }
            }
        }
        return true;
    },

    chain_pair: function (cards) {
        if (cards[cards.length - 1] > 14) {
            return false;
        }
        let tmp;
        for (let i = 0; i < cards.length; i += 2) {
            const pair = this.slice_str(cards, i + 1, 2);
            if (!this.equal_2(pair)) {
                return false;
            }
            if (!tmp) {
                tmp = pair;
            } else {
                if (pair[0] - tmp[0] !== 1) {
                    return false;
                }
                tmp = pair;
            }
        }
        return true;
    },

    chain_three: function (cards) {
        if (cards[cards.length - 1] > 14) {
            return false;
        }
        let tmp;
        for (let i = 0; i < cards.length; i += 3) {
            const three = this.slice_str(cards, i + 1, 3);
            if (!this.equal_3(three)) {
                return false;
            }
            if (!tmp) {
                tmp = three;
            } else {
                if (three[0] - tmp[0] !== 1) {
                    return false;
                }
                tmp = three;
            }
        }
        return true;
    },

    dict: {
        '3': 3, '4': 4, '5': 5, '6': 6,
        '7': 7, '8': 8, '9': 9, 'X': 10,
        'J': 11, 'Q': 12, 'K': 13,
        'A': 14, '2': 15, 'L': 16, 'M': 17
    },

    convert: function (cards) {
        const arr = [];
        for (const card of cards) {
            arr.push(this.dict[card])
        }
        return arr;
    },

    check: function (cards) {
        //test mode
        if (!/^[3456789XJQKA2LM]{1,20}$/.test(cards)) {
            return { type : "无效字符" }
        }

        cards = this.convert(cards);
        cards = this.group(cards);
        const len = cards.length;
        const fmt = function (type, flag, cvt_cards) {
            return {
                type: type,
                flag: flag || null,
                cards: cvt_cards || cards
            }
        }

        if (len === 1) {
            return fmt("单牌");
        } else if (len === 2) {
            if (cards[0] === 16 && cards[1] === 17) {
                return fmt("王炸")
            }
            if (this.equal_2(cards)) {
                return fmt("对子")
            }
        }

        for (const card of cards) {
            if (card === 16 || card === 17) {
                return fmt("未知牌型");
            }
        }

        if (len === 3 && this.equal_3(cards)) {
            return fmt("三不带");
        } else if (len === 4) {
            if (this.equal_4(cards)) {
                return fmt("炸弹");
            }
            if (this.equal_3(this.left_str(cards, 3))) {
                return fmt("三带一");
            }
        } else if (len === 5) {
            if (this.straight(cards)) {
                return fmt("顺子", 5)
            }
            if (this.equal_3(this.left_str(cards, 3)) && this.equal_2(this.right_str(cards, 2))) {
                return fmt("三带对");
            }
        } else if (len === 6) {
            if (this.straight(cards)) {
                return fmt("顺子", 6);
            }
            if (this.chain_three(cards)) {
                return fmt("飞机", 2);
            }
            if (this.chain_pair(cards)) {
                return fmt("连对", 3);
            }
            if (this.equal_4(this.left_str(cards, 4))) {
                if (this.equal_2(this.right_str(cards, 2))) {
                    return fmt("四带一对");
                } else {
                    return fmt("四带二单");
                }
            }
        } else if (len === 7) {
            if (this.straight(cards)) {
                return fmt("顺子", 7)
            }
        } else if (len === 8) {
            if (this.straight(cards)) {
                return fmt("顺子", 8)
            }
            if (this.chain_pair(cards)) {
                return fmt("连对", 4)
            }
            const card_6 = this.left_str(cards, 6);
            if (this.chain_three(card_6)) {
                return fmt("二飞机单带", 2)
            }
            //33334455
            if (this.equal_4(this.left_str(cards, 4))
                && this.equal_2(this.slice_str(cards, 5, 2))
                && this.equal_2(this.right_str(cards, 2))
                && !this.equal_2(this.slice_str(cards, 6, 2))//所带牌中不包括炸弹,所以不能33334444
            ) {
                return fmt("四带二对")
            }
        } else if (len === 9) {
            if (this.chain_three(cards)) {
                return fmt("飞机", 3)
            }
            if (this.straight(cards)) {
                return fmt("顺子", 9)
            }
        } else if (len === 10) {
            if (this.straight(cards)) {
                return fmt("顺子", 10)
            }
            if (this.chain_pair(cards)) {
                return fmt("连对", 5);
            }
            if (this.chain_three(this.left_str(cards, 6))
                && this.equal_2(this.slice_str(cards, 7, 2))
                && this.equal_2(this.slice_str(cards, 9, 2))
            ) {
                return fmt("二飞机二对", 2);
            }
        } else if (len === 11 && this.straight(cards)) {
            return fmt("顺子", 11);
        } else if (len === 12) {
            if (this.straight(cards)) {
                return fmt("顺子", 12);
            }
            if (this.chain_pair(cards)) {
                return fmt("连对", 6);
            }
            if (this.chain_three(cards)) {
                return fmt("飞机", 4);
            }

            let card_9 = this.left_str(cards, 9);

            if (this.chain_three(card_9)) {
                return fmt("三飞机三单", 3)
            }

            const cvt = this.right_str(cards, 9).concat(this.left_str(cards, 3));

            card_9 = this.left_str(cvt, 9);

            if (this.chain_three(card_9)) {
                return fmt("三飞机三单", 3, cvt)
            }

        } else if (len === 14) {
            if (this.chain_pair(cards)) {
                return fmt("连对", 7);
            }
        } else if (len === 15) {
            if (this.chain_three(cards)) {
                return fmt("飞机", 5);
            }
            if (
                this.chain_three(this.left_str(cards, 9))
                && this.equal_2(this.slice_str(cards, 10, 2))
                && this.equal_2(this.slice_str(cards, 12, 2))
                && this.equal_2(this.slice_str(cards, 14, 2))
            ) {
                return fmt("三飞机三对", 3);
            }
        } else if (len === 16) {
            if (this.chain_pair(cards)) {
                return fmt("连对", 8);
            }
            let card_12 = this.left_str(cards, 12);

            if (this.chain_three(card_12)) {
                return fmt("四飞机四单", 4)
            }

            const cvt = this.right_str(cards, 13).concat(this.left_str(cards, 3));

            card_12 = this.left_str(cvt, 12);

            if (this.chain_three(card_12)) {
                return fmt("四飞机四单", 4, cvt)
            }

        } else if (len === 18) {
            if (this.chain_pair(cards)) {
                return fmt("连对", 9);
            }
            if (this.chain_three(cards)) {
                return fmt("飞机", 6);
            }
        } else if (len === 20) {
            if (this.chain_pair(cards)) {
                return fmt("连对", 10);
            }

            let card_15 = this.left_str(cards, 15);

            if (this.chain_three(card_15)) {
                return fmt("五飞机五单", 5);
            }

            const cvt = this.right_str(cards, 17).concat(this.left_str(cards, 3));

            card_15 = this.left_str(cvt, 15);

            if (this.chain_three(card_15)) {
                return fmt("五飞机五单", 5, cvt);
            }
            
            if (
                this.chain_three(this.left_str(cards, 12))
                && this.equal_2(this.slice_str(cards, 13, 2))
                && this.equal_2(this.slice_str(cards, 15, 2))
                && this.equal_2(this.slice_str(cards, 17, 2))
                && this.equal_2(this.slice_str(cards, 19, 2))
            ) {
                return fmt("四飞机四对", 4);
            }
        }
        return fmt("未知牌型")
    }
}


//测试function t(cards) {
    console.log(cards_type_check.check(cards));
}

t("A")
t("ML")
t("333")
t("JJJJ")
t("AAAA23")
t("AAAA2233")
t("999XXXJJJ")
t("3456789XJQKA")
//理论上最大牌型
t("XXJJQQ33445566778899")
t("XXX888999JJJ44553366")
t("33322XXXJJJQQQKKKAAA")////输出type :单牌
flag :null
cards :[14]
type :王炸
flag :null
cards :[16,17]
type :三不带
flag :null
cards :[3,3,3]
type :炸弹
flag :null
cards :[11,11,11,11]
type :四带二单
flag :null
cards :[14,14,14,14,3,15]
type :四带二对
flag :null
cards :[14,14,14,14,3,3,15,15]
type :飞机
flag :3
cards :[9,9,9,10,10,10,11,11,11]
type :顺子
flag :12
cards :[3,4,5,6,7,8,9,10,11,12,13,14]
type :连对
flag :10
cards :[3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12]
type :四飞机四对
flag :4
cards :[8,8,8,9,9,9,10,10,10,11,11,11,3,3,4,4,5,5,6,6]
type :五飞机五单
flag :5
cards :[10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,3,3,3]<br/>