此爲《算法的樂趣》讀書筆記,我用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