麻将算法(上)

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

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 initAllPai() {
        List 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> faPai(List initAllPai) {
    	// 洗牌 打乱顺序
        Collections.shuffle(initAllPai);
    	List> paiLists = new ArrayList<>(); 
        for (int j = 0; j < 4; j++) {
        	List 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 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 initPais = initAllPai();
		
		List> paiLists = faPai(initPais);
		for (List 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 paiList, int tuopai) {
		List 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 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 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 list, Map> map , int tuoPai){
		int pai = 0;//要打出去的牌
		int total = 0;//花色总数
		int huaSe = 0;//记录最少的花色
		List indexList = new ArrayList();//碰杠牌的类型集合
		for(Map.Entry> 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 quePai(int[][] allPai) {

		int pai = 0;
		List tingTable = new ArrayList<>();
		for (int i = 1; i <= 3; i++) {
			List duizi = new ArrayList();
			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 shun = new HashMap();
			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 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;
	}


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


 
















你可能感兴趣的:(麻将算法(上))