基於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 。

語言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.在這之後的仍然沒有合理的攻擊方式,根據隨機數選擇攻擊方式(除去不理想的反擊)。

說了這麼多,感覺沒參加這次比賽的讀者也不能夠對我的策略理解很透徹,更多的算作是我個人的記錄吧,若是讀者感覺第一部分的算法有些幫助的話,我也能少些愧疚了。

歡迎評論和指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章