基于Pierre Dellacherie算法的俄罗斯方块机器人

主题:基于Pierre Dellacherie算法的俄罗斯方块机器人

前言

有的人说这算是AI,有的人说不算是。我个人认为最多算作是一个轻人工智能。因为我觉得这个机器人的算法写的比较死,不能完全意义上算作人工智能。

背景

做这个机器人的原因,是因为接触到了一个企业(西安葡萄城)举办的趣味比赛。在这个比赛中需要制作一个机器人去玩俄罗斯方块,过程中也会根据自己的得分有攻击的机会(将自己消除的俄罗斯方块添加到对手的棋盘底部),最终存活时间久的玩家获胜。

方法

在这个比赛中,要求玩家制作出的机器人能做到以下两点:

  1. 好的处理俄罗斯方块
  2. 好的攻击策略
1. AI玩俄罗斯方块:基于Pierre Dellacherie算法

该算法只考虑局部最优,对于全局最优不考虑也没有那么多的资源去考虑。保证棋盘越矮越紧凑是局部最优的选择方法。

枚举该方块所有旋转的所有落点:一种方块最多有 4 种旋转,并且由于游戏界面是 10 * 20 的,所以对于每个旋转形状,只需要考虑 10 种落点。

这个算法的核心是根据对于当前方块下落后的棋盘做一个评估value。对于当前方块所有可能下落的方式进行评估,选择value值最大的方案。

评估过程主要包含6个参数:
LandingHeight:下落后的高度
ErodedPieceCellsMetric:消除贡献值=消除行数该方块参与消除的格子数。
例如,该情况下消除了2行,该方块提供了3个单位的格子。那么贡献值=2
3=6

RowTransitions:行变换数。按行遍历,共20行,从空白格进入被占格算作一次变换,从被占格进入空白格也算作一次变换。所有行的变换次数之和即为返回值。
PS:根据比赛中俄罗斯方块的玩法(两侧不算做边界,即从左侧平移接触到边界后会从右侧平移出来),不把两侧算作边界。而是当作一个环去计算行变换数。

语言C#
//获取行变换数
private int GetBoardRowTransitions(TetrisGrid grid)
{
    TetrisBlock[,] gb = grid.Blocks;
    int num = 0;
    for (int j = 0; j < gb.GetLength(1); ++j)
    {
        int lst = (gb[0, j] == null) ? 0 : 1, now = 0;
        for (int i = 1; i < gb.GetLength(0); ++i)
        {
            now = (gb[i, j] == null) ? 0 : 1;
            if (lst != now)
            {
                num++;
                lst = now;
            }
        }
        //左右边界是相通的
        now = (gb[0, j] == null) ? 0 : 1;
        if (lst != now)
            num++;
    }
    return num;
}

BoardColTransitions:列变换数:同行变换数,只不过换成了按列遍历。(上下两侧算做边界)

语言C#
//获取列变换数
private int GetBoardColTransitions(TetrisGrid grid)
{
    TetrisBlock[,] gb = grid.Blocks;
    int num = 0;
    for (int i = 0; i < gb.GetLength(0); ++i)
    {
        //上下边界不相通
        int lst = 1, now = 0;
        for (int j = 0; j < gb.GetLength(1); ++j)
        {
            now = (gb[i, j] == null) ? 0 : 1;
            if (lst != now)
            {
                num++;
                lst = now;
            }
        }
        if (now == 0)
            num++;
    }
    return num;
}

BoardBuriedHoles:空洞数,空洞指的是,每列中某个方块下面没有方块的空白位置,该空白可能由 1 个单位或多个单位组成,但只要没有被方块隔断,都只算一个空洞。注意,空洞的计算以列为单位,若不同列的相邻空格连在一起,不可以将它们算作同一个空洞。

语言C#
//获取空洞数
private int GetBoardBuriedHoles(TetrisGrid grid)
{
    TetrisBlock[,] gb = grid.Blocks;
    int num = 0;
    for (int i = 0; i < gb.GetLength(0); ++i)
    {
        bool haveroof = false;
        for (int j = gb.GetLength(1) - 1; j >= 0; --j)
        {
            int now = (gb[i, j] == null) ? 0 : 1;
            if (haveroof && now == 0)
            {
                num++;
                haveroof = false;
            }
            haveroof |= (now == 1);
        }
    }
    return num;
}

BoardWells:井数,与字面意义一样–水井一样的个数。井指的是某一列中,两边都有方块的连续空格,(左右两侧看成一个环,不算做边界)。
返回值为进的深度的连加。若一个井由 1 个方块组成,则为 1 ;若由连续 3 个组成,则和为 1 + 2 + 3 。

扫描二维码关注公众号,回复: 11383294 查看本文章
语言C#
//获取井数
 private int GetBoardWells(TetrisGrid grid)
 {
     TetrisBlock[,] gb = grid.Blocks;
     int num = 0;
     for (int i = 0; i < gb.GetLength(0); ++i)
     {
         int deep = 0;
         for (int j = gb.GetLength(1) - 1; j >= 0; --j)
         {
             //左右相通
             int left = (i - 1 + gb.GetLength(0)) % gb.GetLength(0);
             int right = (i + 1 + gb.GetLength(0)) % gb.GetLength(0);

             int now = (gb[i, j] == null) ? 0 : 1;
             left = (gb[left, j] == null) ? 0 : 1;
             right = (gb[right, j] == null) ? 0 : 1;

             if (now == 0 && left == 1 && right == 1)
             {
                 deep++;
                 num += deep;
             }
             else
             {
                 deep = 0;
             }
         }
     }
     return num;
 }

评估函数:
评估函数如下 (首字母简写):
value=A1*lh+A2*epcm+A3*rt+A4*ct+A5*bh+A6*bw
权重经验值:
A1: -4.500158825082766
A2: 3.4181268101392694
A3 : -3.2178882868487753
A4 : -9.348695305445199
A5 : -7.899265427351652
A6 : -3.3855972247263626
个人改进:
因为这个比赛过程中,会遭受到攻击,若是遭受围攻很容易高度变得很高,所以当高度过高时,所有参数的值可以适当变换。

2. 攻击策略

简单介绍以下规则吧:(也可以点击链接,但是担心链接会失效还是贴点图,hhh)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面介绍以一下我自己的策略:(优先级依次降低)
1.根据个人多次本地测试,前期遭受围攻的话是很难存活的,所以首先就要让自己不要攻击别人来减少被反击的概率。所以对于第一个方块的下落不采取快速下降,采取自然下降。来减少自己前期得分,拖延自己的第一次攻击时间。
2.获取一个高危线:大部分方块高度占2格(10%)+平移加旋转(由于操作时间可能下落一格5%)+(10个格子未被填满5%),本人可攻击的最大值为10*(1 + 1.0*me.Badge / 32 )。
一旦有高于高危线的玩家,选取攻击高风险的玩家。
3.找到上一个攻击的人,在自己的风险值低于平均水平且对手风险值高的时候选取反击的攻击方式。
4.在这之后的仍然没有合理的攻击方式,根据随机数选择攻击方式(除去不理想的反击)。

说了这么多,感觉没参加这次比赛的读者也不能够对我的策略理解很透彻,更多的算作是我个人的记录吧,若是读者感觉第一部分的算法有些帮助的话,我也能少些愧疚了。

欢迎评论和指正!

猜你喜欢

转载自blog.csdn.net/wjl_zyl_1314/article/details/106971460