对弈类游戏的人工智能(5)--2048游戏AI的解读
前言:
闲得没事, 网上搜"游戏AI", 看到一篇<<2048游戏的最佳算法是?来看看AI版作者的回答>>的文章. 而这篇文章刚好和之前讲的对弈类游戏AI对应上. 于是有了想法, 想把它作为一个实例来进行解读, 从而对之前偏理论的文章做个总结.
承接上四篇博文:
(1). 评估函数+博弈树算法
(2). 学习算法
(3). 博弈树优化
(4). 游戏AI的落地
可能有些人会疑惑? 2048并非对弈类类型? 传统的博弈树模型是否能应用于此? 客官莫急, 让我们来一步步揭开谜底.
导读:
本文是对<<2048游戏的最佳算法是?来看看AI版作者的回答>>文章, 以及原作者提供的AI代码进行解读的文章.
如果有兴趣, 可点击试玩的游戏链接, 可查阅的源代码链接.
建模:
之前的对弈类游戏, 博弈双方的地位都是对等的. 但这边只有游戏者一人, 对手在哪里?
让人脑洞大开的是, 2048游戏AI的设计者, 创造性把棋局环境本身做为了博弈的另一方.
当然双方追求的胜利目标不一样:
• 游戏者(AI): 追求2048及2048以上的方块出现
• 棋局环境: 填满棋局格子, 使得4个方向皆不能移动
游戏模型就演变成了信息完备的对弈问题. 而传统博弈树和技巧就自然有了用武之地.
评估函数:
依据游戏经验, 作者选用了如下评估因素:
(1) 单调性: 指方块从左到右、从上到下均遵从递增或递减.
(2) 平滑性: 指每个方块与其直接相邻方块数值的差,其中差越小越平滑.
(3) 空格数: 局面的空格总数.
(4) 最大数: 当前局面的最大数字, 该特征为积极因子.
采用线性函数, 并添加权重系数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // static evaluation function AI.prototype.eval = function () { var emptyCells = this .grid.availableCells().length; var smoothWeight = 0.1, //monoWeight = 0.0, //islandWeight = 0.0, mono2Weight = 1.0, emptyWeight = 2.7, maxWeight = 1.0; return this .grid.smoothness() * smoothWeight //+ this.grid.monotonicity() * monoWeight //- this.grid.islands() * islandWeight + this .grid.monotonicity2() * mono2Weight + Math.log(emptyCells) * emptyWeight + this .grid.maxValue() * maxWeight; }; |
评: 前3项能衡量一个局面的好坏, 而最大数该项, 则让游戏AI多了一点积极和"冒险". 权重系数设定和特征选择其实是个技术活, 作者在这有他的尝试和权衡.
博弈:
游戏AI的决策过程, 是标准的maxmin search和alpha+beta pruning的实现. 所有的方向(上下左右)都会去尝试.
然而在游戏本身做决策时, 不是每个空格都去尝试填{2, 4}. 而是选择了最坏的局面, 做为搜索分支的剪枝条件. 选择性地丢弃了很多搜索分支.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // try a 2 and 4 in each cell and measure how annoying it is // with metrics from eval var candidates = []; var cells = this .grid.availableCells(); var scores = { 2: [], 4: [] }; for ( var value in scores) { for ( var i in cells) { scores[value].push( null ); var cell = cells[i]; var tile = new Tile(cell, parseInt(value, 10)); this .grid.insertTile(tile); scores[value][i] = - this .grid.smoothness() + this .grid.islands(); this .grid.removeTile(cell); } } // now just pick out the most annoying moves var maxScore = Math.max(Math.max.apply( null , scores[2]), Math.max.apply( null , scores[4])); for ( var value in scores) { // 2 and 4 for ( var i=0; i<scores[value].length; i++) { if (scores[value][i] == maxScore) { candidates.push( { position: cells[i], value: parseInt(value, 10) } ); } } } |
对于选择性忽略搜索节点, 其实很有争议. 在某些情况下, 会失去获取最优解的机会. 不过砍掉了很多分支后, 其搜索深度大大加强. 生存能力更强大.
迭代深搜:
不同的javascript引擎其性能差异较大, 若需要限定时间搜索时. 这时迭代深搜就"粉墨登场"了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // performs iterative deepening over the alpha-beta search AI.prototype.iterativeDeep = function () { var start = ( new Date()).getTime(); var depth = 0; var best; do { var newBest = this .search(depth, -10000, 10000, 0 ,0); if (newBest.move == -1) { break ; } else { best = newBest; } depth++; } while ( ( new Date()).getTime() - start < minSearchTime); return best } |
超时判断在每个深度探索结束后进行, 这未必会精确, 甚至误差很大. 我还是推崇前文谈到过的实现方式.
不管怎样, 作者基本达到了其每100ms决策一步的要求.
总结:
前几篇博文涉及到很多点, 都在该2048游戏AI中有所体现. 2048游戏作为非典型的对弈类游戏, 本不太合适作为具体案例来讲解. 但对于原作者创造性的思维和建模, 我们作为后辈可以学到更多. 把环境拟人化的对弈模型, 也是面对反馈类场景的一种很好的评估决策思路.
本文在编写前, 并没注意该博文<<2048 AI 程序算法分析>>的存在. 编写过程中, 借鉴了该文, 也添加了自己的一些认识.
写在最后:
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.
posted on 2015-04-13 01:58 mumuxinfei 阅读(5494) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构