有趣的选题
麻将不少人会打,可是用程序分析麻将和牌,甚至分析听牌就是一个比较难的问题了.
分析的规则如下:
胡牌分析: 给出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. 牌中有两个将,听其中一个将。因为有对称性,不应算两次,每次总是听第一副将。
- 牌中有将,听其它牌。
- 牌中有将,不听其它牌,即NO_TING状态。
- 牌中无将,听将(即有一个单张)。等价于状态1.
- 牌中无将,也不听将。
代码中使用了两个状态,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("
");
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
");
{
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
");
{
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
");
//{
// int t[9] = {
// 4, 4, 4,
// 2
// };
// hu(t, 0);
//}
printf("maxStack: %d", maxStackDepth);
return 0;
}