字母js版本http://www.codefans.net/jscss/code/4222.shtml
谁这么有才做的UI
<html> <head> <style> body{ background-color:grey; font-family:Á¥Êé; font-size:80px; } #guan{ 200px; height:100px; background-color:darkred; color:white; text-align:center; vertical-align:middle; float:left; } #guanteam{ 200px; height:200px; float:left; } #zhang{ 100px; height:200px; background-color:black; color:white; text-align:center; vertical-align:middle; float:left; } #zhao{ 100px; height:200px; background-color:darkgreen; color:white; text-align:center; vertical-align:middle; float:left; } #ma{ 100px; height:200px; background-color:darkblue; color:white; text-align:center; vertical-align:middle; float:left; clear:left; } #huang{ 100px; height:200px; background-color:yellow; color:white; text-align:center; vertical-align:middle; float:left; } #caocao{ 200px; height:200px; background-color:white; color:black; text-align:center; vertical-align:middle; float:left; } #zu1,#zu2,#zu3,#zu4{ 100px; height:100px; background-color:DarkCyan; float:left; } #zu3{ clear:left; } #zu4{ float:right; } #container{ 400px; } </style> </head> <body> <div id="container"> <div id="zhang">ÕÅ·É</div> <div id="caocao">²Ü²Ù</div> <div id="zhao">ÕÔÔÆ</div> <div id="ma">Âí³¬</div> <div id="guanteam"> <div id="guan">¹ØÓð</div> <div id="zu1">×ä</div> <div id="zu2">×ä</div> </div> <div id="huang">»ÆÖÒ</div> <div id="zu3">×ä</div> <div id="zu4">×ä</div> </div> </body> </html>
华容道算法设计http://blog.chinaunix.net/uid-227715-id-2114839.html
人家做的falshhttp://game.onegreen.net/flash/HTML/7602.html
厉害的直接angularhttp://www.jinandsu.net/huarongdao/#/game
git☆ https://github.com/jeantimex/hua-rong-dao-html
三部曲上:http://www.thinksaas.cn/topics/0/500/500678.html,https://segmentfault.com/a/1190000005029301
华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的思想来进行封装,整个过程将分成几个部分记录下来,今天是第一部分,数据结构的定义与初始化。 华容道游戏介绍 华容道游戏据说来源于三国故事“诸葛亮智算华容,关云长义释曹操”。在一个5×4的棋盘上布置多名蜀国大将和军士作为棋子,将曹操围困在中间,通过滑动各个棋子,帮助曹操移动到出口位置逃走。 算法原理 华容道的数学原理尚未研究清楚,还没有数学方法可以一劳永逸的解决它,因此我们只能用计算机来穷举所有的可能解,并找出最优解。 代码及思路 棋局包括棋盘的状态和棋子的状态两部分,我们是要用计算机来演化和搜索棋局。对于一个棋局,如果有一种或多种移动武将的方法,这个棋局就可以演化成一个或多个新的棋局,每个新棋局又可以根据移动武将的方式演化成更多的棋局。 常量定义 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://git.oschina.net/zhoutk/test.git 小结 尽量使用面向对象的思想来解决问题,让数据和操作绑定在一起,努力使代码容易看懂。
三部曲中:http://www.thinksaas.cn/topics/0/500/500673.html,https://segmentfault.com/a/1190000005032938
华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的思想来进行封装,整个过程将分成几个部分记录下来,今天是第二部分,棋局处理Zobrist算法原理及实现。 Zobrist算法原理 Zobrist哈希算法是一种适用于棋类游戏的棋局编码方式,通过建立一个特殊的转换表,对棋盘上每一个位置的所有可能状态赋予一个绝不重复的随机编码,通过对不同位置上的随机编码进行异或计算,实现在极低冲突率的前提下将复杂的棋局编码为一个整数类型哈希值的功能。 Zobrist哈希算法步骤: 识别出棋局的最小单位(格子或交叉点),确定每个最小单位上的所有可能的状态数。以华容道的棋局为例,最小单位就是20个小格子,每个格子有五种状态,分别是空状态、被横长方形占据、被坚长方形占据、被小方格占据和被大方格占据。 为每个单位上的所有状态都分配一个随机的编码值。棋类游戏一般需要“行数×列数×状态数”个状态,以华容道为例,需要为5×4×5=100个状态分配编码值。 对指定的棋局,对每个单位上的状态用对应的编码值(随机数)做异或运算,最后得到一个哈希值。 Zobrist哈希算法优点: 冲突概率小,只要随机编码值的范围够大,棋局哈希冲突的概率非常小,实际应用中基本上不考虑冲突的情况。 棋局发生变化时,不必对整个棋局重新计算哈希值,只需要计算发生变化的那些最小单元的状态变化即可。 Zobrist算法实现 编码表定义 编码表定义为一个三维数组。 class Zobrist{ constructor(){ //三维表属性 this.zobHash = [] for(let i = 0; i < HRD_GAME_ROW; i++) //初始化 { this.zobHash.push([]) for(let j = 0; j < HRD_GAME_COL; j++) { this.zobHash[i].push([]) for(let k = 0; k < MAX_WARRIOR_TYPE; k++) { do{ var tmp = Math.random() tmp = Math.floor(tmp * Math.pow(2,15)) //对16位随机整数值 }while(!tmp) //跳过零值 this.zobHash[i][j].push(tmp) } } } } get(i,j,k){ //get接口 return this.zobHash[i][j][k] } } 计算棋局Zobrist哈希值 对棋盘的格子逐个处理,根据棋盘格子的武将信息获取武将的类型,从而获取该类型对应的编码值,用此编码值参与哈希值进行异或运算。 function getZobristHash(zobHash, state) { let hash = 0; let heroes = state.heroes; for(let i = 1; i <= HRD_GAME_ROW; i++) { for(let j = 1; j <= HRD_GAME_COL; j++) { let index = state.board[i][j] - 1; //取得格子上武将序号 let type = (index >= 0 && index < heroes.length) ? heroes[index].type : 0; //数组索引值超出范围,定为零 hash ^= zobHash.get(i - 1,j - 1,type); //异或计算 // console.log(index+'--'+type+'--'+zobHash[i - 1][j - 1][type]+'<=>'+hash) } } return hash; } 取镜像Zobrist哈希值 棋盘状态左右镜像问题:两个棋局虽然武将的位置不一样,但是如果忽略武将的名字信息,单纯从形状上看是左右对称的镜像结构。对于华容道游戏来说,这种左右镜像的情况对于滑动棋子寻求结果的影响是一样的。 镜像即左右对称,进行一个坐标变换即可得到。 function getMirrorZobristHash(zobHash, state) { let hash = 0; let heroes = state.heroes; for(let i = 1; i <= HRD_GAME_ROW; i++) { for(let j = 1; j <= HRD_GAME_COL; j++) { let index = state.board[i][j] - 1; let type = (index >= 0 && index < heroes.length) ? heroes[index].type : 0; //(HRD_GAME_COL - 1) - (j - 1)) 坐标变换 hash ^= zobHash.get(i - 1,HRD_GAME_COL - j,type); } } return hash; } 程序测试 设计了三个棋局,测试目标:同一个棋局的Zobrist哈希与镜像哈希相等,镜像棋局的Zobrist哈希与其镜像哈希相等。 var zobHash = new Zobrist() var gameState = new HrdGameState() var gameStateL = new HrdGameState() var gameStateR = new HrdGameState() 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 hsl = [ new Warrior(WARRIOR_TYPE.HT_BOX,0,0), new Warrior(WARRIOR_TYPE.HT_VBAR,2,0), new Warrior(WARRIOR_TYPE.HT_VBAR,3,0), new Warrior(WARRIOR_TYPE.HT_HBAR,0,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,2), new Warrior(WARRIOR_TYPE.HT_VBAR,3,2), new Warrior(WARRIOR_TYPE.HT_VBAR,0,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,4), new Warrior(WARRIOR_TYPE.HT_BLOCK,3,4) ] var hsr = [ new Warrior(WARRIOR_TYPE.HT_VBAR,0,0), new Warrior(WARRIOR_TYPE.HT_VBAR,1,0), new Warrior(WARRIOR_TYPE.HT_BOX,2,0), new Warrior(WARRIOR_TYPE.HT_VBAR,0,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,2), new Warrior(WARRIOR_TYPE.HT_HBAR,2,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3), new Warrior(WARRIOR_TYPE.HT_VBAR,3,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,0,4), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,4) ] gameState.initState(hs) gameStateL.initState(hsl) gameStateR.initState(hsr) console.dir(getZobristHash(zobHash,gameStateL) + '--' + getMirrorZobristHash(zobHash,gameStateR)) //两值相等 完整代码 代码托管在开源中国,其中的hyd.js即华容道解法。 https://git.oschina.net/zhoutk/test.git 小结 尽量使用面向对象的思想来解决问题,让数据和操作绑定在一起,努力使代码容易看懂。
三部曲下:http://www.thinksaas.cn/topics/0/501/501098.html,https://segmentfault.com/a/1190000005043566
华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的思想来进行封装,整个过程将分成几个部分记录下来,今天是最后一部分,棋局的广度搜索。 广度搜索 棋局的搜索空间是一个树状关系空间,广度优先搜索能够首先找到最优解,因为首先找到的解深度是最浅的。 游戏定义 我们对算法的要求是:给定一个华容道游戏的开局布局,可以得到这个开局的所有解决方法以及相应的武将移动步骤,要求算法具有能用性,能处理任何一种开局的华容道游戏。 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) ] 四个解: 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://git.oschina.net/zhoutk/test.git
git★ https://git.oschina.net/zhoutk/test.git
最后这个大大似乎已经不公开次项目代码了 https://git.oschina.net/zhoutk
注册开源中国git https://git.oschina.net/