记录一下麻将的通用胡牌算法实现,只要满足M x ABC + N x DDD + EE 即可胡牌。
在这里先分析一下最简单的胡牌思路:先找出所有可能的将牌,若除去两张将牌之外的所有牌都能成扑,则可胡牌。
将牌就是公式里唯一的对子EE、扑的意思是一套牌顺子ABC或者刻子DDD。
将牌的查找:遍历每张手牌,若有两张以上相同牌就能作将,或者用一张癞子凑也可。
接下来就只要判断一副牌是否成扑,伪码如下:
- function isPu = (牌) { // 这里约定传入的牌是有序的、张数是3的倍数
- if (没牌) {
- return true;
- }
- if (若第一张是顺子中的一张) {
- if (isPu(去掉该顺子剩下的牌)) {
- return true;
- }
- }
- if (若第一张是刻子中的一张) {
- if (isPu(去掉该刻子剩下的牌)) {
- return true;
- }
- }
- return false;
- }
只要思路清晰了算法就很简单,为什么只考虑第一张牌?其实函数内部递归调用了每张牌都会计算到的,除非没牌了。下面给出详细代码:
- function isPu(cards, laizi) {
- if (cards.length == 0) {
- return true;
- }
- // 若第一张是顺子中的一张
- for (var first = cards[0] - 2; first <= cards[0]; first++) {
- if(first % 10 > 7 || (laizi == 0 && first < cards[0])) {
- // 剪枝:顺子第一张牌不会大于7点、无赖子情况下顺子第一张只能用手上的牌
- continue;
- }
- var shunCount = 0;
- for (var i = 0; i < 3; i++) {
- if (cards.indexOf(first + i) >= 0) {
- shunCount++;
- }
- }
- if (shunCount == 3 || shunCount + laizi >= 3) {
- // 找到包含第一张牌的顺子
- var puCards = cards.slice();
- var puLaizi = laizi;
- for (var i = 0; i < 3; i++) {
- var deletePos = puCards.indexOf(first + i);
- if (deletePos >= 0) {
- puCards.splice(deletePos, 1);
- }
- else {
- puLaizi--;
- }
- }
- if (isPu(puCards, puLaizi)) {
- // 剩下的牌成扑
- return true;
- }
- }
- }
- // 若第一张是刻子中的一张
- var keziCount = 1;
- var keziCard = cards[0];
- if (cards[1] == keziCard) {
- keziCount++;
- }
- if (cards[2] == keziCard) {
- keziCount++;
- }
- if (keziCount == 3 || keziCount + laizi >= 3) {
- var puCards = cards.slice();
- var puLaizi = laizi;
- for (var i = 0; i < 3; i++) {
- var deletePos = puCards.indexOf(keziCard);
- if (deletePos >= 0) {
- puCards.splice(deletePos, 1);
- }
- else {
- puLaizi--;
- }
- }
- if (isPu(puCards, puLaizi)) {
- return true;
- }
- }
- return false;
- }
下面是判断胡牌的函数:
- function canHuLaizi(cards, laizi) {
- if ((cards.length + laizi + 1) % 3 != 0) {
- // 若牌张数不是2、5、8、11、14则不能胡
- return false;
- }
- // 排序方便胡牌判断
- cards.sort(function(a, b) {
- return a - b;
- })
- // 依次删除一对牌做将,其余牌全部成扑则可胡
- for (var i = 0; i < cards.length; i++) {
- if (i > 0 && cards[i] == cards[i - 1]){
- // 和上一次是同样的牌,避免重复计算
- continue;
- }
- if ((i + 1 < cards.length && cards[i] == cards[i + 1]) || laizi > 0) {
- // 找到对子、或是用一张癞子拼出的对子
- var puCards = cards.slice();
- var puLaizi = laizi;
- puCards.splice(i, 1);
- if (puCards[i] == cards[i]) {
- puCards.splice(i, 1);
- }
- else {
- puLaizi--;
- }
- // 删去对子判断剩下的牌是否成扑
- if (isPu(puCards, puLaizi)) {
- return true;
- }
- }
- }
- if (laizi >= 2 && isPu(cards, laizi - 2)) {
- // 两个癞子做将牌
- return true;
- }
- return false;
- }
这里做一下两个输入参数的说明:
- // cards:手牌数组,不超过14张牌,每张牌由整数表示如下
- // 条:1, 2, 3, 4, 5, 6, 7, 8, 9,
- // 万:11, 12, 13, 14, 15, 16, 17, 18, 19,
- // 筒:21, 22, 23, 24, 25, 26, 27, 28, 29,
- // 东南西北中发白:31, 41, 51, 61, 71, 81, 91,
- //
- // laizi:癞子数量,用整数表示