您的位置:首页 > 大数据 > 人工智能

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

2013-09-15 12:48 176 查看
俗话说:先搭台,后唱戏。游戏也是,必须先有游戏的整体逻辑以及懂规则的裁判,这样人类玩家或是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张牌能不能胡,就知道这副牌是不是听牌了。

所以人工智能不是凭空产生的,需要程序员把这一丁点儿的智能分析透彻,然后再将其转化成代码,其中会用到不那么智能的方法,比如枚举,回溯等。所以人工智能就是一件大衣,里面包着很多基本的东西,罗马不是一天建成的,人工智能也不会像悟空一样从石头里蹦出来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  人工智能 游戏 数学