人工智能,其实它就是一件大衣(系列之二)
2013-09-15 12:48
260 查看
俗话说:先搭台,后唱戏。游戏也是,必须先有游戏的整体逻辑以及懂规则的裁判,这样人类玩家或是AI玩家才能开始搏弈。所以我们先处理最重要的规则,判断一副牌是不是胡牌。这件事儿在大家看来其实非常简单,基本上会打麻将的在几秒钟之内就能判断一副牌是不是胡了。但是交给电脑来做就没那么容易了。
其实在数学上描述胡牌的特征非常容易,那就是当你的牌型到达如下模式的时候,你就可以胡了(七条对不算,那个情况非常特殊,单独考虑就可以了):
(AAA|ABC)*AA
三张一样的或是三连张是一副牌,两张一样的叫将,要想胡牌你需要一个将,然后剩下的必须都是整副牌。有人可能想问杠如何处理,这里我们只考虑手里还没有亮出来的牌,所有已经亮出来的牌都不用考虑,比如杠的,比如碰的,因为那些都不会影响你胡不胡。
再端详一下儿那个模式,眼熟不?那不就是一个文法么?所以这个问题就变成了披着人智外衣的编译……
至少以我的经验来看,我就是这样判断一副牌能不能胡的,我把它分成三张或三连张,然后找将,看看能不能分成上面的样子。然后这就是人类牛X的地方,人们从这种输入中寻找模式的本领非常强大,我可以在几秒内就找到最优的分划并判断这牌胡没胡。
但计算机没有这种“直觉”,计算机只能计算。所以编译的知识就派上用场了,问题变成了给定一副牌做输入,和一个对应三张,三连张,将等模式的文法,如何推导生成这副牌的文法,并判断其是否符合胡牌的文法。这个文法的麻烦之处在于它具有二义性,一副牌存在不止一个划分方法。
说到这儿,就不能不说我最喜欢的一句话了,这句话做为我解决问题的指导思想:有问题,用枚举。枚举几乎是计算机唯一优于人类的地方,所以我们在这里也用枚举法枚举出所有可能的语法树,如果发现有符合胡牌的语法树,则这副牌就可以胡。闲言碎语不要讲,直接上代码吧:
代表一副牌的类如下:
判断胡牌的代码如下:
如上即是判断给定手里的牌之后,判定加入指定牌之后,整副牌可不可以胡的代码。其中应用到了编译中的移入-归约思想,以及回溯的方法。此函数可以输出胡牌的类型,比如平胡,大对子,七条对,以及是不是清一色。有了这个函数,那判断听牌的函数是不是也类似地写呢?是不是也像上面那样做移入-归约和回溯呢?其实没必要,因为听牌的模式稍微复杂一些,与其分析它的模式,不如利用现有的函数,代码如下:
其思想就是,所谓听牌,就是差一张就胡的牌,只要检查一下儿所有的27张牌能不能胡,就知道这副牌是不是听牌了。
所以人工智能不是凭空产生的,需要程序员把这一丁点儿的智能分析透彻,然后再将其转化成代码,其中会用到不那么智能的方法,比如枚举,回溯等。所以人工智能就是一件大衣,里面包着很多基本的东西,罗马不是一天建成的,人工智能也不会像悟空一样从石头里蹦出来。
其实在数学上描述胡牌的特征非常容易,那就是当你的牌型到达如下模式的时候,你就可以胡了(七条对不算,那个情况非常特殊,单独考虑就可以了):
(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张牌能不能胡,就知道这副牌是不是听牌了。
所以人工智能不是凭空产生的,需要程序员把这一丁点儿的智能分析透彻,然后再将其转化成代码,其中会用到不那么智能的方法,比如枚举,回溯等。所以人工智能就是一件大衣,里面包着很多基本的东西,罗马不是一天建成的,人工智能也不会像悟空一样从石头里蹦出来。
相关文章推荐
- poj2256人工智能?其实就是字符串处理啦
- 【研究】移动办公趋势洞察系列之二:人工智能、智能硬件精彩纷呈,业务协同初心不变
- 【小王CCNA系列之二】浅谈交换机安全
- 生活风水系列(更避邪常识)之二
- 人工智能 Java 坦克机器人系列: 神经网络,下部
- 管理者的本质其实就是一个服务者,服务下属的
- SSE指令集系列之二----浮点与整数转换指令
- 死磕Spring系列之二,bean标签的解析和BeanDefinition的注册
- 【Apache Solr系列之二】Apache Solr 4.5.1及MYSQL数据增量索引
- Android系列学习讲座之二--App自动更新之通知栏下载
- RHEL5系列之二: 全新安装RHEL5(3)
- 所谓开发经验,其实就是对业务流程的积累
- 打仗其实最讲成本核算,大炮轰的都是黄金,日军在中国就是不断赔本
- linux下MMC/SD/SDIO驱动系列之二 ---- host注册过程(一)
- HBase应用开发回顾与总结系列之二:RowKey行键设计规范
- LINQ To SQL深入学习系列之二(C#3.0为LINQ的加强之二)
- 敏捷开发产品管理系列之二:产品版本规划
- linux 必学的命令系列之二
- I - 不容易系列之二
- web数据采集核心技术分享系列(三)如何破解验证码?图像分析?特征匹配?人工智能?第三方集成?...哪个最强大?