華容道遊戲(上)

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

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

華容道遊戲介紹

華容道遊戲聽說來源於三國故事「諸葛亮智算華容,關雲長義釋曹操」。在一個5×4的棋盤上佈置多名蜀國大將和軍士做爲棋子,將曹操圍困在中間,經過滑動各個棋子,幫助曹操移動到出口位置逃走。java

算法原理

華容道的數學原理還沒有研究清楚,尚未數學方法能夠一勞永逸的解決它,所以咱們只能用計算機來窮舉全部的可能解,並找出最優解。git

代碼及思路

棋局包括棋盤的狀態和棋子的狀態兩部分,咱們是要用計算機來演化和搜索棋局。對於一個棋局,若是有一種或多種移動武將的方法,這個棋局就能夠演化成一個或多個新的棋局,每一個新棋局又能夠根據移動武將的方式演化成更多的棋局。算法

常量定義

const MAX_WARRIOR_TYPE = 5;                                //格子被武將佔據的狀態,空或武將序號,1+4
const HRD_GAME_ROW = 5;                                    //棋盤實際行數
const HRD_GAME_COL = 4;                                    //棋盤實際列數
const HRD_BOARD_WIDTH = 6;                                 //棋盤定義寬度
const HRD_BOARD_HEIGHT = 7;                                //棋盤定義高度
const BOARD_CELL_EMPTY = 0;                                //格子空置代碼
const BOARD_CELL_BORDER = -1;                              //哨兵代碼
const DIRECTION = [ [0, -1], [1, 0], [0, 1], [-1, 0] ]     //移動方向定義

移動操做定義

class MoveAction{
    constructor(heroIdx, dirIdx){                          
        this.heroIdx = heroIdx || 0                        //武將序號
        this.dirIdx = dirIdx  || 0                         //移動方向
    }
}

武將類型定義

const WARRIOR_TYPE = {
    HT_BLOCK : 1,  //1x1
    HT_VBAR : 2,   //1x2
    HT_HBAR : 3,   //2x1
    HT_BOX : 4     //2x2
}

武將定義

class Warrior {
    constructor(type, left, top){
        this.type = type
        this.left = left
        this.top = top
    }
}

棋局對象定義

二維數組表示棋盤(7×6),擴大了一圈,在外圍設置了「哨兵」;棋盤上各點爲零表示空,數字表明武將的序號。數組

class HrdGameState {
    constructor(){
        this.board = []                              //棋盤
        for(let i = 0; i<HRD_BOARD_HEIGHT; i++){     //棋盤初始化,放置哨兵
            this.board.push([])
            for(let j=0; j<HRD_BOARD_WIDTH; j++){
                this.board[i].push([])
                if(i==0 || j==0 || i==HRD_BOARD_HEIGHT-1 || j==HRD_BOARD_WIDTH-1){
                    this.board[i][j] = BOARD_CELL_BORDER
                }else{
                    this.board[i][j] = BOARD_CELL_EMPTY
                }
            }
        }
        this.parent = null;                        //演化樹上的「父親」
        this.step = 0;                             //演化計數
        this.move = new MoveAction()               //演化方式(武將移動方式)
        this.heroes = []                           //武將列表
    }
    initState(heroes){                             //武將列表初始化
        for(let i = 0; i < heroes.length; i++)
        {
            if(!addGameStateHero(this, i, heroes[i]))    //將武將放置到棋盤上
             {
                return false;
             }
        }
        return true;
    }
}

武將放置函數

function addGameStateHero(gameState, heroIdx, hero)
{
    if(isPositionAvailable(gameState, hero.type, hero.left, hero.top))     //檢測給定位置是否能放置該武將
    {
        takePosition(gameState, heroIdx, hero.type, hero.left, hero.top);    //放置武將到棋盤上
        gameState.heroes.push(hero);                        //將武將存入列表中
        return true;
    }

    return false;
}

檢測可否放置武將函數

function isPositionAvailable(gameState, type, left, top)
{
    let isOK = false;

    switch(type)
    {
    case WARRIOR_TYPE.HT_BLOCK:
        isOK = (gameState.board[top + 1][left + 1] == BOARD_CELL_EMPTY);
        break;
    case WARRIOR_TYPE.HT_VBAR:
        isOK = (gameState.board[top + 1][left + 1] == BOARD_CELL_EMPTY)
               && (gameState.board[top + 2][left + 1] == BOARD_CELL_EMPTY);
        break;
    case WARRIOR_TYPE.HT_HBAR:
        isOK = (gameState.board[top + 1][left + 1] == BOARD_CELL_EMPTY)
               && (gameState.board[top + 1][left + 2] == BOARD_CELL_EMPTY);
        break;
    case WARRIOR_TYPE.HT_BOX:
        isOK = (gameState.board[top + 1][left + 1] == BOARD_CELL_EMPTY)
               && (gameState.board[top + 1][left + 2] == BOARD_CELL_EMPTY)
               && (gameState.board[top + 2][left + 1] == BOARD_CELL_EMPTY)
               && (gameState.board[top + 2][left + 2] == BOARD_CELL_EMPTY);
        break;
    default:
        isOK = false;
        break;
    }

    return isOK;
}

放置武將函數

function takePosition(gameState, heroIdx, type, left, top)
{
    switch(type)
    {
    case WARRIOR_TYPE.HT_BLOCK:
        gameState.board[top + 1][left + 1] = heroIdx + 1;
        break;
    case WARRIOR_TYPE.HT_VBAR:
        gameState.board[top + 1][left + 1] = heroIdx + 1;
        gameState.board[top + 2][left + 1] = heroIdx + 1;
        break;
    case WARRIOR_TYPE.HT_HBAR:
        gameState.board[top + 1][left + 1] = heroIdx + 1;
        gameState.board[top + 1][left + 2] = heroIdx + 1;
        break;
    case WARRIOR_TYPE.HT_BOX:
        gameState.board[top + 1][left + 1] = heroIdx + 1;
        gameState.board[top + 1][left + 2] = heroIdx + 1;
        gameState.board[top + 2][left + 1] = heroIdx + 1;
        gameState.board[top + 2][left + 2] = heroIdx + 1;
        break;
    default:
        break;
    }
}

棋局對象測試

測試代碼數據結構

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)
]
var gameState = new HrdGameState()                        //新建棋局對象
gameState.initState(hs)                                   //初始化棋局,往空棋盤上擺開局
console.dir(gameState.board)                              //輸出棋局

測試結果

結果符合預期函數

[ [ -1, -1, -1, -1, -1, -1 ],
  [ -1, 1, 2, 2, 3, -1 ],
  [ -1, 1, 2, 2, 3, -1 ],
  [ -1, 4, 5, 5, 6, -1 ],
  [ -1, 4, 8, 9, 6, -1 ],
  [ -1, 7, 0, 0, 10, -1 ],
  [ -1, -1, -1, -1, -1, -1 ] ]

完整代碼

代碼託管在開源中國,其中的hyd.js即華容道解法。測試

https://gitee.com/zhoutk/test

小結

儘可能使用面向對象的思想來解決問題,讓數據和操做綁定在一塊兒,努力使代碼容易看懂。this

相關文章
相關標籤/搜索