人工智能,其实它就是一件大衣(系列之二)

来源:互联网 发布:区块链 都有什么算法 编辑:程序博客网 时间:2024/04/20 01:51
俗话说:先搭台,后唱戏。游戏也是,必须先有游戏的整体逻辑以及懂规则的裁判,这样人类玩家或是AI玩家才能开始搏弈。所以我们先处理最重要的规则,判断一副牌是不是胡牌。这件事儿在大家看来其实非常简单,基本上会打麻将的在几秒钟之内就能判断一副牌是不是胡了。但是交给电脑来做就没那么容易了。
其实在数学上描述胡牌的特征非常容易,那就是当你的牌型到达如下模式的时候,你就可以胡了(七条对不算,那个情况非常特殊,单独考虑就可以了):
(AAA|ABC)*AA
三张一样的或是三连张是一副牌,两张一样的叫将,要想胡牌你需要一个将,然后剩下的必须都是整副牌。有人可能想问杠如何处理,这里我们只考虑手里还没有亮出来的牌,所有已经亮出来的牌都不用考虑,比如杠的,比如碰的,因为那些都不会影响你胡不胡。
再端详一下儿那个模式,眼熟不?那不就是一个文法么?所以这个问题就变成了披着人智外衣的编译……
至少以我的经验来看,我就是这样判断一副牌能不能胡的,我把它分成三张或三连张,然后找将,看看能不能分成上面的样子。然后这就是人类牛X的地方,人们从这种输入中寻找模式的本领非常强大,我可以在几秒内就找到最优的分划并判断这牌胡没胡。
但计算机没有这种“直觉”,计算机只能计算。所以编译的知识就派上用场了,问题变成了给定一副牌做输入,和一个对应三张,三连张,将等模式的文法,如何推导生成这副牌的文法,并判断其是否符合胡牌的文法。这个文法的麻烦之处在于它具有二义性,一副牌存在不止一个划分方法。
说到这儿,就不能不说我最喜欢的一句话了,这句话做为我解决问题的指导思想:有问题,用枚举。枚举几乎是计算机唯一优于人类的地方,所以我们在这里也用枚举法枚举出所有可能的语法树,如果发现有符合胡牌的语法树,则这副牌就可以胡。闲言碎语不要讲,直接上代码吧:
代表一副牌的类如下:
enum Patterns {     PT_YIFUPAI,     PT_SANZHANG,     PT_JIANG,};class PaiInfo {      Card getFirst();      // 拿到第一张牌,下一次模式匹配从这张牌开始      int getTotal();       // 返回剩余牌的总数量      bool hasPattern(Pattern, Card); // 判断是否有给定的模式      void removePattern(Pattern, Card); // 归约,提出给定模式      void restorePattern(Pattern, Card); // 回溯,将给定模式再放回去。};
判断胡牌的代码如下:
    static int hupai_pattern(PaiInfo &pai, int jiang)    {        Card card = pai.getFirst();        switch (pai.getTotal()) {        case 0:            return 1;        case 1:            return 0;        case 2:            if (jiang == 0 && pai.hasPattern(PT_JIANG, card))                return 1;            return 0;        case 3:            if (pai.hasPattern(PT_SANZHANG, card) ||                pai.hasPattern(PT_YIFUPAI, card))                return 1;            return 0;        default:            if (pai.hasPattern(PT_SANZHANG, card)) {                pai.removePattern(PT_SANZHANG, card);                int ret = hupai_pattern(pai, jiang);                pai.restorePattern(PT_SANZHANG, card);                if (ret) return ret;            }            if (pai.hasPattern(PT_YIFUPAI, card)) {                pai.removePattern(PT_YIFUPAI, card);                int ret = hupai_pattern(pai, jiang);                pai.restorePattern(PT_YIFUPAI, card);                if (ret) return ret;            }            if (jiang == 0 && pai.hasPattern(PT_JIANG, card)) {                pai.removePattern(PT_JIANG, card);                int ret = hupai_pattern(pai, jiang+1);                pai.restorePattern(PT_JIANG, card);                if (ret) return ret;            }            return 0;        }    }    int UserInfo::hupai(Card card)    {        int res = 0;        active_cards.restorePattern(PT_DANZHANG, card);        if (inactive_cards.empty()) {            if (active_cards.getProduct() == 128)                res = active_cards.getNumber(card) == 4 ? HU_LONGQIDUI :                    HU_QITIAODUI;        }        if (!res) {            bool possible = true;            for (int i = 0; i < 3; i++)                if (active_cards._total_number[i] % 3 == 1) possible = false;            if (possible) {                if (active_cards.daduizi()) res = HU_DADUIZI;                else if (hupai_pattern(active_cards, 0)) res = HU_PINGHU;            }        }        if (res && qingyise()) res += HU_QINGYISE;        active_cards.removePattern(PT_DANZHANG, card);        return res;    }
如上即是判断给定手里的牌之后,判定加入指定牌之后,整副牌可不可以胡的代码。其中应用到了编译中的移入-归约思想,以及回溯的方法。此函数可以输出胡牌的类型,比如平胡,大对子,七条对,以及是不是清一色。有了这个函数,那判断听牌的函数是不是也类似地写呢?是不是也像上面那样做移入-归约和回溯呢?其实没必要,因为听牌的模式稍微复杂一些,与其分析它的模式,不如利用现有的函数,代码如下:
    bool UserInfo::tingpai()    {        START_FOR_EACH_CARD(0)            if (hupai(position_to_card(r, c))) return true;        END_FOR_EACH_CARD;        return false;    }
其思想就是,所谓听牌,就是差一张就胡的牌,只要检查一下儿所有的27张牌能不能胡,就知道这副牌是不是听牌了。
所以人工智能不是凭空产生的,需要程序员把这一丁点儿的智能分析透彻,然后再将其转化成代码,其中会用到不那么智能的方法,比如枚举,回溯等。所以人工智能就是一件大衣,里面包着很多基本的东西,罗马不是一天建成的,人工智能也不会像悟空一样从石头里蹦出来。

原创粉丝点击