zoukankan      html  css  js  c++  java
  • 不务正业: 麻将胡牌分析

    有趣的选题

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

    分析的规则如下:

    胡牌分析: 给出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("
    ");
    		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;
    }
    
    
  • 相关阅读:
    mysql导入导出数据过大命令
    thinkphp条件查询
    php表单提交安全方法
    ubuntu软件(查看文件差异)
    thinkphp if标签
    thinkphp导出报表
    jquery.easing.js下载地址
    水平手风琴切换效果插件亲自试过很好用
    li ie6/7 3px bug
    placeholder兼容IE6-9代码
  • 原文地址:https://www.cnblogs.com/ahuangliang/p/6188159.html
Copyright © 2011-2022 走看看