鱼C论坛

 找回密码
 立即注册
查看: 4058|回复: 1

[技术交流] 麻将和牌解析算法Java实现

[复制链接]
发表于 2017-10-2 15:06:47 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 zxcv8556723 于 2017-10-3 09:12 编辑

先上效果图:
1.png
算法思路:
  首先对传入的牌编号数组进行排序(整牌),防止超过4张相同牌型,无效牌编号的出现

  然后根据3n+2的公式对牌组进行拆解,如果拆解最后没有剩余,即满足和牌规定

  如果有剩余,且不能继续拆解时,将牌组还原,向后偏移到下一个牌组下标,继续重新拆解,直到拆解到牌组的最后

  3n+2公式不满足时,判断固定和牌牌型:国士无双,七对子

  判断国士无双采用去对牌中的一张,然后判断剩余牌是否都是边牌杂牌,如果有不是边牌杂牌,直接不满足
  判断七对子采用收集七个对子的单牌数组,然后对数组进行重复判断,不重复即满足

  最后返回最终结果
  
  枚举起手的牌,并且判断和牌,返回可以胡牌的String,没有和牌返回0
  寻找和牌使用枚举,先加入一张牌,然后排序整理,最后带入和牌判断的方法判断
  通过判断后记录下来,全部枚举完毕后返回记录下的数组,则是全部的和牌可能
  
牌组编号转换对照:
(0表示空,没有牌)
  东    1
  西    2
  南    3
  北    4
  中    5
  白    6
  发    7

  万    11-19
  筒    21-29
  索    31-39

源码:(该源码完全是我基于解析算法写出,没有进行进一步的优化,也没有进行所有和牌的验证,可能性太多了,仅供交流分享)
package com.zdg.mahjong;

public class Rule {

    public String get_HuPai(String paiZu) {
        String result = null;
        int[] intTemp = null;
        for (int i = 1; i < 40; i++) {
            if (!(i == 8 || i == 9 || i == 10 || i == 20 || i == 30)) {
                intTemp = zhengLi(paiZu + "," + tr2(i));
                if (isHuPai(intTemp)) {
                    result = result + "," + tr2(i);
                }
            }
        }
        //===================打印排序后的牌组===================
        System.out.print("待和牌:");
        for (int n = 0; n < intTemp.length - 1; n++) {
            System.out.print(tr2(intTemp[n]));
            if (n != intTemp.length - 2) {
                System.out.print(",");
            }
        }
        System.out.println();
        //===================打印排序后的牌组===================
        if (result == null) {
            //没有找到和牌
            return "0";
        } else {
            //去除第一个逗号
            return result.substring(5);
        }
    }

    public boolean is_HuPai(String paiZu) {
        int[] intTemp = zhengLi(paiZu);
        //===================打印排序后的牌组===================
        System.out.print("待测牌:");
        for (int n = 0; n < intTemp.length; n++) {
            System.out.print(tr2(intTemp[n]));
            if (n != intTemp.length - 1) {
                System.out.print(",");
            }
        }
        System.out.println();
        //===================打印排序后的牌组===================
        return isHuPai(intTemp);
    }

    private int[] zhengLi(String paiZu) {
        String[] strTemp = paiZu.split(",");
        int[] intTemp = new int[strTemp.length];
        //防止牌组超过14张,或者传入空牌组
        if (strTemp.length > 14 | strTemp.length == 0) {
            return null;
        } else {
            //String转int,便于后续使用
            for (int i = 0; i < strTemp.length; i++) {
                //牌组文字编号转换
                intTemp[i] = tr1(strTemp[i]);
                if (intTemp[i] == 0) {
                    //牌组不在编号范围内
                    return null;
                }
            }
            //对牌组进行排序,从小到大
            intTemp = xuanzePaiXu(intTemp);
            //同一种牌不超4张牌检测
            for (int anIntTemp : intTemp) {
                if (is4(intTemp, anIntTemp)) {
                } else {
                    return null;
                }
            }
            return intTemp;
        }
    }

    private boolean isHuPai(int[] paiZu) {
        /*
        如果是和牌,返回true
         */
        int[] tempPaiZu = paiZu;//tempPaiZu是用来拆解的牌组
           /*
        3n+2和牌牌型

        大致过程:去除将牌;剩余牌组去除刻牌;剩余牌组去除连续牌
         */
        for (int a = 0; a < tempPaiZu.length - 1; a++) {
            int temp1 = tempPaiZu[a];
            int temp1Type = tempPaiZu[a] / 10;
            if (tempPaiZu[a + 1] == temp1 & tempPaiZu[a + 1] / 10 == temp1Type) {
                //找到将牌,删除,继续删除刻牌
                tempPaiZu = delArr(tempPaiZu, a);
                tempPaiZu = delArr(tempPaiZu, a);
                if (tempPaiZu.length % 3 != 0) {
                    //相公牌,永不和牌
                    return false;
                } else {
                    if (tempPaiZu.length == 0) {
                        //和牌类型:一对牌,只有两张
                        return true;
                    } else {
                        //去除刻牌和链牌,最多四组,四层for循环
                        /*
                        刻        刻        刻        刻
                        刻        刻        刻        链
                        刻        刻        链        刻
                        刻        刻        链        链
                        刻        链        刻        刻
                        刻        链        刻        链
                        刻        链        链        刻
                        刻        链        链        链
                        链        刻        刻        刻
                        链        刻        刻        链
                        链        刻        链        刻
                        链        刻        链        链
                        链        链        刻        刻
                        链        链        刻        链
                        链        链        链        刻
                        链        链        链        链
                         */
                        int[] backPaiZu = tempPaiZu;//备份牌组
                        int[] result;
                        for (int n1=0;n1<2;n1++){
                            for (int n2=0;n2<2;n2++){
                                for (int n3=0;n3<2;n3++){
                                    for (int n4=0;n4<2;n4++){
                                        result = fenjiePaiZu(fenjiePaiZu(fenjiePaiZu(fenjiePaiZu(tempPaiZu, n1), n2), n3), n4);
                                        if (result[0] == -1) {
                                            return true;
                                        } else if (result[0] == 0) {
                                            tempPaiZu= backPaiZu;
                                        }else {
                                            return false;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    //还原将牌
                    tempPaiZu = addArr(tempPaiZu,temp1);
                    tempPaiZu = addArr(tempPaiZu,temp1);
                    tempPaiZu=xuanzePaiXu(tempPaiZu);
                }
            }
        }
        //3n+2匹配失败
        tempPaiZu = paiZu;
        /*
        国士无双和牌牌型
        国士无双牌组只有一个对牌,其余牌全部为除对牌以外的杂牌和三花边牌,且不能重复
         */
        //去除对牌的一张单牌
        if (tempPaiZu.length == 14) {
            for (int a = 1; a < 14; a++) {
                if (tempPaiZu[a] == tempPaiZu[a - 1]) {
                    if (tempPaiZu[a] / 10 == 0 | tempPaiZu[a] % 10 == 1 | tempPaiZu[a] % 10 == 9) {
                        tempPaiZu = delArr(tempPaiZu, a);
                        break;
                    }
                }
            }
        }
        boolean isGuoShiWuShuang = false;
        if (tempPaiZu.length == 13) {
            isGuoShiWuShuang = true;
            for (int a = 0; a < 13; a++) {
                //边牌杂牌检测
                if (!(tempPaiZu[a] / 10 == 0 | tempPaiZu[a] % 10 == 1 | tempPaiZu[a] % 10 == 9)) {
                    isGuoShiWuShuang = false;
                    break;
                }
            }
        }
        if (isGuoShiWuShuang) {
            return true;
        }
        //国士无双匹配失败,还原牌组
        tempPaiZu = paiZu;
        /*
        七对子和牌牌型
         */
        boolean isQiDuiZi = false;
        if (tempPaiZu.length == 14) {
            int[] tempQiDuiZi = new int[7];
            for (int i = 0; i < tempQiDuiZi.length; i++) {
                //判断都为对牌,牌型不重复
                if ((tempPaiZu[2 * i] == tempPaiZu[2 * i + 1]) & (tempPaiZu[2 * i] / 10 == tempPaiZu[2 * i + 1] / 10)) {
                    tempQiDuiZi[i] = tempPaiZu[2 * i];
                    isQiDuiZi = true;
                } else {
                    isQiDuiZi = false;
                    break;
                }
            }
            if (isQiDuiZi) {
                for (int i = 1; i < 7; i++) {
                    //七个对子牌组没有重复
                    if (tempQiDuiZi[i - 1] == tempQiDuiZi[i]) {
                        isQiDuiZi = false;
                        break;
                    } else {
                        isQiDuiZi = true;
                    }
                }
            }
            if (isQiDuiZi) {
                return true;
            }
        }
        /*
        //七对子匹配失败,还原数组
        tempPaiZu = paiZu;
        */
        //所有可能都没有匹配
        return false;
    }

    private static int[] fenjiePaiZu(int[] tempPaiZu, int type) {
        /*
        type==>0删除刻牌
        type==>1删除链牌

        fenjiePaiZu==>返回tempPaiZu,表示删除成功
        fenjiePaiZu==>返回{-1},表示和牌成功
        fenjiePaiZu==>返回{0},表示分解失败
        */
        if (tempPaiZu[0] == -1) {
            return new int[]{-1};
        }
        if (tempPaiZu[0] == 0) {
            return new int[]{0};
        }
        if (type == 0) {
            for (int b = 0; b < tempPaiZu.length - 2; b++) {
                int temp2 = tempPaiZu[b];
                int temp2Type = tempPaiZu[b] / 10;
                if (tempPaiZu[b + 1] == temp2 & tempPaiZu[b + 1] / 10 == temp2Type &
                        tempPaiZu[b + 2] == temp2 & tempPaiZu[b + 2] / 10 == temp2Type) {
                    //删除刻牌
                    tempPaiZu = delArr(tempPaiZu, b);
                    tempPaiZu = delArr(tempPaiZu, b);
                    tempPaiZu = delArr(tempPaiZu, b);
                    if (tempPaiZu.length == 0) {
                        //和牌成立
                        return new int[]{-1};
                    }
                    //删除刻牌成功
                    return tempPaiZu;
                }
            }
        } else if (type == 1) {
            for (int b = 0; b < tempPaiZu.length - 2; b++) {
                int temp2 = tempPaiZu[b];
                int temp2Type = tempPaiZu[b] / 10;
                for (int c = b + 1; c < tempPaiZu.length - 1; c++) {
                    int temp3 = tempPaiZu[c];
                    int temp3Type = tempPaiZu[c] / 10;
                    if (temp2 == temp3 - 1 & temp2Type != 0 & temp3Type != 0) {
                        for (int d = c + 1; d < tempPaiZu.length; d++) {
                            int temp4 = tempPaiZu[d];
                            int temp4Type = tempPaiZu[d] / 10;
                            if (temp3 == temp4 - 1 & temp2 == temp3 - 1 & temp4Type != 0
                                    ) {
                                //删除链牌
                                tempPaiZu = delArr(tempPaiZu, d);
                                tempPaiZu = delArr(tempPaiZu, c);
                                tempPaiZu = delArr(tempPaiZu, b);
                                //拆解完毕,没有剩余和牌成功
                                if (tempPaiZu.length == 0) {
                                    //和牌成立
                                    return new int[]{-1};
                                }
                                //删除刻牌成功
                                return tempPaiZu;
                            }
                        }
                    }
                }
            }
        }
        //删除失败
        return new int[]{0};
    }

    /*
    辅助方法
     */
    private static int[] xuanzePaiXu(int[] arr) {
        /**
         * 选择排序 <br>
         * 从第2-n个元素中找出最小的元素,与第1个比较交换,<br>
         * 从第3-n个元素中找出最小的元素,与第2个比较交换<br>
         * O(n*n)
         */
        int temp;
        int loc = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            temp = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < temp) {
                    // 从小到大
                    temp = arr[j];
                    loc = j;
                }
            }
            if (temp != arr[i]) {
                // temp是最值
                arr[loc] = arr[i];
                arr[i] = temp;
            } // 相等不需要交换
        }
        return arr;
    }

    private static boolean is4(int[] arr, int jiance) {
        /*
        检测的牌超过4张返回false
         */
        int count = 0;
        for (int i = 0; i < arr.length; i++) {
            if (jiance == arr[i]) {
                count++;
            }
        }
        if (count > 4) {
            return false;
        } else {
            return true;
        }
    }

    //定长数组的长度调整
    private static int[] delArr(int[] arr, int loc) {
        int[] temp = new int[arr.length - 1];
        System.arraycopy(arr, 0, temp, 0, loc);
        System.arraycopy(arr, loc + 1, temp, loc, arr.length - loc - 1);
        return temp;
    }

    private static int[] addArr(int[] arr, int add) {
        int[] temp = new int[arr.length + 1];
        System.arraycopy(arr, 0, temp, 0, arr.length);
        temp[temp.length - 1] = add;
        return temp;
    }

    //牌组编号互转:String转int
    private static int tr1(String a) {
        switch (a) {
            case "东风":
                return 1;
            case "西风":
                return 2;
            case "南风":
                return 3;
            case "北风":
                return 4;
            case "红中":
                return 5;
            case "白板":
                return 6;
            case "发财":
                return 7;
            case "一万":
                return 11;
            case "二万":
                return 12;
            case "三万":
                return 13;
            case "四万":
                return 14;
            case "五万":
                return 15;
            case "六万":
                return 16;
            case "七万":
                return 17;
            case "八万":
                return 18;
            case "九万":
                return 19;
            case "一筒":
                return 21;
            case "二筒":
                return 22;
            case "三筒":
                return 23;
            case "四筒":
                return 24;
            case "五筒":
                return 25;
            case "六筒":
                return 26;
            case "七筒":
                return 27;
            case "八筒":
                return 28;
            case "九筒":
                return 29;
            case "一索":
                return 31;
            case "二索":
                return 32;
            case "三索":
                return 33;
            case "四索":
                return 34;
            case "五索":
                return 35;
            case "六索":
                return 36;
            case "七索":
                return 37;
            case "八索":
                return 38;
            case "九索":
                return 39;
            default:
                return 0;
        }
    }

    //牌组编号互转:int转String
    private static String tr2(int a) {
        switch (a) {
            case 1:
                return "东风";
            case 2:
                return "西风";
            case 3:
                return "南风";
            case 4:
                return "北风";
            case 5:
                return "红中";
            case 6:
                return "白板";
            case 7:
                return "发财";
            case 11:
                return "一万";
            case 12:
                return "二万";
            case 13:
                return "三万";
            case 14:
                return "四万";
            case 15:
                return "五万";
            case 16:
                return "六万";
            case 17:
                return "七万";
            case 18:
                return "八万";
            case 19:
                return "九万";
            case 21:
                return "一筒";
            case 22:
                return "二筒";
            case 23:
                return "三筒";
            case 24:
                return "四筒";
            case 25:
                return "五筒";
            case 26:
                return "六筒";
            case 27:
                return "七筒";
            case 28:
                return "八筒";
            case 29:
                return "九筒";
            case 31:
                return "一索";
            case 32:
                return "二索";
            case 33:
                return "三索";
            case 34:
                return "四索";
            case 35:
                return "五索";
            case 36:
                return "六索";
            case 37:
                return "七索";
            case 38:
                return "八索";
            case 39:
                return "九索";
            default:
                return "0";
        }
    }
}

main方法调用示范
package com.zdg.mahjong;

public class Test {
    public static void main(String[] args) {
        Rule mj = new Rule();
        String result;
        boolean isWin;
        String pai;
        System.out.println("国士无双14测试1");
        pai = "一万,九万,一筒,九筒,一索,九索,东风,西风,南风,北风,红中,白板,发财,一万";
        isWin = mj.is_HuPai(pai);
        System.out.println("判断结果:" + isWin);
        System.out.println();

        System.out.println("10和8测试");
        pai = "三万,三万,三万,四万,五万,六万,七万,八万,八万,八万";
        result = mj.get_HuPai(pai);
        System.out.println("和牌有:" + result);
        System.out.println();
    }
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-10-27 09:26:16 | 显示全部楼层
,膜拜
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-22 05:01

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表