• 2048AI设计与实现

    • 1. 摘要
    • 2. 问题描述
      • 2.1 2048游戏简介
      • 2.2 2048智能提示实现
    • 3. 建模
    • 4. 算法分析
    • 5. 算法实现
      • 5.1 格局评估指标
      • 5.2 Minimax Search和Alpha Beta Pruning的实现
      • 5.3 己方格局评价函数实现
        • 5.3.1 平滑性
        • 5.3.2 单调性
        • 5.3.3 空格数
        • 5.3.4 最大数
      • 5.4 对方格局评价函数实现
      • 5.5 迭代深搜
    • 6. 运行效果
    • 7. 结论
    • 8. 源码和apk
    • 9. References

2048AI设计与实现

1. 摘要

针对2048游戏,实现一个AI程序,当用户选择提示功能时,系统根据当前局势找出适当的决策帮助用户赢得游戏。系统界面实现由Android完成,主体AI使用了传统的博弈树模型中常用的算法,即Minimax和Alpha-beta剪枝,重点落在启发函数的设计上。项目的启发函数从四个方面评估当前的局势,结合迭代深搜,在限定搜索时间内做出决策,赢得游戏的概率高于90%。

2. 问题描述

2.1 2048游戏简介

《2048》是一款比较流行的数字游戏,最早于2014年3月20日发行。原版2048首先在GitHub上发布,原作者是Gabriele Cirulli,后被移植到各个平台。

游戏规则:

每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方乱数出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。不断的叠加最终拼凑出2048这个数字就算成功。

2.2 2048智能提示实现

系统AI提供提示功能,从当前格局出发,寻找所能达到搜索层次的最优的解

点击Hint按钮,显示滑动提示

3. 建模

Since the game is a discrete state space, perfect information, turn-based game like chess and checkers, I used the same methods that have been proven to work on those games, namely minimax search with alpha-beta pruning. Since there is already a lot of info on that algorithm out there, I’ll just talk about the two main heuristics that I use in the static evaluation function and which formalize many of the intuitions that other people have expressed here.

2048本质上可以抽象成信息对称双人对弈模型(玩家向四个方向中的一个移动,然后计算机在某个空格中填入2或4)。这里“信息对称”是指在任一时刻对弈双方对格局的信息完全一致,移动策略仅依赖对接下来格局的推理。作者使用的核心算法为对弈模型中常用的带Alpha-beta剪枝的Minimax。这个算法也常被用于如国际象棋等信息对称对弈AI中。

4. 算法分析

http://blog.codinglabs.org/articles/2048-ai-analysis.html

5. 算法实现

5.1 格局评估指标

    /*** 格局评估函数* * @return 返回当前格局的评估值,用于比较判断格局的好坏*/private double evaluate() {double smoothWeight = 0.1, //平滑性权重系数monoWeight = 1.3, //单调性权重系数emptyWeight = 2.7, //空格数权重系数maxWeight = 1.0; //最大数权重系数return grid.smoothness() * smoothWeight+ grid.monotonicity() * monoWeight+ Math.log(getEmptyNum(grid.getCellMatrix())) * emptyWeight+ grid.maxValue() * maxWeight;}

采用线性函数,,并添加权重系数,前3项指标能衡量一个局面的好坏,而最大数该项,则让游戏AI多了一点积极和”冒险”。

5.2 Minimax Search和Alpha Beta Pruning的实现

private SearchResult search(int depth, double alpha, double beta, int positions, int cutoffs) {double bestScore;int bestMove = -1;SearchResult result = new SearchResult();int[] directions = {0, 1, 2, 3};if (this.grid.playerTurn) {  // Max 层bestScore = alpha;for (int direction : directions) {  // 玩家遍历四个滑动方向,找出一个最好的GameState newGrid = new GameState(this.grid.getCellMatrix());if (newGrid.move(direction)) {positions++;
//                if (newGrid.isWin()) {//                    return new SearchResult(direction, 10000, positions, cutoffs);
//                }AI newAI = new AI(newGrid);newAI.grid.playerTurn = false;if (depth == 0) { //如果depth=0,搜索到该层后不再向下搜索result.move = direction;result.score = newAI.evaluate();} else { //如果depth>0,则继续搜索下一层,下一层为电脑做出决策的层result = newAI.search(depth - 1, bestScore, beta, positions, cutoffs);if (result.score > 9900) { // 如果赢得游戏result.score--; // 轻微地惩罚因为更大的搜索深度}positions = result.positions;cutoffs = result.cutoffs;}//如果当前搜索分支的格局分数要好于之前得到的分数,则更新决策,同时更新bestScore,也即alpha的值if (result.score > bestScore) {bestScore = result.score;bestMove = direction;}//如果当前bestScore也即alpha>beta时,表明这个节点下不会再有更好解,于是剪枝if (bestScore > beta) {cutoffs++;  //剪枝return new SearchResult(bestMove, beta, positions, cutoffs);}}}} else {// Min 层,该层为电脑层(也即我们的对手),这里我们假设对手(电脑)足够聪明,总是能做出使格局变到最坏的决策bestScore = beta;// 尝试给每个空闲块填入2或4,然后计算格局的评估值List<Candidate> candidates = new ArrayList<>();List<int[]> cells = this.grid.getAvailableCells();int[] fill = {2, 4};List<Double> scores_2 = new ArrayList<>();List<Double> scores_4 = new ArrayList<>();for (int value : fill) {for (int i = 0; i < cells.size(); i++) {this.grid.insertTitle(cells.get(i)[0], cells.get(i)[1], value);if (value == 2) scores_2.add(i, -this.grid.smoothness() + this.grid.islands());if (value == 4) scores_4.add(i, -this.grid.smoothness() + this.grid.islands());this.grid.removeTile(cells.get(i)[0], cells.get(i)[1]);}}// 找出使格局变得最坏的所有可能操作double maxScore = Math.max(Collections.max(scores_2), Collections.max(scores_4));for (int value : fill) {if (value == 2) {for (Double fitness : scores_2) {if (fitness == maxScore) {int index = scores_2.indexOf(fitness);candidates.add(new Candidate(cells.get(index)[0], cells.get(index)[1], value));}}}if (value == 4) {for (Double fitness : scores_4) {if (fitness == maxScore) {int index = scores_4.indexOf(fitness);candidates.add(new Candidate(cells.get(index)[0], cells.get(index)[1], value));}}}}// 然后遍历这些操作,基于这些操作向下搜索,找到使得格局最坏的分支for (int i = 0; i < candidates.size(); i++) {int pos_x = candidates.get(i).x;int pos_y = candidates.get(i).y;int value = candidates.get(i).value;GameState newGrid = new GameState(this.grid.getCellMatrix());// 电脑即对手做出一个可能的对于电脑来说最好的(对于玩家来说最坏的)决策newGrid.insertTitle(pos_x, pos_y, value);positions++;AI newAI = new AI(newGrid);// 向下搜索,下一层为Max层,轮到玩家进行决策newAI.grid.playerTurn = true;// 这里depth没有减1是为了保证搜索到最深的层为Max层result = newAI.search(depth, alpha, bestScore, positions, cutoffs);positions = result.positions;cutoffs = result.cutoffs;// 该层为Min层,哪个分支的局势最不好,就选哪个分支,这里的bestScore代表betaif (result.score < bestScore) {bestScore = result.score;}// 如果当前bestScore也即beta<alpha时,表明这个节点下不会再有更好解,于是剪枝if (bestScore < alpha) {cutoffs++;  //减枝return new SearchResult(-1, alpha, positions, cutoffs);}}}return new SearchResult(bestMove, bestScore, positions, cutoffs);
}

5.3 己方格局评价函数实现

5.3.1 平滑性

/*** 测量网格的平滑程度(这些块的值可以形象地解释为海拔)。* 相邻两个方块的值差异越小,格局就越平滑(在log空间中,所以它表示在合并之前需要进行的合并的数量)。** @return*/
public double smoothness() {int smoothness = 0;for (int x = 0; x < 4; x++) {for (int y = 0; y < 4; y++) {if (this.cellMatrix[x][y] != 0) {double value = Math.log(this.cellMatrix[x][y]) / Math.log(2);// 计算水平方向和垂直方向的平滑性评估值for (int direction = 1; direction <= 2; direction++) {int[] vector = this.vectors[direction];int cnt_x = x, cnt_y = y;do {cnt_x += vector[0];cnt_y += vector[1];} while (isInBounds(cnt_x, cnt_y) && isCellAvailable(cnt_x, cnt_y));if (isInBounds(cnt_x, cnt_y)) {if (cellMatrix[cnt_x][cnt_y] != 0) {double targetValue = Math.log(cellMatrix[cnt_x][cnt_y]) / Math.log(2);smoothness -= Math.abs(value - targetValue);}}}}}}return smoothness;
}

5.3.2 单调性

/*** 测量网格的单调性。* 这意味着在向左/向右和向上/向下的方向,方块的值都是严格递增或递减的。** @return*/
public double monotonicity() {// 保存四个方向格局单调性的评估值int[] totals = {0, 0, 0, 0};// 左/右 方向for (int x = 0; x < 4; x++) {int current = 0;int next = current + 1;while (next < 4) {while (next < 4 && this.cellMatrix[x][next] == 0) next++;if (next >= 4) next--;double currentValue = (this.cellMatrix[x][current] != 0) ? Math.log(this.cellMatrix[x][current]) / Math.log(2) : 0;double nextValue = (this.cellMatrix[x][next] != 0) ? Math.log(this.cellMatrix[x][next]) / Math.log(2) : 0;if (currentValue > nextValue) {totals[0] += nextValue - currentValue;} else if (nextValue > currentValue) {totals[1] += currentValue - nextValue;}current = next;next++;}}// 上/下 方向for (int y = 0; y < 4; y++) {int current = 0;int next = current + 1;while (next < 4) {while (next < 4 && this.cellMatrix[next][y] == 0) next++;if (next >= 4) next--;double currentValue = (this.cellMatrix[current][y] != 0) ? Math.log(this.cellMatrix[current][y]) / Math.log(2) : 0;double nextValue = (this.cellMatrix[next][y] != 0) ? Math.log(this.cellMatrix[next][y]) / Math.log(2) : 0;if (currentValue > nextValue) {totals[2] += nextValue - currentValue;} else if (nextValue > currentValue) {totals[3] += currentValue - nextValue;}current = next;next++;}}// 取四个方向中最大的值为当前格局单调性的评估值return Math.max(totals[0], totals[1]) + Math.max(totals[2], totals[3]);
}

5.3.3 空格数

private int getEmptyNum(int[][] matrix) {int sum = 0;for (int i = 0; i < matrix.length; i++)for (int j = 0; j < matrix[0].length; j++)if (matrix[i][j] == 0) sum++;return sum;
}

5.3.4 最大数

/*** 取最大数,这里取对数是为与前面其它指标的计算保持一致,均在log空间进行** @return*/
public double maxValue() {return Math.log(ArrayUtil.getMax(cellMatrix)) / Math.log(2);
}

5.4 对方格局评价函数实现

对方对于格局的目标就是使得连通个数变多并且使得平滑性降低,表现效果就是使得格局趋于散乱,让玩家难以合并相同的数字。

/*** 递归调用计算当前格局的连通块个数* * @return*/
public int islands() {int islands = 0;marked = new boolean[4][4];for (int x = 0; x < 4; x++) {for (int y = 0; y < 4; y++) {if (this.cellMatrix[x][y] != 0) {this.marked[x][y] = false;}}}for (int x = 0; x < 4; x++) {for (int y = 0; y < 4; y++) {if (this.cellMatrix[x][y] != 0 && !this.marked[x][y]) {islands++;mark(x, y, this.cellMatrix[x][y]);}}}return islands;
}private void mark(int x, int y, int value) {if (x >= 0 && x <= 3 && y >= 0 && y <= 3 && (this.cellMatrix[x][y] != 0)&& (this.cellMatrix[x][y] == value) && (!this.marked[x][y])) {this.marked[x][y] = true;for (int direction = 0; direction < 4; direction++) {int[] vector = this.vectors[direction];mark(x + vector[0], y + vector[1], value);}}
}

5.5 迭代深搜

// 执行搜索操作,返回最好的移动方向
public int getBestMove() {return this.iterativeDeep(100);
}// 基于alpha-beta的Minimax搜索,进行迭代深搜,搜索时间设定为0.1秒,即决策的思考时间为0.1秒
private int iterativeDeep(long minSearchTime) {long start = new Date().getTime();int depth = 0;int best = -1;do {SearchResult newBest = this.search(depth, -10000, 10000, 0, 0);if (newBest.move == -1) break;else best = newBest.move;depth++;} while (new Date().getTime() - start < minSearchTime);return best;
}

6. 运行效果

自动运行结果

7. 结论

游戏AI的决策过程,是标准的Minimax Search 结合 Alpha Beta Pruning的实现。 所有的方向(上下左右)都会去尝试。然而在对手做决策时,不是每个空格都去尝试填2或4。 而是选择了最坏的局面,做为搜索分支的剪枝条件,选择性地丢弃了很多搜索分支。对于选择性忽略搜索节点,在某些情况下,会失去获取最优解的机会。不过砍掉了很多分支后, 其搜索深度大大加强,生存能力更强大。另外,超时判断在每个深度探索结束后进行, 这未必会精确,甚至误差很大,但不管如何游戏AI基本达到了每100ms决策一步的要求。最后,根据原作者ovolve创造性的思维和建模,把环境拟人化的对弈模型,使得传统的博弈树模型能应用于此,这是面对反馈类场景的一种很好的评估决策思路。

8. 源码和apk

https://github.com/wylu/md2048

9. References

https://stackoverflow.com/questions/22342854/what-is-the-optimal-algorithm-for-the-game-2048

http://ov3y.github.io/2048-AI/

https://github.com/ov3y/2048-AI

https://en.wikipedia.org/wiki/Evaluation_function

《Artificial Intelligence : A Modern Approach》 (Third Edition) 第5章 对抗搜索

http://www.flyingmachinestudios.com/programming/minimax/

https://www.neverstopbuilding.com/blog/2013/12/13/tic-tac-toe-understanding-the-minimax-algorithm13/

http://web.cs.ucla.edu/~rosen/161/notes/alphabeta.html

https://www.cnblogs.com/mumuxinfei/p/4415352.html

http://blog.codinglabs.org/articles/2048-ai-analysis.html

http://www.cnblogs.com/mumuxinfei/p/4305981.html

http://www.cnblogs.com/mumuxinfei/p/4379595.html

http://www.cnblogs.com/mumuxinfei/p/4396339.html

http://www.cnblogs.com/mumuxinfei/p/4398680.html

2048AI设计与实现相关推荐

  1. 2022-2028年中国UI设计行业现状调研分析及发展趋势研究报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国UI设计行业市场行业相关概述.中国UI设 ...

  2. 【VB】学生信息管理系统2——窗体设计

    这次学生系统是照着书敲的,先敲完然后开始调试!中途遇到了很多问题,查了很多,这里不容易系统的总结!所以就针对各个问题,各个击破! 问题一:VB 6.0中,状态栏控件(sbstatusbar):右击选项 ...

  3. LeetCode简单题之设计 Goal 解析器

    题目 请你设计一个可以解释字符串 command 的 Goal 解析器 .command 由 "G"."()" 和/或 "(al)" 按某种 ...

  4. LeetCode简单题之设计停车系统

    题目 请你给一个停车场设计一个停车系统.停车场总共有三种不同大小的车位:大,中和小,每种尺寸分别有固定数目的车位. 请你实现 ParkingSystem 类: ParkingSystem(int bi ...

  5. 单周期十条指令CPU设计与verilog实现(Modelsim)

    目录 一.实验目的 二.实验内容 三.实验原理 1. 规定 2. 原理图 四.实验步骤 1. CPU各部件实现 pc insMem acc alu cu dataMem 2. CPU例化实现 3. 编 ...

  6. EDA电子设计技术与应用

    EDA电子设计技术与应用 电子设计自动化(英语:Electronic design automation,缩写:EDA)是指利用计算机辅助设计(CAD)软件,来完成超大规模集成电路(VLSI)芯片的功 ...

  7. AI中pass架构设计优化

    AI中pass架构设计优化 Relay 和 TVM IR,包含一系列优化passes,可提高模型的性能指标,例如平均推理,内存占用,或特定设备的功耗.有一套标准优化,及特定机器学习的优化,包括常量折叠 ...

  8. MegEngine 框架设计

    MegEngine 框架设计 MegEngine 技术负责人许欣然将带了解一个深度学习框架是如何把网络的定义逐步优化并最终执行的,从框架开发者的视角来看待深度学习. 背景 AI 浪潮一波又一波,仿佛不 ...

  9. AI框架精要:设计思想

    AI框架精要:设计思想 本文主要介绍飞桨paddle平台的底层设计思想,可以帮助用户理解飞桨paddle框架的运作过程,以便于在实际业务需求中,更好的完成模型代码编写与调试及飞桨paddle框架的二次 ...

最新文章

  1. 《树莓派Python编程指南》——2.2 一个Python游戏:猫和老鼠
  2. Windows进程与线程学习笔记(七)—— 时间片管理
  3. django网上商城_网上商城开源项目合集
  4. 成员变量的初始化和内存中的运行机制
  5. oracle数据库恢复参数文件位置,Oracle数据库的参数文件备份与恢复
  6. git克隆远程仓库的时候断电了,使用git-fetch断点续传
  7. 开源漏洞扫描工具(OWASP-Dependency-Check)探索
  8. Spring MVC拦截器的执行流程
  9. swift 点击imageView全屏预览(UIview中弹出提示框)
  10. 原来awt是可以设置多个frame
  11. group by having where order by
  12. sqlserver sql行专列_Sqlserver 列转行 行转列
  13. vscode java
  14. Intellij IDEA--导入导出配置
  15. R语言基于visreg 包COX回归和连续变量交互效应(交互作用)的可视化分析
  16. T-SQL - 习题02_将数据表year|month|amount查询成year|m1|m2|m3|m4的样式
  17. android免root读写u盘最新方法,支持安卓Q+
  18. ffmpeg分离视频音频流!
  19. 数学分析 导数与微分(第5章)
  20. 2013 7 5新鲜出炉 Ubuntu12 04下载Android4 0 1源码全过程---------------

热门文章

  1. 线上环境频繁GC问题排查,Finalizer对象该背这个锅吗?
  2. 爬取豆瓣电影剧情片排行榜
  3. 城市之眼视觉计算技术
  4. 编写程序实现通过有道或百度翻译url对用户输入数据进行翻译_8亿用户AI有道:超强神经网络翻译技术大解密...
  5. C++学习笔记——第三天运算符和表达式
  6. python 取数组最后一个,如何获取数组中的最后一个键?
  7. 正睿OI DAY12 ks5
  8. MERGE操作学习总结
  9. 数据库SQL实战 --47.将所有获取奖金的员工当前的薪水增加10%
  10. 年中团建之军训-回顾