麻将算法(上)

来源:互联网 发布:腾讯mac软件下载中心 编辑:程序博客网 时间:2024/05/17 09:06

一、麻将规则(云南昭通麻将)

1.牌

1)         “万”“筒”和“条”三房牌,各36张,共108张牌;

2)        只能 “碰”“杠”“胡”,不能吃牌

3)        4人进行游戏;

4)        游戏开始时,庄家摸14张牌,闲家摸13张牌;

5)        有4个癞子牌(坨牌:既可以作为本身牌型可以替换为任意牌)

2.游戏术语

1)        坎:自己手中有三张相同的牌;

2)        碰牌:自己手中有两张,别人再打出一张,则可以用两张去碰,并将这三张亮出来;

3)        明杠:自己手中有一坎,别人在打出一张,则可以用三张去杠,称之为明杠,所有的杠都是从牌墙前面补牌的。

4)        暗杠:自己摸到了四张相同的牌,则可以暗杠;

5)        补杠:自己已经碰了的牌,自己又摸到了第4张,则可以补杠;

6)        根:自己的手牌和碰牌中有四张相同的但是没杠下来的,称之为根;

7)        叫:即听牌,用户当前的13张牌,再摸一张牌就可以和牌的牌型;

8)        墙子:摸完牌后,翻出一张牌,放在下家的堆牌区中。

9)        坨:摸完牌后,翻出牌墙第一张牌,定义为癞子牌。

10)     满飞满杀:坨拿去杠。

案例1:手上25条,有人打出5条,可以拿25条加1个坨去杠5

案列2:已碰了3万,手上有坨,可以直接拿坨去杠3

11)     飞碰:坨加一张单牌拿去碰的过程。

12)     亮牌:胡牌后亮出所有牌(牌桌上的玩家都可以看),剩下的继续血战。

 

3.胡牌规则

1)        胡牌条件:缺一门成牌型即可胡牌

缺一门:游戏开始后,必须打缺一门,中途可以换打缺哪门(不是定缺),胡牌局牌时必须只有2 门或1门(筒、万、条牌型)

胡牌的基本牌型:

                                   i.             11、123、123、123、123;

                                 ii.             11、123、123、123、111/1111(下同);

                                iii.             11、123、123、111、111;

                                iv.             11、123、111、111、111;

                                 v.             11、111、111、111、111;

                                vi.             11、22、33、44、55、66、77;

2)  牌型

牌型

说明

棒棒

正常普通的和牌,所谓的平胡

勾胡

手上有杠的牌型

天胡

庄家起手牌就和牌

地胡

庄家打出的第一手牌被闲家和

清一色

只有一种花色的牌型

杠上炮

玩家杠了之后摸起来那张牌点的炮

杠上花

玩家杠了以后自摸,无论暗杠、明杠、加杠都相当于自摸

抢杠

和别人补杠的杠牌(不是补杠后摸得那张)

大对子

11、111、111、111、111的牌型

巧七对

11、22、33、44、55、66、77的牌型

龙七对

11、22、33、44、55、6666的牌型

 

 

 

 二、麻将算法设计(二维数组形式)

1.类型定义(类型*10)

万   10 (万牌的张数)      

       11——19          一万-九万

筒   20 (筒牌的张数)

21——29    1筒 - 9筒

条   30 (条子牌的张数)

31——31   1条 - 9条  

风牌 40(目前无风牌)41——49  东南西北中发白   

2.牌下标定义

(1)万字牌的对应

4张1万 11  11 11  11

4张2万 12  12 12  12

4张3万 13  13 13  13

4张4万 14  14 14  14

4张5万 15  15 15  15

4张6万 16  16 16  16

4张7万 17  17 17  17

4张8万 18  18 18  18

4张9万 19  19 19  19

3.初始化牌和发牌

1)把所有牌放到牌堆

  /**     * 初始化麻将     *     * @return     */    public List<Integer> initAllPai() {        List<Integer> allPai = new LinkedList<>();        for (int i = 11; i < 40; i++) {            if (i % 10 == 0) {                continue;            }            for (int j = 0; j < 4; j++) {                allPai.add(i);            }        }        return allPai;    }

2)发牌,发四份牌

    /**     * 发牌     * @param initAllPai     */    public List<List<Integer>> faPai(List<Integer> initAllPai) {    // 洗牌 打乱顺序        Collections.shuffle(initAllPai);    List<List<Integer>> paiLists = new ArrayList<>();         for (int j = 0; j < 4; j++) {        List<Integer> pais = new ArrayList<>();        for (int i = 0; i < 13; i++) {        //从牌堆中移除一张牌        Integer remove = initAllPai.remove(0);        pais.add(remove);        }        paiLists.add(pais);        }        return paiLists;    }

3)把牌分成二维数组判断能否胡牌、杠牌、碰牌

        /** * 牌的类型 一万-九万 11 - 19 一筒-九筒 21 - 29 一索-九索 31-39 将牌转换为二维数组 * * 首位是牌型总数 *  * @param list * @return */public static int[][] changeToArr(List<Integer> list) {int[][] allPai = new int[4][10];for (int i = 0; i < list.size(); i++) {Integer pai = list.get(i);switch (pai / 10) {case 1:allPai[0][0] = allPai[0][0] + 1;allPai[0][pai % 10] = allPai[0][pai % 10] + 1;break;case 2:allPai[1][0] = allPai[1][0] + 1;allPai[1][pai % 10] = allPai[1][pai % 10] + 1;break;case 3:allPai[2][0] = allPai[2][0] + 1;allPai[2][pai % 10] = allPai[2][pai % 10] + 1;break;case 4:allPai[3][0] = allPai[3][0] + 1;allPai[3][pai % 10] = allPai[3][pai % 10] + 1;break;default:break;}}return allPai;}


4.测试效果

public static void main(String[] args) {List<Integer> initPais = initAllPai();List<List<Integer>> paiLists = faPai(initPais);for (List<Integer> pais : paiLists) {System.out.println("everyone pais :" + pais);}}
所有的牌:all pai:[[36,4,4,4,4,4,4,4,4,4],[36,4,4,4,4,4,4,4,4,4],[36,4,4,4,4,4,4,4,4,4],[0,0,0,0,0,0,0,0,0,0]]

结果

该玩家牌为 :[13, 21, 25, 16, 24, 12, 24, 38, 27, 36, 36, 15, 39]  缺牌类型为:0该玩家牌为 :[33, 36, 26, 34, 36, 39, 39, 38, 19, 21, 24, 35, 26]  缺牌类型为:0该玩家牌为 :[17, 26, 17, 22, 11, 29, 37, 18, 31, 23, 11, 13, 28]  缺牌类型为:2该玩家牌为 :[13, 33, 25, 11, 17, 31, 37, 37, 27, 15, 12, 11, 28]  缺牌类型为:1


三)麻将业务算法

1)判断是否缺一门(只有筒、万、条中的一种或两种牌型)

/** * 是否缺一门 * @param paiList 玩家手中所有牌 * @param tuopai 癞子牌 * @return */public static boolean isQueYiMen(List<Integer> paiList, int tuopai) {List<Integer> allPais = new ArrayList<>(paiList);allPais.remove(tuopai);int[][] allPai =  changeToArr(allPais);if (allPai[0][0] == 0) {//万牌为0张return true;}if (allPai[1][0] == 0) {//筒牌为0张return true;}if (allPai[2][0] == 0) {//条牌为0张return true;}return false;//三种牌都不缺}


2)缺的牌类型下标

        /** * 缺的类型下标 * @param allPai * @param tuopai * @return */public static int choseQueType(int[][] allPai, int tuopai) {if (allPai[0][0] == 0) {//万牌为0张,即可返回,不用执行下面代码,提高运行效率return 0;}if (allPai[1][0] == 0) {//筒牌为0张return 1;}if (allPai[2][0] == 0) {//条牌为0张return 2;}int yu = tuopai / 10 - 1;int mod = tuopai % 10;int num = allPai[yu][mod];//癞子牌的号码,癞子牌不算一种牌类型int index = 0;for (int count = 0; count < 2; count++) {//遍历移除癞子牌三种类型牌的数量int last = allPai[index][0];int next = allPai[count + 1][0];// 除去坨牌的数量if (index == yu) {last = last - num;}if (next == yu) {next = next - num;}if (next < last) {index = count + 1;}}return index;}

3)能否杠牌

/** * 判断能否杠牌 *  * @param paiList    所有手牌 * @param targetPai  目标牌 * @param gangType   杠类型 * @return */public static boolean canGang(List<Integer> paiList, int targetPai, int gangType) {int[][] allPai =  changeToArr(paiList);int idx = targetPai / 10;int pos = targetPai % 10;int yetPaiNum = allPai[idx - 1][pos];switch (gangType) {case 1: {// 暗杠 - 4张一样的手牌return yetPaiNum == 4;}case 2: {// 明杠  -3张一张的手牌return yetPaiNum == 3;}}return false;}

4)能否碰牌

/** * 判断能否碰牌 *  * @param allPai  所有手牌 * @param targetPai  目标牌 * @return */public static boolean canPeng(List<Integer> paiList, int targetPai) {int[][] allPai =  changeToArr(paiList);int idx = targetPai / 10;int pos = targetPai % 10;int yetPaiNum = allPai[idx - 1][pos];if (yetPaiNum >=2) {//手上该目标牌超过两张,可执行碰操作return true;}return false;}

5)选择打某张牌(基本)

/** * 根据玩家的碰杠牌选择打的牌  避免花猪 * @param list 手牌 * @param map  碰杠牌堆 * @param tuoPai  癞子牌 * @return */public static int chosePai(List<Integer> list, Map<Integer, List<Integer>> map , int tuoPai){int pai = 0;//要打出去的牌int total = 0;//花色总数int huaSe = 0;//记录最少的花色List<Integer> indexList = new ArrayList<Integer>();//碰杠牌的类型集合for(Map.Entry<Integer, List<Integer>> entry : map.entrySet()){int key = entry.getKey();if (indexList != null && !indexList.contains(key / 10 - 1)) {indexList.add(key / 10 - 1);}}int[][] allPai = changeToArr(list);allPai[tuoPai/10 - 1][0] -= allPai[tuoPai/10 - 1][tuoPai % 10];allPai[tuoPai/10 - 1][tuoPai % 10] = 0;//把癞子牌的数量置零for(int index = 0 ; index < 3 ; index ++){//遍历三种花色牌的情况,if (indexList.contains(index)) {continue;}if (total == 0) {total = allPai[index][0];huaSe = index;}if (total > allPai[index][0]) {total = allPai[index][0];huaSe = index;}}for(int i = 1; i < 10 ; i++){//选择最左的牌if (allPai[huaSe][i] > 0) {pai = (huaSe + 1) * 10 + i;break;}}return pai;}

6)对对胡

/** * 对对胡 * @param allPai 所有手牌 * @return */public static boolean duiduiHu(int[][] allPai) {// 对对胡boolean isDuizi = true;if (allPai[0][0] + allPai[1][0] + allPai[2][0] != 14) {//所有手牌一共14张,对对胡有7个对子return false;}//遍历所有手牌for (int i = 0; i < 3; i++) {for (int j = 1; j <= 9; j++) {if (allPai[i][j] > 0 && (allPai[i][j] != 2&&allPai[i][j] != 4)) {isDuizi = false;break;}}if (!isDuizi) {break;}}if (isDuizi) {return true;}return false;}

7)麻将听牌列表

/** * 麻将缺什么牌 型完整 *  * @param allPai * @return */public static List<Integer> quePai(int[][] allPai) {int pai = 0;List<Integer> tingTable = new ArrayList<>();for (int i = 1; i <= 3; i++) {List<Integer> duizi = new ArrayList<Integer>();for (int j = 1; j <= 9; j++) {//遍历每一张牌,统计每张牌组合牌型pai = 10 * i + j;int yu = pai / 10 - 1;int mod = pai % 10;// 是否有对子int size = allPai[yu][mod];//这种牌pai张数if (size == 0) {continue;}boolean hasShun = shunFilter(allPai, pai, tingTable);if (size == 2 && !hasShun) {//没有带顺序的牌 并且是一对的duizi.add(pai);}if (size == 2 && hasShun) {//有带顺序的牌 并且是一对的if (!tingTable.contains(pai)) {tingTable.add(pai);}}if (size == 2) {if (!tingTable.contains(pai)) {tingTable.add(pai);}}if (size == 3 && hasShun) {duizi.add(pai);}}if (duizi.size() > 1) {for (Integer data : duizi) {if (!tingTable.contains(data)) {tingTable.add(data);}}}}// 连续牌缺牌的整体判断for (int i = 1; i <= 3; i++) {Map<Integer, Integer> shun = new HashMap<Integer, Integer>();int max = 0;int start = 0;int yu = i - 1;for (int j = 1; j <= 9; j++) {int next = 1;start = j;for (int k = j; k <= 8; k++) {if (allPai[yu][k] > 0 && allPai[yu][k + 1] > 0) {next++;} else {break;}}if (next > 3) {shun.put(start, next);}if (next > max) {max = next;}}for (Map.Entry<Integer, Integer> entry : shun.entrySet()) {for (int k = 0; k < entry.getValue(); k++) {pai = 10 * i + entry.getKey() + k;if (!tingTable.contains(pai)) {tingTable.add(pai);}}if (entry.getKey() > 1) {pai = 10 * i + entry.getKey() - 1;if (!tingTable.contains(pai)) {tingTable.add(pai);}}int end = entry.getKey() + entry.getValue();if (end < 10) {pai = 10 * i + end;if (!tingTable.contains(pai)) {tingTable.add(pai);}}}shun.clear();}return tingTable;}


注:未完待续,麻将算法较为复杂的地方是听牌列表判断、能否胡牌判断、特别是有癞子牌的时候简直是噩梦


 
















原创粉丝点击