麻将胡牌算法

前言

麻将胡牌算法。
全国各地有很多种麻将玩法,胡牌也有很多种,但是对于胡牌而言,但是就手牌的组合规则而言,大体上满足几个大类。

  • 七对:
    14张手牌,共有7个对子。
  • 十三幺:
    万筒条的一九牌+东南西北中发白,再加上一张上述牌。
  • 3N+2:
    N个顺子(ABC)或者刻子(AAA),外加一对将。

七对和十三幺是比较容易判断的牌型,而3N+2相对不容易判断,本章主要讲3N+2牌型如何判定。

分析

名词解释
顺子: ABC
刻子: AAA

万条筒可以构建顺子,东南西北中发白只能构成刻子(AAA),不能构成顺子(ABC)。
因此如下进行牌值规划时,
11~19对应一万到九万
21~29对应幺鸡到九条
31~39对应一筒到九筒
41,43,45,47对应东南西北
51,53,55对应中发白
东南西北中发白设定为不连续的牌值是为了避免算法识别为顺子。

如何判断胡牌3N+2
(1) 从手牌中首先假设一对将牌,并取出,那么剩余的牌需要满足3N
(2) 判断剩余的3N牌,下面的问题是如何判断3N了,若干的刻子和顺子的组合。

由于将牌的可能性有多种,所以需要假设多次将牌,进行多轮3N牌的判定。

3N的判定
为了方便,首先对牌进行排序(从小到大)。
判定是刻子还是顺子优先?
假设选择顺子,那么从刻子里抽取一张后,AAABCD 这种本来符合3N的牌,变成了AAD和ABC这种的组合,显然判断错误,正确的拆法应当是AAA和BCD。
因此应当是刻子优先,那么AAABCD对应刻子优先的拆法是AAA和BCD。
判断流程
(1) 3N 排序后,抽取前三张牌,三张牌相等,则为刻子,此时将这三张牌移除。如果3张牌不相等,则抽取第一张牌(假设牌值为a),并且试图构建顺子,从手牌寻找牌值为a+1和a+2的牌,如果能找到,则构建顺子,并移除这三张牌,继续下一个循环的的判断(再次进入3N牌的判断逻辑)。否则返回失败,手牌不满足3N。

代码实现

(1) KV实体

package com.mahjong.util;

import java.io.Serializable;

/**
 * KV实体,用于键值对
 * 
 * @author leng
 *
 * @param <K>
 * @param <V>
 */
public class KVEntity<K, V> implements Serializable {
	private static final long serialVersionUID = 1L;
	protected K k;
	protected V v;

	public KVEntity() {
		super();
	}

	public KVEntity(K k, V v) {
		super();
		this.k = k;
		this.v = v;
	}

	public static <K, V> KVEntity<K, V> build(K k, V v) {
		return new KVEntity<>(k, v);
	}

	public K getK() {
		return k;
	}

	public void setK(K k) {
		this.k = k;
	}

	public V getV() {
		return v;
	}

	public void setV(V v) {
		this.v = v;
	}

}

(2) map统计

package com.mahjong.util;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * map统计工具
 * 
 * @author leng
 *
 */
public class MapCountUtil {

	/**
	 * 统计集合coll,每个元素出现的次数,返回无序HashMap
	 * 
	 * @param coll
	 * @return
	 */
	public static <T> HashMap<T, Integer> count(Collection<T> collection) {
		HashMap<T, Integer> map = new HashMap<T, Integer>();
		countCollection(collection, map);
		return map;
	}

	

	/**
	 * 统计集合coll,每个元素出现的次数,返回有序Map
	 * 
	 * @param coll
	 * @return
	 */
	private static <T> void countCollection(Collection<T> collection, Map<T, Integer> map) {
		if (null == collection) {
			return;
		}
		collection.forEach((t) -> {
			Integer count = map.get(t);
			if (null == count) {
				count = 0;
			}
			map.put(t, count + 1);
		});
	}	
}

(3) 集合辅助类

package com.mahjong.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


/**
 * 集合工具
 *
 * @author leng
 */
public class CollectionUtil {

    /**
     * 是否为空集合
     * @param collection
     * @return
     */
    public static boolean isEmpty(Collection<?> collection) {
        return null == collection || collection.size() == 0;
    }

    /**
     * 是否不为空集合
     * @param collection
     * @return
     */
    public static boolean isNotEmpty(Collection<?>  collection) {
        return null != collection && collection.size() > 0;
    }


    /**
     * 从集合中获取指定下标的对象,没有该下标的对象或者对应该下标对象为null,则返回null <br>
     * 注意:该方法的目的主要是为了避免下标越界
     *
     * @param list  集合
     * @param index 下标
     * @return
     */
    public static <T> T get(List<T> list, int index) {
        if (isEmpty(list)) {
            return null;
        }

        if (index <= -1 || index >= list.size()) {
            return null;
        }

        return list.get(index);
    }

    /**
     * 从集合中获取指定下标组的对象,如果下标不合法,则直接丢弃,如果下标对应的对象等于null,则直接丢弃 <br>
     * 注意:该方法的目的主要是为了避免下标越界
     *
     * @param list  集合
     * @param index 下标组
     * @return
     */
    public static <T> List<T> get(List<T> list, int... index) {
        if (isEmpty(list)) {
            return null;
        }
        List<T> newList = new ArrayList<>();

        for (int i : index) {
            if (i > -1 && i < list.size() && null != list.get(i)) {
                newList.add(list.get(i));
            }
        }

        return newList;
    }

    /**
     * 从集合中按顺序抽取n个元素,如果数量不足,或者集合为null,则返回null,如果满足抽取条件,则抽取n元素,并且removeFlag=
     * true时候 ,从集合coll中将移除这n个被抽取的元素
     *
     * @param coll       被抽取的集合
     * @param n          需要抽取的元素的数量
     * @param removeFlag 是否从被抽取集合中删除这n个元素
     * @return
     */
    public static <T> List<T> extract(Collection<T> coll, int n, boolean removeFlag) {
        if (isNotEmpty(coll) && coll.size() >= n && n > 0) {
            List<T> list = new ArrayList<>(n);
            for (T t : coll) {
                list.add(t);
                if (list.size() == n) {
                    break;
                }
            }

            /**
             * 这里不能使用coll.removeAll(list),因为可能会删除超过n个元素
             */
            if (list.size() == n) {
                for (T t : list) {
                    coll.remove(t);
                }
                return list;
            }
        }
        return null;
    }


    /**
     * 从指定集合中,删除beRemoveObject元素n次,如果不存在n个这样的元素,则不删除
     *
     * @param coll           待删除的集合
     * @param beRemoveObject 被删除的元素
     * @param n              要删除多少次
     * @return
     */
    public static <T> boolean removeObjects(Collection<T> coll, T beRemoveObject, int n) {
        if (isNotEmpty(coll) && coll.size() >= n && n > 0) {
            List<T> list = new ArrayList<>(n);
            for (T t : coll) {
                if (beRemoveObject == t || beRemoveObject.equals(t)) {
                    list.add(t);
                    if (list.size() == n) {
                        break;
                    }
                }
            }

            /**
             * 这里不能使用coll.removeAll(list),因为可能会删除超过n个元素
             */
            if (list.size() == n) {
                for (T t : list) {
                    coll.remove(t);
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 从指定集合coll中,删除beRemoveObject,如果被删除对象组在coll中部分存在 ,则只删除存在的部分
     *
     * @param coll           集合
     * @param beRemoveObject 被删除的元素组
     * @return
     */
    @SafeVarargs
    public static <T> boolean removeObjects(Collection<T> coll, T... beRemoveObject) {
        if (isNotEmpty(coll) && null != beRemoveObject && beRemoveObject.length > 0) {
            for (T t : beRemoveObject) {
                coll.remove(t);
                if (coll.size() == 0) {
                    return true;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 从指定集合coll中,删除childcoll集合,如果childcoll集合在coll中部分存在,则不删除
     *
     * @param coll      集合
     * @param childcoll 被移除的子集合
     * @return
     */
    public static <T> boolean removeObjects(Collection<T> coll, Collection<T> childcoll) {
        if (isNotEmpty(coll) && isNotEmpty(childcoll)) {
            /**
             * 先复制一份,测试一下移除,如果移除之后,集合减小的长度等于被移除集合的长度,表示childcoll集合在coll集合中全部存在
             */
            List<T> list = new ArrayList<>(coll);
            for (T t : childcoll) {
                list.remove(t);
            }

            if (childcoll.size() == (coll.size() - list.size())) {
                for (T t : childcoll) {
                    coll.remove(t);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 集合coll是否全部存在elements元素
     *
     * @param coll
     * @param elements
     * @return
     */
    public static boolean existAll(Collection<Integer> coll, Integer... elements) {
        List<Integer> list = new ArrayList<>(coll);
        for (Integer element : elements) {
            if (list.contains(element)) {
                list.remove(element);
            } else {
                return false;
            }
        }
        System.out.println(coll);
        return true;
    }

    /**
     * 集合的最后一个元素
     *
     * @param list
     * @return
     */
    public static <T> T lastObject(List<T> list) {
        if (null == list || list.size() == 0) {
            return null;
        }
        int size = list.size();
        return list.get(size - 1);
    }

    /**
     * 集合的最后n个元素,如果list中元素不在足够n个,则返回全部的list
     *
     * @param list
     * @return
     */
    public static <T> List<T> lastObjects(List<T> list, int n) {
        if (null == list || list.size() == 0) {
            return null;
        }
        int size = list.size();

        if (n >= size) {
            return list;
        }
        return list.subList(size - n, size);
    }

}

(4)胡牌检测类

package com.mahjong.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * 胡牌检测
 * 
 * @author leng
 *
 */
public class HuCheckUtil {

	/**
	 * 万,条,筒的起点和终点
	 */
	public static final List<KVEntity<Integer, Integer>> WAN_TIAO_TONG = Arrays.asList(KVEntity.build(11, 19),
			KVEntity.build(21, 29), KVEntity.build(31, 39));

	/**
	 * 万,条,筒,东南西北,中发白的起点和终点
	 */
	public static final List<KVEntity<Integer, Integer>> WAN_TIAO_TONG_ZI = Arrays.asList(KVEntity.build(11, 19),
			KVEntity.build(21, 29), KVEntity.build(31, 39), KVEntity.build(41, 47), KVEntity.build(51, 55));

	/**
	 * 万,条,筒,东南西北的起点和终点
	 */
	public static final List<KVEntity<Integer, Integer>> WAN_TIAO_TONG_FENG = Arrays.asList(KVEntity.build(11, 19),
			KVEntity.build(21, 29), KVEntity.build(31, 39), KVEntity.build(41, 47));

	
	/**
	 * 是否满足3n+2胡牌模式,平胡模式<br>
	 * 
	 * @param cards
	 *            已经有的牌
	 * @param cardId
	 *            摸到的牌
	 * @return
	 */
	public static boolean is3N_2(List<Integer> cards, Integer cardId) {
		return is3N_2(cards, cardId, true, true);
	}

	/**
	 * 是否满足3n+2胡牌模式,平胡模式<br>
	 * 
	 * @param cards
	 *            已经有的牌
	 * @param cardId
	 *            摸到的牌
	 * @param checkKezi
	 *            是否检查刻子
	 * @param checkShunzi
	 *            是否检查顺子
	 * @return
	 */
	public static boolean is3N_2(List<Integer> cards, Integer cardId, boolean checkKezi, boolean checkShunzi) {
		List<Integer> pais = new ArrayList<Integer>(cards);
		if (null != cardId) {
			pais.add(cardId);
		}
		return is3N_2(pais, checkKezi, checkShunzi);
	}

	

	/**
	 * 是否满足3n+2胡牌模式,平胡模式<br>
	 * 
	 * @param cards
	 *            已经有的牌
	 * @return
	 */
	public static boolean is3N_2(List<Integer> cards, boolean checkKezi, boolean checkShunzi) {
		List<Integer> pais = new ArrayList<Integer>(cards);
		Collections.sort(pais);

		HashMap<Integer,Integer> paisCount=MapCountUtil.count(pais);

		for (Integer pai : paisCount.keySet()) {
			// 先找到将牌
			if (paisCount.get(pai) >= 2) {
				List<Integer> paiTemp = new ArrayList<Integer>(pais);
				CollectionUtil.removeObjects(paiTemp, pai, 2);

				// 前置检查,如果一色牌之和不能被3整除,则不可能是3n的牌,就不用继续is3N的检测,否则就需要进行下一步的检测
				boolean check3n = true;
				for (KVEntity<Integer, Integer> kv : WAN_TIAO_TONG) {

					long count = paiTemp.parallelStream().filter(value -> (value >= kv.getK() && value <= kv.getV()))
							.count();// 一色牌求张数
					if (count % 3 != 0) {
						check3n = false;
						break;
					}

					long sum = paiTemp.parallelStream().filter(value -> (value >= kv.getK() && value <= kv.getV()))
							.mapToLong(Integer::longValue).sum();// 一色牌求和
					if (sum % 3 != 0) {
						check3n = false;
						break;
					}
				}

				if (check3n && is3N(paiTemp, checkKezi, checkShunzi)) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * 是否是3n牌
	 * 
	 * @param cards
	 *            待检测的牌
	 * @return
	 */
	private static boolean is3N(List<Integer> cards, boolean checkKezi, boolean checkShunzi) {
		if (cards.size() == 0) {
			return true;
		}

		HashMap<Integer,Integer> paisCount=MapCountUtil.count(cards);
		
		int pai = cards.get(0);
		if (checkKezi && paisCount.get(pai) >= 3) {
			// 刻子
			CollectionUtil.removeObjects(cards, pai, 3);
			return is3N(cards, checkKezi, checkShunzi);
		} else if (checkShunzi && cards.contains(pai + 1) && cards.contains(pai + 2)) {
			// 顺子
			CollectionUtil.removeObjects(cards, pai, pai + 1, pai + 2);
			return is3N(cards, checkKezi, checkShunzi);
		}
		return false;
	}
}

(5) 测试胡牌验证

List<Integer> list = Arrays.asList(11, 12, 13, 14, 15, 16, 17);
		System.out.println(list + ",胡牌:" + HuCheckUtil.is3N_2(list, 14));

		List<Integer> list2 = Arrays.asList(11, 12, 12, 12, 12, 13, 21, 21, 24, 25, 26);
		boolean flag = HuCheckUtil.is3N_2(list2, null);
		System.out.println(list2 + ",胡牌:" + flag);
		
		List<Integer> list3 = Arrays.asList(11, 12, 12, 12, 12, 13, 21, 21, 24, 28, 26);
		boolean flag3 = HuCheckUtil.is3N_2(list3, null);
		System.out.println(list3 + ",胡牌:" + flag3);

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值