華容道遊戲(下)

此爲《算法的樂趣》讀書筆記,我用javascript(ES6)從新實現算法。javascript

華容道遊戲看似簡單,但求解須要設計的數據結構比較複雜,還牽涉到棋類遊戲的棋局判斷,因此整個過程仍是挺費勁的。我儘可能用面向對象的思想來進行封裝,整個過程將分紅幾個部分記錄下來,今天是最後一部分,棋局的廣度搜索。

廣度搜索

棋局的搜索空間是一個樹狀關係空間,廣度優先搜索可以首先找到最優解,由於首先找到的解深度是最淺的。java

遊戲定義

咱們對算法的要求是:給定一個華容道遊戲的開局佈局,能夠獲得這個開局的全部解決方法以及相應的武將移動步驟,要求算法具備能用性,能處理任何一種開局的華容道遊戲。git

class HrdGame{
    constructor(caoIdx, heroes){                
        this.caoIdx = caoIdx;                        //曹操在武將列表中的序號
        var startState = new HrdGameState();         //新建開局棋局
        startState.initState(heroes)                 //開局棋局初始化
        this.states = []                             //存儲全部棋局狀態,廣度搜索的狀態空間
        this.zhash = {}                              //棋局及其鏡像哈希,判重空間
        this.result = 0;                             //解的總數
        addNewStatePattern(this,startState)          //開局處理,至關於遊戲初始化
    }
}

算法思路及代碼

遊戲的求解過程就是棋局的搜索過程,每移動一個棋子就會生成一個新的棋局,對每個棋局咱們都要生成其全部的後續棋局。結束條件:在生成每個新棋局時,判斷是否爲解,是則該狀態終止;另外一方面,每個棋局若其後續棋局數爲空,也自動終止。算法

解的斷定

function isEscaped(game, gameState){            //曹操的位置到達(1,3)
    return (gameState.heroes[game.caoIdx -1].left == CAO_ESCAPE_LEFT) && (gameState.heroes[game.caoIdx - 1].top == CAO_ESCAPE_TOP)
}

棋局搜索

function resolveGame(game)                                     //廣度搜索主函數
{
    let index = 0;
    while(index < game.states.length){
        gameState = game.states[index];                        //依次選定棋局狀態
        if(isEscaped(game, gameState)){                        //找到解,輸出
            game.result++;
            console.log('result:'+game.result+' step--'+gameState.step+' index:'+index)
        }
        else{
            searchNewGameStates(game, gameState);             //選定棋局搜索全部新棋局
        }
        index++;
    }
    return (game.result > 0);
}

搜索新棋局

武將移動產生新棋局。數據結構

function searchNewGameStates(game, gameState)                   //搜索新棋局
{
    for(let i = 0; i < gameState.heroes.length; i++)            //遍歷武將
    {
        for(let j = 0; j < MAX_MOVE_DIRECTION; j++)             //遍歷全部方向
        {
            trySearchHeroNewState(game, gameState, i, j);       //移動武將產生新棋局
        }
    }
}

新棋局生成

根據華容道規則,對一個武將棋子連續移動只算一步,所以在每一步移動成功後,須要繼續對該棋子嘗試移動,可是移動的方向有限制,不能向原方向移動。函數

function trySearchHeroNewState(game, gameState, heroIdx, dirIdx)
{
    let newState = moveHeroToNewState(gameState, heroIdx, dirIdx);    //新棋局產生
    if(newState) {
        if(addNewStatePattern(game, newState))                        //處理新棋局,判重,添加到狀態鏈中
        {
             /*嘗試連續移動,根據華容道遊戲規則,連續的移動也只算一步*/
            tryHeroContinueMove(game, newState, heroIdx, dirIdx);
            return;
        }
    }
}

移動武將

function moveHeroToNewState(gameState, heroIdx, dirIdx)
{
    if(canHeroMove(gameState, heroIdx, dirIdx))                //可以移動
    {
        var newState = new HrdGameState();                     //新建棋局
        if(newState)
        {
            copyGameState(gameState, newState);                //用父棋局初始化新棋局
            var hero = newState.heroes[heroIdx];               //取得武將
            const dir = DIRECTION[dirIdx];                     //取得方向

            clearPosition(newState, hero.type, hero.left, hero.top); //清除父棋局信息
            takePosition(newState, heroIdx, hero.type, hero.left + dir[0], hero.top + dir[1]);            //新棋局數據生成
            hero.left = hero.left + dir[0];                    //武將新位置設定
            hero.top = hero.top + dir[1];

            newState.step = gameState.step + 1;                //移動步數加一
            newState.parent = gameState;                       //造成因果鏈
            newState.move.heroIdx = heroIdx;                   //記錄移動方法
            newState.move.dirIdx = dirIdx;
            return newState;                                   //返回新棋局
        }
    }
    return null;
}

處理棋局

function addNewStatePattern(game, gameState)
{
    var l2rHash = getZobristHash(zobHash, gameState);            //計算棋局哈希值
    if(!game.zhash[l2rHash])                                     //棋局不存在
    {
        game.zhash[l2rHash] = l2rHash;                           //棋局哈希存儲
        var r2lHash = getMirrorZobristHash(zobHash, gameState);
        game.zhash[r2lHash] = r2lHash;                           //棋局鏡像哈希存儲
        game.states.push(gameState);                             //棋局存儲

        return true;
    }

    return false;                                                //棋局已經存在,忽略
}

開局及解

橫刀立馬

定義:佈局

var hs =[new Warrior(WARRIOR_TYPE.HT_VBAR,0,0),          
          new Warrior(WARRIOR_TYPE.HT_BOX,1,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,2),
          new Warrior(WARRIOR_TYPE.HT_HBAR,1,2),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,2),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,0,4),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,3,4)
]

四個解:this

result:1 step--81 index:11930
result:2 step--85 index:12123
result:3 step--98 index:12337
result:4 step--101 index:12348

運籌帷幄

定義:設計

var hs = [new Warrior(WARRIOR_TYPE.HT_VBAR,0,0),          //構建武將列表,初始棋局
          new Warrior(WARRIOR_TYPE.HT_BOX,1,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,0),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,0,2),
          new Warrior(WARRIOR_TYPE.HT_HBAR,1,2),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,3,2),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,3)
]

四個解:調試

result:1 step--73 index:11391
result:2 step--84 index:12207
result:3 step--86 index:12263
result:4 step--89 index:12306

兵分三路

定義:

var hs = [new Warrior(WARRIOR_TYPE.HT_BLOCK,0,0),          //構建武將列表,初始棋局
          new Warrior(WARRIOR_TYPE.HT_BOX,1,0),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,3,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,1),
          new Warrior(WARRIOR_TYPE.HT_HBAR,1,2),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,1),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,3)
]

四個解:

result:1 step--74 index:7767
result:2 step--80 index:9212
result:3 step--94 index:10921
result:4 step--97 index:11157

完整代碼

文中是主要代碼分析,完整代碼託管在開源中國,其中的hyd.js即華容道解法。

https://gitee.com/zhoutk/test

小結

終於完成了,其中遇到一個坑,就是zobrist的空間問題,《算法的樂趣》書中是說用32位整數,但其提供的源碼是左移15位,我以爲也應該夠,就用了15位整數。結果搜索不到解,各類調試、跟蹤,感受哪哪都是對的,曹操就是下不來,鬱悶一晚。忽然想到會不會是zobrist空間過小,若空間過小,新的棋局會與舊棋局衝突,這樣應該會致使不少狀態被忽略。清晨起牀一試,爽!

相關文章
相關標籤/搜索