MinMax-极小极大算法——2048

Github上找到的是JS的代码,个人用Python重写了,代码之后会开源在github上。

算法介绍

MinMax

大家在编程的时候应该或多或少都接触到过这样的写法:min(max(xxx,yyy)),MinMax算法的表达形式就是如此,不过其中的Min和Max都是具有对应含义的。

一般解决博弈类问题的自然想法是将格局组织成一棵),树的每一个节点表示一种格局,而父子关系表示由父格局经过一步可以到达子格局。Minimax也不例外,它通过对以当前格局为根的格局树搜索来确定下一步的选择。而一切格局树搜索算法的核心都是对每个格局价值的评价。Minimax算法基于以下朴素思想确定格局价值:

  • Minimax是一种悲观算法,即假设对手每一步都会将我方引入从当前看理论上价值最小的格局方向,即对手具有完美决策能力。因此我方的策略应该是选择那些对方所能达到的让我方最差情况中最好的,也就是让<u>对方在完美决策</u>下所<u>对我造成的损失最小</u>。
  • Minimax理论最优解,因为理论最优解往往依赖于对手是否足够愚蠢,Minimax中我方完全掌握主动,如果对方每一步决策都是完美的,则我方可以达到预计的最小损失格局,如果对方没有走出完美决策,则我方可能达到比预计的最悲观情况更好的结局。总之我方就是要在<u>最坏情况中选择最好的</u>。

举例For Example:

现在考虑这样一个游戏:有三个盘子A、B和C,每个盘子分别放有三张纸币。A放的是1、20、50;B放的是5、10、100;C放的是1、5、20。单位均为“元”。有甲、乙两人,两人均对三个盘子和上面放置的纸币有可以任意查看。游戏分三步:

  1. 甲从三个盘子中选取一个。
  2. 乙从甲选取的盘子中拿出两张纸币交给甲。
  3. 甲从乙所给的两张纸币中选取一张,拿走。

其中甲的目标是最后拿到的纸币面值尽量大,乙的目标是让甲最后拿到的纸币面值尽量小。

分析过程可看 MinMax和Alpha-beta剪枝分析[转]

Alpha-beta剪枝

是在Minmax的基础上通过对每个结点下界alpha和上界beta值的维护进行了剪枝

偶数层为Max层(己方),奇数层为Min层(对手),其中root为当前形势。

执行过程

在root层,\alpha' = max(N_1.\beta, N_2.\beta,...,N_i.\beta)==(self.\beta,N_i.\beta)

在Max层

  • 初始,\alpha'=max(-\infty, N.\beta)

  • 非叶子节点更新(包括根节点), \alpha'=max(\alpha,N_1.\beta,...,N_i.\beta)

  • 叶子节点更新, \alpha'=max(self.\alpha, N_1.v,...,N_i.v)

  • 根节点更新,\alpha'=max(\alpha,N_1.\beta,...,N_i.\beta)

    ▲更新后发现self.\alpha'>self.\beta则剪枝,不再搜索

在Min层,

  • 初始,\beta'=min(\infty, N.v)

  • 叶子节点更新,\beta'=min(N_1.v,...,N_i.v)

  • 非叶子节点更新, \beta'=min(self.\beta,N_1.\alpha,...,N_i.\alpha)

    ▲更新后发现self.\beta'<self.\alpha则剪枝,不再搜索

△其中N为子节点; self.α表示当前节点已更新的α值

伪代码

function alphabeta(node, depth, α, β, Player)
    //达到最深搜索深度或胜负已分         
    if  depth = 0 or node is a terminal node
        return the heuristic value of node
    if  Player = MaxPlayer // 极大节点
        for each child of node // 子节点是极小节点
            α := max(α, alphabeta(child, depth-1, α, β, not(Player) ))   
            if β ≤ α 
            // 该极大节点的值>=α>=β,该极大节点后面的搜索到的值肯定会大于β,因此不会被其上层的极小节点所选用了。对于根节点,β为正无穷
                 break //beta剪枝                        
        return α
    else // 极小节点
        for each child of node //子节点是极大节点
            β := min(β, alphabeta(child, depth-1, α, β, not(Player) )) // 极小节点
            if β ≤ α // 该极大节点的值<=β<=α,该极小节点后面的搜索到的值肯定会小于α,因此不会被其上层的极大节点所选用了。对于根节点,α为负无穷
                break //alpha剪枝
        return β 

2048AI介绍

游戏规则:

2048游戏共有16个格子,初始时初始数字由2或者4构成,之后每次移动生成2的概率为0.9,生成4的概率为0.1,见2048随机生成新数字源码

1、手指向一个方向滑动,所有格子会向那个方向运动。
2、相同数字的两个格子,相撞时数字会相加。
3、每次滑动时,空白处会随机刷新出一个数字的格子。
4、当界面不可运动时(当界面全部被数字填满时),游戏结束;当界面中最大数字是2048时,游戏胜利。

建模:

之前的对弈类游戏, 博弈双方的地位都是对等的. 但这边只有游戏者一人, 对手在哪里?
  让人脑洞大开的是, 2048游戏AI的设计者, 创造性把棋局环境本身做为了博弈的另一方.
  当然双方追求的胜利目标不一样:
  • 我方:游戏者(AI), 追求2048及2048以上的方块出现
  • 对方:计算机(棋局环境), 填满棋局格子, 使得4个方向皆不能移动
• 胜利条件:出现某个方块的数值为“2048”。
•失败条件:格子全满,且无法向四个方向中任何一个方向移动(均不能触发合并)。
  ▲.游戏模型就被建模成了信息完备的双人对弈问题. 而传统博弈树和技巧就自然有了用武之地.

评估函数:

评估函数是算法的核心,如何评价当前格局的价值是重中之重。依据游戏经验, 作者选用了如下评估因素:
  (1) 单调性: 指方块从左到右、从上到下均遵从递增或递减.
  (2) 平滑性: 指每个方块与其直接相邻方块数值的差,其中差越小越平滑.
  (3) 空格数: 局面的空格总数.
  (4) 最大数: 当前局面的最大数字, 该特征为积极因子.

采用线性函数, 并添加权重系数:

// 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的实现. 所有的方向(上下左右)都会去尝试.

Alpha+beta pruning + worst consideration pruning

然而在游戏本身做决策时, 在Min节点还采用了另一种剪枝,即只考虑对方走出让格局最差的那一步(而实际2048中计算机的选择是随机的),做为搜索分支的剪枝条件,即不是每个空格都去尝试填{2, 4}。

这种假定环境生成了最坏的局面的剪枝做法,可以很好地提高搜索效率,并且获得更强的生存能力。如果全部搜索的话,对方所有可能的选择就为变成了“空格数×2”种,使得搜索效率很低,严重限制搜索深度。而这种选择性地丢掉了很多搜索分支,能够大大地提高搜索效率。

// 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) } );
    }
  }
}

分析:对于选择性忽略搜索节点, 其实很有争议. 在某些情况下, 会失去获取最优解的机会. 不过砍掉了很多分支后, 其搜索深度大大加强. 生存能力更强大.

限时的迭代深搜

// 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决策一步的要求.

Python实现核心代码:


附录

极大极小算法有些不明白 ?

极小极大算法主要应用于什么样的游戏:

  • 零和游戏(Zero-sum Game)
  • 完全信息(Perfect Information)——Max代表你自己,Min代表你的对手

对弈类游戏的人工智能(5)--2048游戏AI的解读

  • 把环境拟人化的对弈模型, 也是面对反馈类场景的一种很好的评估决策思路.

2048-AI程序算法分析——Java实现

一图流解释 Alpha-Beta 剪枝(Alpha-Beta Pruning)

2048高分技巧

1、最大数尽可能放在角落。
2、数字按顺序紧邻排列。
3、首先满足最大数和次大数在的那一列/行是满的。
4、时刻注意活动较大数(32以上)旁边要有相近的数。
5、以大数所在的一行为主要移动方向
6、不要急于“清理桌面”。

2048随机生成新数字源码

// Adds a tile in a random position
Grid.prototype.addRandomTile = function () {
  if (this.cellsAvailable()) {
    var value = Math.random() < 0.9 ? 2 : 4;
    //var value = Math.random() < 0.9 ? 256 : 512;
    var tile = new Tile(this.randomAvailableCell(), value);

    this.insertTile(tile);
  }
};

▲需要注意的是,2的几率是0.9,4的几率是0.1,但是除了开局会随机出现1或者2个方块,而在正常游戏中每次移动后只出现一个随机方块。——我当时自己写的是随机1-2个,导致我的分数挺低的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260