麻将算法(上)
来源:互联网 发布:腾讯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:手上2个5条,有人打出5条,可以拿2个5条加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;}
注:未完待续,麻将算法较为复杂的地方是听牌列表判断、能否胡牌判断、特别是有癞子牌的时候简直是噩梦
- 麻将算法(上)
- 麻将AI算法(下)
- 麻将算法(一)洗牌
- 麻将算法(四)吃牌
- node.js——麻将算法(六)简易版麻将出牌AI1.0
- node.js——麻将算法(七)简易版麻将出牌AI2.0
- 麻将听胡算法
- 麻将听牌算法
- 麻将查听算法
- 麻将胡牌算法
- 广安麻将的算法
- unity3d广东麻将算法
- 判断麻将和牌的算法(转载)
- 判断麻将和牌的算法(转载)
- 中国麻将(Chinese Mahjong, UVa 11210)【JAVA算法实现】
- 麻将算法(二)牌型转换以及接牌
- 麻将算法(三)碰牌以及杠牌
- 麻将算法(五)胡牌之M选N
- hdu2056 Rectangles(C语言)
- html js简单实现图片轮播功能
- css时间过渡
- Logge的简单使用r
- python爬虫----网易云音乐歌曲爬取并存入Excel
- 麻将算法(上)
- 前端基础个人总结五
- 杭电1005这个函数怎么办
- html标签
- Uva12545
- 管理的封闭原理
- 测试工程师基础知识
- RecyclerView条目动画,超简单,一行代码搞定
- spring boot thymeleaf的使用