zxcv8556723 发表于 2017-10-2 15:06:47

麻将和牌解析算法Java实现

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

先上效果图:

算法思路:
首先对传入的牌编号数组进行排序(整牌),防止超过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));
            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));
            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;
      //防止牌组超过14张,或者传入空牌组
      if (strTemp.length > 14 | strTemp.length == 0) {
            return null;
      } else {
            //String转int,便于后续使用
            for (int i = 0; i < strTemp.length; i++) {
                //牌组文字编号转换
                intTemp = tr1(strTemp);
                if (intTemp == 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;
            int temp1Type = tempPaiZu / 10;
            if (tempPaiZu == temp1 & tempPaiZu / 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 == -1) {
                                          return true;
                                        } else if (result == 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 == tempPaiZu) {
                  if (tempPaiZu / 10 == 0 | tempPaiZu % 10 == 1 | tempPaiZu % 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 / 10 == 0 | tempPaiZu % 10 == 1 | tempPaiZu % 10 == 9)) {
                  isGuoShiWuShuang = false;
                  break;
                }
            }
      }
      if (isGuoShiWuShuang) {
            return true;
      }
      //国士无双匹配失败,还原牌组
      tempPaiZu = paiZu;
      /*
      七对子和牌牌型
         */
      boolean isQiDuiZi = false;
      if (tempPaiZu.length == 14) {
            int[] tempQiDuiZi = new int;
            for (int i = 0; i < tempQiDuiZi.length; i++) {
                //判断都为对牌,牌型不重复
                if ((tempPaiZu == tempPaiZu) & (tempPaiZu / 10 == tempPaiZu / 10)) {
                  tempQiDuiZi = tempPaiZu;
                  isQiDuiZi = true;
                } else {
                  isQiDuiZi = false;
                  break;
                }
            }
            if (isQiDuiZi) {
                for (int i = 1; i < 7; i++) {
                  //七个对子牌组没有重复
                  if (tempQiDuiZi == tempQiDuiZi) {
                        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 == -1) {
            return new int[]{-1};
      }
      if (tempPaiZu == 0) {
            return new int[]{0};
      }
      if (type == 0) {
            for (int b = 0; b < tempPaiZu.length - 2; b++) {
                int temp2 = tempPaiZu;
                int temp2Type = tempPaiZu / 10;
                if (tempPaiZu == temp2 & tempPaiZu / 10 == temp2Type &
                        tempPaiZu == temp2 & tempPaiZu / 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;
                int temp2Type = tempPaiZu / 10;
                for (int c = b + 1; c < tempPaiZu.length - 1; c++) {
                  int temp3 = tempPaiZu;
                  int temp3Type = tempPaiZu / 10;
                  if (temp2 == temp3 - 1 & temp2Type != 0 & temp3Type != 0) {
                        for (int d = c + 1; d < tempPaiZu.length; d++) {
                            int temp4 = tempPaiZu;
                            int temp4Type = tempPaiZu / 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;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr < temp) {
                  // 从小到大
                  temp = arr;
                  loc = j;
                }
            }
            if (temp != arr) {
                // temp是最值
                arr = arr;
                arr = 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) {
                count++;
            }
      }
      if (count > 4) {
            return false;
      } else {
            return true;
      }
    }

    //定长数组的长度调整
    private static int[] delArr(int[] arr, int loc) {
      int[] temp = new int;
      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;
      System.arraycopy(arr, 0, temp, 0, arr.length);
      temp = 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();
    }
}

蒜爆泥鳅 发表于 2022-10-27 09:26:16

{:10_266:},膜拜
页: [1]
查看完整版本: 麻将和牌解析算法Java实现