js实现斗地主的算法 验证牌型 找大于上家的牌型

js实现斗地主的算法 验证牌型 找大于上家的牌型,第1张

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录 前言一、核心算法二、出牌规则的制订1.出牌规则a.顺子b.连对或者对子c.飞机d.四带情况 2.提取公共方法3.选牌时验证牌型的方法4.找大于上一家的牌型方法例子 总结下吧


前言

最近喜欢用小程序斗地主,因此想自己也写一个斗地主的项目,下面就是关于选牌验证牌型以及寻找大于上一家牌型的方法,感兴趣的可以看下。注:以下每个方法输入的参数都是从大到小排序的数组,且元素都是number。


一、核心算法

一开始使用forEach 验证所选的牌组是否为飞机、连对、顺子、炸d,比如飞机:arr[idx] === arr[idx+3],arr[idx] - arr[idx+3] === 1;但是发现会存在几种问题 类似这种的[4,4,4,4,3,3,3]比较难以判断且飞机以及连对情况比较多,判断起来很麻烦。后来发现通过比较去重后的数组更方便代码逻辑更好写,步骤:
1.去重
2.比较
3.获取原数组 恢复原样
不多说,上代码:

核心算法 利用 连对 飞机 顺子 去重后的数组保持连续的特点 即 ....(n-2)+(n-1) + n
*/
    let findAndCheck = {
        noRepeat:function(arr) {
            let newArr = [];
            for(i=0;i<arr.length;i++){
                if(newArr.indexOf(arr[i]) === -1){
                    newArr.push(arr[i]);
                }
            }
            return newArr;
        },
        compare:function(arr,len) { //用于判断连对 飞机 顺子 的 连续的数
            let baseNum = arr[0];
            let n = 0;
            let newArr = [];
            let arr2 = [];
            arr.forEach(val => {
                if(baseNum - val === n) {//判断是否属于连续的数
                    newArr.push(val);
                    n++;
                } else {
                    baseNum = val;
                    if(newArr.length >= len){ //证明这里有符合条件的牌组
                        arr2.push(newArr);
                    }
                    newArr = [];
                    newArr.push(val);
                    n = 1;
                }
            });
            newArr = newArr.concat(...arr2);
            return newArr;
        },
        subArr:function(arr,noRepeatArr,len){ //原数组 去重的数组 截取长度个数
            let arr2 = [...arr];  //防止改变原数组
            let newArr = [];
            let amountArr = [];
            noRepeatArr.forEach((val)=>{
                let idx = arr2.indexOf(val);
                let subArr  = arr2.splice(idx,len);//拿到符合的项目
                amountArr.push(subArr);//符合条件的项目 方便直接出牌
                newArr = newArr.concat(subArr); //拿到符合的牌组和剩余的牌组 splic(index,length,item) 改变原数组 返回截取部分
            });
        return [newArr,arr2,amountArr];
        }
    }

二、出牌规则的制订 1.出牌规则 a.顺子

代码如下(示例):

    //顺子 p[n] - p[n+1] = 1或者-1  p[0] - p[n] = n
    
    function checkShunZi(paiZu,num) { //顺子情况 5-11  查找时可能 数组中可能存在两条以上的顺子
        paiZu = paiZu.filter((val)=>{
            return (val < 14);
        })
        let fitArr = []; 
        let unRule = []; //存放剩余的牌
        let isShunzi = false;
        let newArr = comFn(paiZu,fitArr,num);
        fitArr = newArr[0];
        unRule = newArr[1];
        let amountArr = newArr[2];
        if(fitArr.length === paiZu.length && fitArr.length >= 5) {
            isShunzi = true;
        }
        if(num){return [fitArr,unRule,amountArr]};
        console.log("isShunzi",isShunzi);
        return [isShunzi,1,fitArr,unRule];
    }
b.连对或者对子
function checkLianDui(paiZu,num) { //连对情况 6-20  3 - 12 4-13 查找时可能 数组中可能存在两个以上的连对
        if(paiZu.length > 2 ){
            paiZu = paiZu.filter((val)=>{ //去掉大于14的数
                return (val < 14);
            })
        }
        let fitArr = [];
        let unRule = [];
        let isLianDui = false;
        paiZu.map((val,idx)=>{
            if(val === paiZu[idx+1]) {
                fitArr = fitArr.concat(paiZu.slice(idx,(idx+1)));
            }
        });
        let newArr = comFn(paiZu,fitArr,num);
        fitArr = newArr[0];
        unRule = newArr[1];
        let amountArr = newArr[2];
        if(fitArr.length === paiZu.length && fitArr.length >= 6 || fitArr.length === 2 ) {isLianDui = true;};
        if(num){return [fitArr,unRule,amountArr]};
        return [isLianDui,2,fitArr,unRule];
    }

c.飞机
    // 牌组中飞机 或者三个同样的牌 p[0] === p[2]  p[3] === p[5] p[6] === p[8]  p[9] === p[11] p[12] === p[14]
    // 得到 p[n] === p[n+2] 如果是飞机 则有 p[n] - p[n+3] = 1 或者 -1

    function checkFeiJI(paiZu,num) {  //查找时可能 数组中可能存在两个以上的个飞机
        let fitArr = [];  //存放三个一样的牌组
        //三个 或者飞机
        let isFeiJi = false;
        let p = [...paiZu];
        let unRule = [];//存放不符合规则的元素
        p.map((val,idx)=>{
            if(p[idx] === p[idx+2]){
                fitArr = fitArr.concat(p.slice(idx,idx+3));
            }
        });
        let newArr = comFn(paiZu,fitArr,num);
        fitArr = newArr[0];
        unRule = newArr[1];
        let amountArr = newArr[2];
        if(fitArr.length > 0 && fitArr.length < p.length){//证明飞机有带 带单张 或者 对子 
            let duiZiNum = 0;//对子的个数
            let daiNum = fitArr.length / 3 ; //要带牌的个数
            if(daiNum === unRule.length) {
                console.log("它是飞机带了个"+ daiNum + "个",fitArr.join("")+unRule.join(""));
                isFeiJi = true;
            } else if(daiNum === (unRule.length/2)){
                //判断带的牌是否为对子
                let n = 0;
                let m = 1;
                unRule.map((val,idx)=>{
                    //12 34 56相等
                    if(unRule[idx+n] && unRule[idx+n] === unRule[idx+m]){
                        duiZiNum++;
                        n++;
                        m++;
                    }
                })
                if(duiZiNum === daiNum){
                    console.log(`它是飞机带了`+duiZiNum+"对",fitArr.join("")+unRule.join(""));
                    isFeiJi = true;
                }
            } else if((unRule.length + 3) === (daiNum-1)){
                unRule = unRule.concat(fitArr.splice(-3,4));
                console.log("它是飞机带了",fitArr.join("")+unRule.join(""));
                isFeiJi = true;
            }
        } else if(fitArr.length === p.length) {
            console.log(`它是飞机没带`,fitArr.join("")+unRule.join(""));
            isFeiJi = true;
        } else {
            console.log("不是飞机");
        }
        if(num){return [fitArr,unRule,amountArr]};
        return [isFeiJi,3,fitArr,unRule];
    }
d.四带情况
//炸d 或者 四带二 四带两对 王炸
    //得到 p[n] === p[n+3]

    function checkZhaDan(paiZu,num){ //个数 大于3 小于9
        let p = [...paiZu];
        let fitArr = [];
        let unRule = [];
        let wangZha = [];
        let isZhaDan = false;
        p.map((val,idx)=>{
            if(val > 15 && p[idx+1] > 15){
                wangZha = [val,p[idx+1]];
                console.log("王炸");
            }
            if(val === p[idx+3]){ //找到符合条件的元素
                fitArr = fitArr.concat(p.slice(idx,(idx+4)));
            }
        });
        let newArr = comFn(paiZu,fitArr,num);
        fitArr = newArr[0];
        unRule = newArr[1];
        let amountArr = newArr[2];
        if(wangZha.length) { //王炸单独处理
            fitArr = wangZha.concat(fitArr);
            amountArr.push(wangZha);
            unRule.splice(0,2);
        }
        console.log("找炸d",fitArr);
        if(num){
            return [fitArr,unRule,amountArr];
        }
        //判断出牌类型
        if(fitArr.length === 4 || fitArr.length === 2){
            if(fitArr.length === 2){
                console.log("它是王炸",p.join(""));
                isZhaDan = true;
            } else if(fitArr.length === 4  && unRule.length === 0){
                console.log("它是炸d",fitArr.join(""));
                isZhaDan = true;
            } else if(unRule.length === 2){
                console.log("它是四带二",p.join(""));
                isZhaDan = true;
            } else if(unRule[0] === unRule[1] && unRule[2] === unRule[3]) {//判断对子情况
                console.log("它是四带两对",p.join(""));
                isZhaDan = true;
            }
        } else if(fitArr.length === 8){ //带炸d
            console.log("它是四带两对",p.join(""))
            isZhaDan = true;
        } else {
            console.log("不是炸d的其他牌型",unRule.join(""));
        }
        return [isZhaDan,4,fitArr,unRule];
    }
2.提取公共方法

代码如下(示例):

//提取公共方法 让代码个更优雅
    function comFn(paiZu,arr,num){
        let newArr = findAndCheck.noRepeat(arr);//去重 
        newArr = findAndCheck.compare(newArr,num);//比较 拿到合适的牌组
        newArr = findAndCheck.subArr(paiZu,newArr,4);//重新组成原来的牌
        return newArr;
    }
3.选牌时验证牌型的方法

代码如下:

//出牌
    let rules = [checkZhaDan,checkFeiJI,checkLianDui,checkShunZi]; //将牌的规则放入数组,当选完牌后循环验证
    function checkPai(paiZu,isOk) {
        // if(!isOk){return}; //用于判断是否到你出牌了
        let result = [];
        if(paiZu.length === 1){return [true,[paiZu[0]],[]];};//单张
        let res = rules.some((val,idx)=>{
            result = val(paiZu);
            return result[0];
        });
        return result;
        if(res) {

        } else {
            console.log("不符合出牌规则");
        }
        console.log("res",res);
    }
4.找大于上一家的牌型方法

代码如下:

//查找比上家大的牌组
    function findPai(nowPaiZu,lastPaiZu) { //nowPaiZu:本家牌组 lastPaiZu:上一家出的牌型
        //判断上家出的什么牌 也可以直接通过服务器返回相关信息
        let paiType = checkPai(lastPaiZu); //找到它的要比较的最大值 使用checkPai方法验证上家出的时什么牌型返回[boolean,number,符合的数组,带的数组]
        console.log("上家的牌型",paiType);
        let len = paiType[2].length;
        let max = paiType[2][0];
        let fitArr = nowPaiZu.filter((val)=>{
            return (val > max)
        });
        let res = [];
        if(paiType[1] !== 4 && fitArr.length < paiType[2].length){
            res = checkZhaDan(nowPaiZu,1);
            return;
        }
        switch(paiType[1]) { //找对子 1 找连对 3 找飞机 2 找炸d 1 找顺子 5
            case 1:{
                res = checkShunZi(fitArr,5);
                break;
            }; //找没有重复的牌
            case 2:{
                let n = 1;
                if(len > 2){n=3};
                res = checkLianDui(fitArr,3);
                break;
            };
            case 3:{
                res = checkFeiJI(fitArr,2);
                break;
            };
            case 4:{
                res = checkZhaDan(fitArr,1);
                break;
            };
            default :{
                res = checkDanZhang(fitArr);
            }
        }
        let isTrue = res[0].length - len;
        if(isTrue >= 0) {
            console.log("有可以打的牌",res[0].join(""));
            if(paiType[3].length>0 && paiType[1] > 3){//证明上一家出的牌有带
                let surplusArr = nowPaiZu.filter((val)=>{
                    return (val <= max);
                });
                if((res[0].length+surplusArr.length) < lastPaiZu.length){return;} //牌组不够长
                let daiNum = 0 ; //要带牌的个数
                let len = paiType[3].length;
                if(paiType[1] === 3){ //飞机  如果单张或对子不够的处理没做 
                    daiNum = paiType[2].length / 3 ; //要带牌的个数
                    if(daiNum !== len){ //对子
                        let duiArr = checkLianDui(surplusArr,1);
                    } else {//单张
                        let danArr = checkDanZhang(surplusArr);
                    }
                } else {//炸d
                    if(paiType[2].length === len){ //四带两对
                        let duiArr = checkLianDui(surplusArr,1);
                    } else {
                        let danArr = checkDanZhang(surplusArr);
                    }
                }
                //带单张
                let res = checkDanZhang(surplusArr);
                //带对子
                let duiziNum = 0;
            }
        } 

        if(isTrue < 0 && paiType[1] !== 4){
            res = checkZhaDan(nowPaiZu,1);
            console.log("有炸d",res[0].join(""));
        } else {
            // console.log("没牌打了")
        }
    }
例子
    let lastPai = [4,4,4,4];
    let nowPai = [17,16,11,11,11,11,10,10,10,10,7,7,7,7,5,4,4,4,4,3];
    findPai(nowPai,lastPai);
总结下吧

到这里就完毕了,虽然还没有全部完成,但是基本的算法已经完成了,剩下的只要补充完整就差不多了。电脑端选牌可以用window.getSelection();选取牌,手机端可以用touch事件。上线的话利用websocket实现多人在线。有时间就会写下一篇。欢迎评论点赞。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1297432.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存