不务正业: 麻将胡牌分析

有趣的选题

麻将不少人会打,可是用程序分析麻将和牌,甚至分析听牌就是一个比较难的问题了.

分析的规则如下:

胡牌分析: 给出14-18张麻将,程序需判定是否和牌,并输出和牌的组合情况,最好能列出所有可能的和牌组合.
听牌分析: 给出13-17张麻将,程序需判定是否听牌,并输出听牌的组合情况,最好能列出所有可能的听牌组合.

麻将和牌组合可以分成两种,以国标麻将的叫法是顺子和刻子.顺子是指花色相同,序数递增一的三张牌, 如
123.刻子是指花色相同,序数也相同的三张牌,如111, 或字牌"东东东". 其中一副和牌,还需有且只有一个对子,
称为将牌.一副14张的和牌型, 需要分析出: "xxx, xxx, xxx, xxx, yy"的组合. 当四张相同的牌出现在和牌
中,可以认为是刻子的特殊情况,称为杠,也算一个组合.

听牌的情况是指只差一张牌就可以和牌.即和牌的组合中,任意拿走一张牌,即是一个听牌的组合.

为了验证面向数据的程序设计方法的有效性,终于选择了这个不是那么直观问题.

当然,也没有那么难,这个问题的难度大约是4小时的分析时间吧.

plus:
偶然拿起了这个程序,发现其实引

结论

麻将胡牌和听牌是一个组合问题,使用深搜+剪技的算法,在多重版本不同的基本上可以在(n/3)^2 时间以内

数据结构

麻将中有34个不同牌,用0-33的数字来表示,其中0-26是条筒万的花色,27-34是风牌和字牌。
一个组合,包括类型0-7,序数最小的牌,实际代码中区分了花色是为了方便显示。
一副牌的结构,包括5个顺子或刻子或将牌的组合。
将牌只能出现一次,所以特别使用了status区分有将和无将。考虑听的情况,将牌有五个状态。
0. 牌中有两个将,听其中一个将。因为有对称性,不应算两次,每次总是听第一副将。

  1. 牌中有将,听其它牌。
  2. 牌中有将,不听其它牌,即NO_TING状态。
  3. 牌中无将,听将(即有一个单张)。等价于状态1.
  4. 牌中无将,也不听将。
    代码中使用了两个状态,NO_JIANG表示牌中无将,NO_TING表示当前没有听牌。

状态

为了路径剪枝,对可能的组合,设置了前置条件。
XU_PAI,牌号小于27时,当前还在分析序牌,可以接受顺子。
HAS_B,HAS_C,当前是否可能有第二张或第三张顺子,序数7以下可以有HAS_B | HAS_C, 序数8只有HAS_B。
NO_TING,考虑听牌时,顺子ABC,可能有缺A,缺B,缺C三种情况,但只能出现一次,有听时,清除NO_TING。不再接受缺牌的顺子。

关于NO_JIANG和JIANG,原始考虑为有三个无将的状态(0,3,4),有将的状态有两个(1, 2).
接受1张将牌的情况,只在无将,也无听;接受2张将牌的情况,有无将,不听牌或有将,不听将。

代码

#include<iostream>

//一种花色的手牌



int result[5][3] = {0};
int rc = 0;
int r_status;
int stackDepth = 0, maxStackDepth = 0;



#define HU 0x0
#define XU_PAI 0x1
#define NO_TING 0x2
#define NO_JIANG 0x8
#define HAS_B 0x10
#define HAS_C 0x20

int status;

struct policy {
	int guard;
	int occupy; 	
	int sideEffect;
} policies[] = {
	{ 0,         4, 0}, //gang
	{ 0,         3, 0},  //ke
	{ NO_JIANG,  2,   NO_JIANG },  // jiang
	{ XU_PAI | HAS_B | HAS_C,   1, 0 },  // sun
	{ NO_TING | NO_JIANG,          2, NO_TING },  //ting jiang
	{ NO_TING | NO_JIANG,          1, NO_TING | NO_JIANG },  //ting diao
	{ NO_TING | XU_PAI | HAS_B, 1, NO_TING },  //ting sun
	{ NO_TING | XU_PAI | HAS_C, 1, NO_TING },  //ting kan
};

int flags(int f, int set, int clear) {
	f |= set;
	f &= ~(clear);
	return f;
}

bool isOccupyOrder(int pai, int kind, int occupy) {
	if (rc == 0) return false;
	int *r = result[rc - 1];
	return r[0] == pai && policies[r[1]].occupy < occupy && r[2] == kind;
}



void mj(int* t, int n) {
	stackDepth++;

    while (n < 34 && !t[n]) ++n;
    if (n >= 34) {
		r_status = status;
		if (maxStackDepth < stackDepth)
			maxStackDepth = stackDepth;
		for (int i = 0; i < rc; ++i) {
			int a = result[i][0], b = result[i][1], c = "ABCD"[result[i][2]];
			if (b == 3) {
				printf("{%c|%d-%d-%d}", c, a, a + 1, a + 2);
			}
			else if (b == 2) {
				printf("{j:%c|%d=%d}", c, a, a);
			}
			else if (b == 1) {
				printf("{%c|%d+%d+%d}", c, a, a, a);
			}
			else if (b == 0) {
				printf("{%c|%d+%d+%d+%d}", c, a, a, a, a);
			}
			else if (b == 4) {
				printf("{t:%c|%d=%d}", c, a, a);
			}
			else if (b == 5) {
				printf("{t:%c|%d=}", c, a);
			}
			else if (b == 6) {
				printf("{t:%c|%d-%d}", c, a, a + 1);
			}
			else if (b == 7) {
				printf("{t:%c|%d-%d}", c, a, a + 2);
			}
			
			else {
				printf("{##}");
			}
        }
        printf("\n");
		stackDepth--;
        return ;
    }

	int pai = n % 9 + 1;
	int kind = n / 9;
    int a = t[n], b = 0, c = 0;
	
	if (n < 27) {
		status = flags(status, XU_PAI, HAS_B | HAS_C);

		if (pai < 9 && t[n + 1]) {
			b = t[n + 1];
			status = flags(status, HAS_B, 0);
		}
		if (pai + 1 < 9 && t[n + 2]) {
			c = t[n + 2];	
			status = flags(status, HAS_C, 0);
		}
	} else {
		status = flags(status, 0, HAS_B | HAS_C | XU_PAI);
	}

	// 保存所有状态
	int save = status;

    // 分四种情况, 将, 碰, 杠(4张), 单独处理顺.
	for (int i = 0; i < sizeof(policies) / sizeof(struct policy); ++i ){
		struct policy* p = policies + i;

		if ((p->occupy > a) || ((p->guard & status) != p->guard)
			|| (p->occupy > 1 && isOccupyOrder(pai, kind, p->occupy)))
		{
			continue;
		}

		status = flags(status, 0, p->sideEffect);

		if (p->guard & HAS_B) {
			t[n + 1] -= 1; 
		}

		if (p->guard & HAS_C) {
			t[n + 2] -= 1;
		}	

		result[rc][0] = pai;
		result[rc][1] = i;
		result[rc][2] = kind;
		++rc;

		t[n] -= p->occupy;
		mj(t, n);

		// restore for next backtrace		
		--rc;
		status = save;

		t[n] = a;
		if (b) t[n + 1] = b;
		if (c) t[n + 2] = c;
	}

	stackDepth--;
}

void ting(int pai[][9]) {
	status = NO_TING | NO_JIANG | JIANG;	
	mj((int*)pai, 0);
}

void hu(int pai[][9]) {		
	status = NO_JIANG | JIANG;;
	mj((int*)pai, 0);
}

int main()
{
#if 1
	printf("test: nine-link\n");
	{		
		int t[4][9] = {
			{0},
			{3, 1, 1,
			1, 1, 1,
			1, 1, 3},
		};
		ting(t);

		printf("check hu");
		//status = NO_JIANG | XU_PAI;
		for (int i = 0; i < 9; ++i) {
			t[1][i] += 1;
			hu(t);
			t[1][i] -= 1;
		}
		//hasTing = false;
	}
#endif
	printf("test: 1 1 1 1 2 2 2 2 3 3 3 3 4 4\n");
	{
		int t[4][9] = {
			{0}, {0}, {0},
		    {4, 4, 4, 2}
		};
		hu(t);
	}

	//printf("test: 1 1 1 1 2 2 2 2 3 3 3 3 4 4\n");
	//{
	//	int t[9] = {
	//		4, 4, 4,
	//		2
	//	};
	//	hu(t, 0);
	//}

	printf("maxStack: %d", maxStackDepth);
    return 0;
}

posted on 2017-01-12 14:06  Anthony-黄亮  阅读(870)  评论(0编辑  收藏  举报

导航