鱼C论坛

 找回密码
 立即注册
查看: 3789|回复: 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

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

  2. public class Rule {

  3.     public String get_HuPai(String paiZu) {
  4.         String result = null;
  5.         int[] intTemp = null;
  6.         for (int i = 1; i < 40; i++) {
  7.             if (!(i == 8 || i == 9 || i == 10 || i == 20 || i == 30)) {
  8.                 intTemp = zhengLi(paiZu + "," + tr2(i));
  9.                 if (isHuPai(intTemp)) {
  10.                     result = result + "," + tr2(i);
  11.                 }
  12.             }
  13.         }
  14.         //===================打印排序后的牌组===================
  15.         System.out.print("待和牌:");
  16.         for (int n = 0; n < intTemp.length - 1; n++) {
  17.             System.out.print(tr2(intTemp[n]));
  18.             if (n != intTemp.length - 2) {
  19.                 System.out.print(",");
  20.             }
  21.         }
  22.         System.out.println();
  23.         //===================打印排序后的牌组===================
  24.         if (result == null) {
  25.             //没有找到和牌
  26.             return "0";
  27.         } else {
  28.             //去除第一个逗号
  29.             return result.substring(5);
  30.         }
  31.     }

  32.     public boolean is_HuPai(String paiZu) {
  33.         int[] intTemp = zhengLi(paiZu);
  34.         //===================打印排序后的牌组===================
  35.         System.out.print("待测牌:");
  36.         for (int n = 0; n < intTemp.length; n++) {
  37.             System.out.print(tr2(intTemp[n]));
  38.             if (n != intTemp.length - 1) {
  39.                 System.out.print(",");
  40.             }
  41.         }
  42.         System.out.println();
  43.         //===================打印排序后的牌组===================
  44.         return isHuPai(intTemp);
  45.     }

  46.     private int[] zhengLi(String paiZu) {
  47.         String[] strTemp = paiZu.split(",");
  48.         int[] intTemp = new int[strTemp.length];
  49.         //防止牌组超过14张,或者传入空牌组
  50.         if (strTemp.length > 14 | strTemp.length == 0) {
  51.             return null;
  52.         } else {
  53.             //String转int,便于后续使用
  54.             for (int i = 0; i < strTemp.length; i++) {
  55.                 //牌组文字编号转换
  56.                 intTemp[i] = tr1(strTemp[i]);
  57.                 if (intTemp[i] == 0) {
  58.                     //牌组不在编号范围内
  59.                     return null;
  60.                 }
  61.             }
  62.             //对牌组进行排序,从小到大
  63.             intTemp = xuanzePaiXu(intTemp);
  64.             //同一种牌不超4张牌检测
  65.             for (int anIntTemp : intTemp) {
  66.                 if (is4(intTemp, anIntTemp)) {
  67.                 } else {
  68.                     return null;
  69.                 }
  70.             }
  71.             return intTemp;
  72.         }
  73.     }

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

  81.         大致过程:去除将牌;剩余牌组去除刻牌;剩余牌组去除连续牌
  82.          */
  83.         for (int a = 0; a < tempPaiZu.length - 1; a++) {
  84.             int temp1 = tempPaiZu[a];
  85.             int temp1Type = tempPaiZu[a] / 10;
  86.             if (tempPaiZu[a + 1] == temp1 & tempPaiZu[a + 1] / 10 == temp1Type) {
  87.                 //找到将牌,删除,继续删除刻牌
  88.                 tempPaiZu = delArr(tempPaiZu, a);
  89.                 tempPaiZu = delArr(tempPaiZu, a);
  90.                 if (tempPaiZu.length % 3 != 0) {
  91.                     //相公牌,永不和牌
  92.                     return false;
  93.                 } else {
  94.                     if (tempPaiZu.length == 0) {
  95.                         //和牌类型:一对牌,只有两张
  96.                         return true;
  97.                     } else {
  98.                         //去除刻牌和链牌,最多四组,四层for循环
  99.                         /*
  100.                         刻        刻        刻        刻
  101.                         刻        刻        刻        链
  102.                         刻        刻        链        刻
  103.                         刻        刻        链        链
  104.                         刻        链        刻        刻
  105.                         刻        链        刻        链
  106.                         刻        链        链        刻
  107.                         刻        链        链        链
  108.                         链        刻        刻        刻
  109.                         链        刻        刻        链
  110.                         链        刻        链        刻
  111.                         链        刻        链        链
  112.                         链        链        刻        刻
  113.                         链        链        刻        链
  114.                         链        链        链        刻
  115.                         链        链        链        链
  116.                          */
  117.                         int[] backPaiZu = tempPaiZu;//备份牌组
  118.                         int[] result;
  119.                         for (int n1=0;n1<2;n1++){
  120.                             for (int n2=0;n2<2;n2++){
  121.                                 for (int n3=0;n3<2;n3++){
  122.                                     for (int n4=0;n4<2;n4++){
  123.                                         result = fenjiePaiZu(fenjiePaiZu(fenjiePaiZu(fenjiePaiZu(tempPaiZu, n1), n2), n3), n4);
  124.                                         if (result[0] == -1) {
  125.                                             return true;
  126.                                         } else if (result[0] == 0) {
  127.                                             tempPaiZu= backPaiZu;
  128.                                         }else {
  129.                                             return false;
  130.                                         }
  131.                                     }
  132.                                 }
  133.                             }
  134.                         }
  135.                     }
  136.                     //还原将牌
  137.                     tempPaiZu = addArr(tempPaiZu,temp1);
  138.                     tempPaiZu = addArr(tempPaiZu,temp1);
  139.                     tempPaiZu=xuanzePaiXu(tempPaiZu);
  140.                 }
  141.             }
  142.         }
  143.         //3n+2匹配失败
  144.         tempPaiZu = paiZu;
  145.         /*
  146.         国士无双和牌牌型
  147.         国士无双牌组只有一个对牌,其余牌全部为除对牌以外的杂牌和三花边牌,且不能重复
  148.          */
  149.         //去除对牌的一张单牌
  150.         if (tempPaiZu.length == 14) {
  151.             for (int a = 1; a < 14; a++) {
  152.                 if (tempPaiZu[a] == tempPaiZu[a - 1]) {
  153.                     if (tempPaiZu[a] / 10 == 0 | tempPaiZu[a] % 10 == 1 | tempPaiZu[a] % 10 == 9) {
  154.                         tempPaiZu = delArr(tempPaiZu, a);
  155.                         break;
  156.                     }
  157.                 }
  158.             }
  159.         }
  160.         boolean isGuoShiWuShuang = false;
  161.         if (tempPaiZu.length == 13) {
  162.             isGuoShiWuShuang = true;
  163.             for (int a = 0; a < 13; a++) {
  164.                 //边牌杂牌检测
  165.                 if (!(tempPaiZu[a] / 10 == 0 | tempPaiZu[a] % 10 == 1 | tempPaiZu[a] % 10 == 9)) {
  166.                     isGuoShiWuShuang = false;
  167.                     break;
  168.                 }
  169.             }
  170.         }
  171.         if (isGuoShiWuShuang) {
  172.             return true;
  173.         }
  174.         //国士无双匹配失败,还原牌组
  175.         tempPaiZu = paiZu;
  176.         /*
  177.         七对子和牌牌型
  178.          */
  179.         boolean isQiDuiZi = false;
  180.         if (tempPaiZu.length == 14) {
  181.             int[] tempQiDuiZi = new int[7];
  182.             for (int i = 0; i < tempQiDuiZi.length; i++) {
  183.                 //判断都为对牌,牌型不重复
  184.                 if ((tempPaiZu[2 * i] == tempPaiZu[2 * i + 1]) & (tempPaiZu[2 * i] / 10 == tempPaiZu[2 * i + 1] / 10)) {
  185.                     tempQiDuiZi[i] = tempPaiZu[2 * i];
  186.                     isQiDuiZi = true;
  187.                 } else {
  188.                     isQiDuiZi = false;
  189.                     break;
  190.                 }
  191.             }
  192.             if (isQiDuiZi) {
  193.                 for (int i = 1; i < 7; i++) {
  194.                     //七个对子牌组没有重复
  195.                     if (tempQiDuiZi[i - 1] == tempQiDuiZi[i]) {
  196.                         isQiDuiZi = false;
  197.                         break;
  198.                     } else {
  199.                         isQiDuiZi = true;
  200.                     }
  201.                 }
  202.             }
  203.             if (isQiDuiZi) {
  204.                 return true;
  205.             }
  206.         }
  207.         /*
  208.         //七对子匹配失败,还原数组
  209.         tempPaiZu = paiZu;
  210.         */
  211.         //所有可能都没有匹配
  212.         return false;
  213.     }

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

  218.         fenjiePaiZu==>返回tempPaiZu,表示删除成功
  219.         fenjiePaiZu==>返回{-1},表示和牌成功
  220.         fenjiePaiZu==>返回{0},表示分解失败
  221.         */
  222.         if (tempPaiZu[0] == -1) {
  223.             return new int[]{-1};
  224.         }
  225.         if (tempPaiZu[0] == 0) {
  226.             return new int[]{0};
  227.         }
  228.         if (type == 0) {
  229.             for (int b = 0; b < tempPaiZu.length - 2; b++) {
  230.                 int temp2 = tempPaiZu[b];
  231.                 int temp2Type = tempPaiZu[b] / 10;
  232.                 if (tempPaiZu[b + 1] == temp2 & tempPaiZu[b + 1] / 10 == temp2Type &
  233.                         tempPaiZu[b + 2] == temp2 & tempPaiZu[b + 2] / 10 == temp2Type) {
  234.                     //删除刻牌
  235.                     tempPaiZu = delArr(tempPaiZu, b);
  236.                     tempPaiZu = delArr(tempPaiZu, b);
  237.                     tempPaiZu = delArr(tempPaiZu, b);
  238.                     if (tempPaiZu.length == 0) {
  239.                         //和牌成立
  240.                         return new int[]{-1};
  241.                     }
  242.                     //删除刻牌成功
  243.                     return tempPaiZu;
  244.                 }
  245.             }
  246.         } else if (type == 1) {
  247.             for (int b = 0; b < tempPaiZu.length - 2; b++) {
  248.                 int temp2 = tempPaiZu[b];
  249.                 int temp2Type = tempPaiZu[b] / 10;
  250.                 for (int c = b + 1; c < tempPaiZu.length - 1; c++) {
  251.                     int temp3 = tempPaiZu[c];
  252.                     int temp3Type = tempPaiZu[c] / 10;
  253.                     if (temp2 == temp3 - 1 & temp2Type != 0 & temp3Type != 0) {
  254.                         for (int d = c + 1; d < tempPaiZu.length; d++) {
  255.                             int temp4 = tempPaiZu[d];
  256.                             int temp4Type = tempPaiZu[d] / 10;
  257.                             if (temp3 == temp4 - 1 & temp2 == temp3 - 1 & temp4Type != 0
  258.                                     ) {
  259.                                 //删除链牌
  260.                                 tempPaiZu = delArr(tempPaiZu, d);
  261.                                 tempPaiZu = delArr(tempPaiZu, c);
  262.                                 tempPaiZu = delArr(tempPaiZu, b);
  263.                                 //拆解完毕,没有剩余和牌成功
  264.                                 if (tempPaiZu.length == 0) {
  265.                                     //和牌成立
  266.                                     return new int[]{-1};
  267.                                 }
  268.                                 //删除刻牌成功
  269.                                 return tempPaiZu;
  270.                             }
  271.                         }
  272.                     }
  273.                 }
  274.             }
  275.         }
  276.         //删除失败
  277.         return new int[]{0};
  278.     }

  279.     /*
  280.     辅助方法
  281.      */
  282.     private static int[] xuanzePaiXu(int[] arr) {
  283.         /**
  284.          * 选择排序 <br>
  285.          * 从第2-n个元素中找出最小的元素,与第1个比较交换,<br>
  286.          * 从第3-n个元素中找出最小的元素,与第2个比较交换<br>
  287.          * O(n*n)
  288.          */
  289.         int temp;
  290.         int loc = 0;
  291.         for (int i = 0; i < arr.length - 1; i++) {
  292.             temp = arr[i];
  293.             for (int j = i + 1; j < arr.length; j++) {
  294.                 if (arr[j] < temp) {
  295.                     // 从小到大
  296.                     temp = arr[j];
  297.                     loc = j;
  298.                 }
  299.             }
  300.             if (temp != arr[i]) {
  301.                 // temp是最值
  302.                 arr[loc] = arr[i];
  303.                 arr[i] = temp;
  304.             } // 相等不需要交换
  305.         }
  306.         return arr;
  307.     }

  308.     private static boolean is4(int[] arr, int jiance) {
  309.         /*
  310.         检测的牌超过4张返回false
  311.          */
  312.         int count = 0;
  313.         for (int i = 0; i < arr.length; i++) {
  314.             if (jiance == arr[i]) {
  315.                 count++;
  316.             }
  317.         }
  318.         if (count > 4) {
  319.             return false;
  320.         } else {
  321.             return true;
  322.         }
  323.     }

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

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

  337.     //牌组编号互转:String转int
  338.     private static int tr1(String a) {
  339.         switch (a) {
  340.             case "东风":
  341.                 return 1;
  342.             case "西风":
  343.                 return 2;
  344.             case "南风":
  345.                 return 3;
  346.             case "北风":
  347.                 return 4;
  348.             case "红中":
  349.                 return 5;
  350.             case "白板":
  351.                 return 6;
  352.             case "发财":
  353.                 return 7;
  354.             case "一万":
  355.                 return 11;
  356.             case "二万":
  357.                 return 12;
  358.             case "三万":
  359.                 return 13;
  360.             case "四万":
  361.                 return 14;
  362.             case "五万":
  363.                 return 15;
  364.             case "六万":
  365.                 return 16;
  366.             case "七万":
  367.                 return 17;
  368.             case "八万":
  369.                 return 18;
  370.             case "九万":
  371.                 return 19;
  372.             case "一筒":
  373.                 return 21;
  374.             case "二筒":
  375.                 return 22;
  376.             case "三筒":
  377.                 return 23;
  378.             case "四筒":
  379.                 return 24;
  380.             case "五筒":
  381.                 return 25;
  382.             case "六筒":
  383.                 return 26;
  384.             case "七筒":
  385.                 return 27;
  386.             case "八筒":
  387.                 return 28;
  388.             case "九筒":
  389.                 return 29;
  390.             case "一索":
  391.                 return 31;
  392.             case "二索":
  393.                 return 32;
  394.             case "三索":
  395.                 return 33;
  396.             case "四索":
  397.                 return 34;
  398.             case "五索":
  399.                 return 35;
  400.             case "六索":
  401.                 return 36;
  402.             case "七索":
  403.                 return 37;
  404.             case "八索":
  405.                 return 38;
  406.             case "九索":
  407.                 return 39;
  408.             default:
  409.                 return 0;
  410.         }
  411.     }

  412.     //牌组编号互转:int转String
  413.     private static String tr2(int a) {
  414.         switch (a) {
  415.             case 1:
  416.                 return "东风";
  417.             case 2:
  418.                 return "西风";
  419.             case 3:
  420.                 return "南风";
  421.             case 4:
  422.                 return "北风";
  423.             case 5:
  424.                 return "红中";
  425.             case 6:
  426.                 return "白板";
  427.             case 7:
  428.                 return "发财";
  429.             case 11:
  430.                 return "一万";
  431.             case 12:
  432.                 return "二万";
  433.             case 13:
  434.                 return "三万";
  435.             case 14:
  436.                 return "四万";
  437.             case 15:
  438.                 return "五万";
  439.             case 16:
  440.                 return "六万";
  441.             case 17:
  442.                 return "七万";
  443.             case 18:
  444.                 return "八万";
  445.             case 19:
  446.                 return "九万";
  447.             case 21:
  448.                 return "一筒";
  449.             case 22:
  450.                 return "二筒";
  451.             case 23:
  452.                 return "三筒";
  453.             case 24:
  454.                 return "四筒";
  455.             case 25:
  456.                 return "五筒";
  457.             case 26:
  458.                 return "六筒";
  459.             case 27:
  460.                 return "七筒";
  461.             case 28:
  462.                 return "八筒";
  463.             case 29:
  464.                 return "九筒";
  465.             case 31:
  466.                 return "一索";
  467.             case 32:
  468.                 return "二索";
  469.             case 33:
  470.                 return "三索";
  471.             case 34:
  472.                 return "四索";
  473.             case 35:
  474.                 return "五索";
  475.             case 36:
  476.                 return "六索";
  477.             case 37:
  478.                 return "七索";
  479.             case 38:
  480.                 return "八索";
  481.             case 39:
  482.                 return "九索";
  483.             default:
  484.                 return "0";
  485.         }
  486.     }
  487. }
复制代码


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

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

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

使用道具 举报

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-4-20 08:40

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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