使用js求解并展示华容道的步骤

使用js求解华容道的步骤

概述

用一个大小为20的数组表示地图。
地图中有两个空格,空格可以上下左右四个方向。每个地图有八种移动情况。
初始化一个队列,放进去初始的地图。
从队列取出第一个地图,然后尝试这八种情况,把可行的放到队列里。依此类推,直到成功。
运行结果

代码

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>华容道练习20201228</title>
</head>
<style>
    td{
        padding: 0.4em;
    }
    table{
        float: left;
        margin: 4px;
    }
</style>
<body>
<div id="msgDiv">华容道</div>
<br/>
<button id="btn1" onclick="getBestPath()">求解</button>
<span id="info" style="display: none;">用时:<span id="useTime"></span>ms, 步骤:<span id="stepNum"></span><span style="opacity: 0.6;float: right;"> &nbsp; (尝试了:<span id="count"></span>次)</span></span>
<br/>
<div style="clear: both;"></div>
<script type="text/javascript">
/*
地图宽4,高5; 用连续的20个数表示地图; 
        0000 代表当前位置的点与相邻的点没有关系,
        1000 代表与上边的点连接
        0100 代表与右边的点连接
        .... 依次类推,上右下左
表示点的数字
    [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(v=>v.toString(2))
    [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(v=>""+((v>>3)&1)+((v>>2)&1)+((v>>1)&1)+((v>>0)&1))
    { 0: "0000", 1: "0001", 2: "0010", 3: "0011",
      4: "0100", 5: "0101", 6: "0110", 7: "0111",
      8: "1000", 9: "1001", 10: "1010",11: "1011",
      12: "1100",13: "1101",14: "1110",15: "1111" }
偏移量:
    [0,1,2,3].map(v=>{ return {x:v&1,y:(v&2)>>1} }) // 
表示点的数字 与 它对应的 相邻的点的偏移量们
    JSON.stringify([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(v=>{
        var arr=[];
        if((v>>3)&1) arr.push([0,-1]);
        if((v>>2)&1) arr.push([1,0]);
        if((v>>1)&1) arr.push([0,1]);
        if((v>>0)&1) arr.push([-1,0]);
        return arr;
    })) // const NEAR_SKEWS = ...
    JSON.stringify([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(v=>{
        var arr=[];
        if((v>>3)&1) arr.push([0,-1]);
        if((v>>2)&1) arr.push([1,0]);
        if((v>>1)&1) arr.push([0,1]);
        if((v>>0)&1) arr.push([-1,0]);
        if(arr.length>2) return null; // 不处理复杂的
        if(arr.length===2) arr.push([arr[0][0]+arr[0][1],arr[1][0]+arr[1][1]]);
        return arr;
    })) // const NEAR_POINT_SKEWS

*/
    
	console.log("华容道练习");

    /**
     * 定义一些常量,也可以定义到 Cmp 中
     */
    const EMPS_LEN = 2, EMPS_LEN_HALF = 1; // 常量,两个空格, 2的一半是1
    const WIDTH = 4, HEIGHT = 5; // 地图大小
    const CHAR_NUM_MP = {"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"a":10,"b":11,"c":12,"d":13,"e":14,"f":15}; // 字符 : 数字
    const NUM_CHAR_ARR = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];
    // 根据点的数字,确定相邻的点基于当前的点的坐标的偏移量
    const NEAR_SKEWS = [[],[[-1,0]],[[0,1]],[[0,1],[-1,0]],[[1,0]],[[1,0],[-1,0]],[[1,0],[0,1]],[[1,0],[0,1],[-1,0]],[[0,-1]],[[0,-1],[-1,0]],[[0,-1],[0,1]],[[0,-1],[0,1],[-1,0]],[[0,-1],[1,0]],[[0,-1],[1,0],[-1,0]],[[0,-1],[1,0],[0,1]],[[0,-1],[1,0],[0,1],[-1,0]]]; 
    // 针对当前这个地图做的一个特殊的数组,存放了点的数字 对应着的物体中的所有点
    const NEAR_POINT_SKEWS = [[],[[-1,0]],[[0,1]],[[0,1],[-1,0],[1,-1]],[[1,0]],[[1,0],[-1,0],[1,-1]],[[1,0],[0,1],[1,1]],[null],[[0,-1]],[[0,-1],[-1,0],[-1,-1]],[[0,-1],[0,1],[-1,1]],[null],[[0,-1],[1,0],[-1,1]],[null],[null],[null]];
    const EMP_CHAR = 'f', EMP_NUM = 15; // 表示空白的字符,常用。 1111 。
    const TOP_NUM = 8, RIGHT_NUM = 4, BOTTOM_NUM = 2, LEFT_NUM = 1; // 上右下左 // 1000, 0100, 0010, 0001
    // 下面四个是一组 ,(为了遍历空格的四周而服务)
    const SKEWS_XY = [[0,-1],[1,0],[0,1],[-1,0]]; // 偏移量,上右下左
    const SKEWS_NUM = [TOP_NUM, RIGHT_NUM, BOTTOM_NUM, LEFT_NUM]; // 标记物体方向,上右下左
    const MOVE_XY = [[0,1],[-1,0],[0,-1],[1,0]]; // 偏移量,下左上右
    // const MOVE_NUM = [BOTTOM_NUM, LEFT_NUM , TOP_NUM, RIGHT_NUM]; // 标记移动方向,下左上右
    var numToChar = function(num){return NUM_CHAR_ARR[num];};
	// 一个地图状态 
	function Cmp(mpNums,emps,parentMp,count){ // 地图数组,空格的位置,父Cmp,计数
        this.mpStr = mpNums.map(numToChar).join("");
        this.mpNums = mpNums;
        // [(x1,y1),(x2,y2)] 表示空格的位置 // 注意:不能修改内部的数组里面的值
        this.emps = emps; 
		// 最小的步数
		this.count = count;
		// count最小的父状态
        this.parentMp = parentMp;
        // this.childMps = null; // 记录子mps,如果可行的话 [[子mps],] 子mps按照count从小到大排列
    }
    // 是否符合条件
    Cmp.prototype.isOk = function(){
        // return this.mpStr[1+4*3]=="0110" && this.mpStr[2+4*3]=="0011" && this.mpStr[1+4*4]=="1100" && this.mpStr[2+4*4]=="1001";
        return this.mpStr[13]=="6";//只用判断这一个就行;  && this.mpStr[14]=="3" && this.mpStr[17]=="c" && this.mpStr[18]=="9";
    }
    // 是否对称
    Cmp.prototype.is对称 = (function(){
        var relMp = {
            "6":"3", "3":"6", 
            "c":"9", "9":"c", 
            "4":"1", "1":"4"
        };
        return function (){
            return this.mpStr[0]===this.mpStr[3] && 
                    this.mpStr[4]===this.mpStr[7] && 
                    this.mpStr[8]===this.mpStr[11] && 
                    this.mpStr[12]===this.mpStr[15] && 
                    this.mpStr[16]===this.mpStr[19] && 
                    (this.mpStr[1]===this.mpStr[2] || this.mpStr[1]===relMp[this.mpStr[2]]) && 
                    (this.mpStr[5]===this.mpStr[6] || this.mpStr[5]===relMp[this.mpStr[6]]) && 
                    (this.mpStr[9]===this.mpStr[10] || this.mpStr[9]===relMp[this.mpStr[10]]) && 
                    (this.mpStr[13]===this.mpStr[14] || this.mpStr[13]===relMp[this.mpStr[14]]) && 
                    (this.mpStr[17]===this.mpStr[18] || this.mpStr[17]===relMp[this.mpStr[18]]);
        }
    })();
    // 获取地图中的点信息
    Cmp.prototype.getCharAt = function(x,y){
        if(x===-1||x===WIDTH||y===-1||y===HEIGHT)return undefined;
        return this.mpStr[ x + y*WIDTH ];
    };
    Cmp.prototype.getIntAt = function(x,y){
        if(x===-1||x===WIDTH||y===-1||y===HEIGHT)return undefined; // 必须要加判断,不然后出错
        return this.mpNums[ x + y*WIDTH ];
    };
    // 拷贝数组
    Cmp.prototype.copyNums = function(){
        return this.mpNums.slice();
    }
    Cmp.prototype.copyEmps = function(){
        return this.emps.slice();
    }
    // 将地图转化为表格
    Cmp.prototype.toDomTable = function(){
        var table = document.createElement("table"), tr, td;
        table.border=1; table.cellSpacing=1;
        var x,y,tNum;
        var pmp=this.parentMp;
        for(y=0;y<HEIGHT;y++){
            tr = document.createElement("tr");
            for(x=0;x<WIDTH;x++){
                td = document.createElement("td");
                tNum = this.getIntAt(x,y);
                if(pmp!==null){
                    if(pmp.getIntAt(x,y) != tNum) td.style.backgroundColor="gold";
                }
                if(tNum === EMP_NUM){
                    td.style.border         = "1px solid white";
                } else {
                    td.style.borderTop      = (tNum & TOP_NUM)      ? "1px solid white" : "1px solid black";
                    td.style.borderRight    = (tNum & RIGHT_NUM)    ? "1px solid white" : "1px solid black";
                    td.style.borderBottom   = (tNum & BOTTOM_NUM)   ? "1px solid white" : "1px solid black";
                    td.style.borderLeft     = (tNum & LEFT_NUM)     ? "1px solid white" : "1px solid black";
                }
                tr.appendChild(td);
            }
            table.appendChild(tr);
        }
        return table;
    }
    // 获取下一步的地图;第i个空格,空格的移动方向为 j
    Cmp.prototype.next = function(i,j){ // 如果无法移动,返回null
        var cmp2 = null; // 下一步地图
        var x       =   this.emps[i][0], 
            y       =   this.emps[i][1]; // 空格的坐标
        var sx      =   SKEWS_XY[j][0], 
            sy      =   SKEWS_XY[j][1]; // 相对于空格的位置
        var sNum    =   SKEWS_NUM[j]; // 相对于空格的位置
        var px      =   x + sx, 
            py      =   y + sy; // 要移动的点的坐标
        var pNum = this.getIntAt(px,py); // 要移动的点的num
        if( pNum === undefined ) return cmp2; // 越界了
        var aNum,bNum,cNum,dNum; // 临时变量
        // 判断能否移动成功
        if(pNum !== EMP_NUM){ // 不能挨着空格,因为空格不能移动
            var tNums = this.copyNums(); // 地图Nums的拷贝
            var tnps = NEAR_POINT_SKEWS[pNum]; // 点相邻的点们
            var tEmps = this.copyEmps(); // 点坐标的浅拷贝
            if(pNum === 0){ // 挨着小兵
                tNums[x+y*WIDTH]=pNum; // 直接交换
                tNums[px+py*WIDTH]=EMP_NUM;
                tEmps[i] = [px,py];// 更新空格的位置
                cmp2 = new Cmp(tNums,tEmps,this,this.count+1);
            } else if(pNum === sNum){ // 挨着条状的,且跟移动方向一条直线的,可以移动
                aNum = x+y*WIDTH; // 空格
                bNum = px+py*WIDTH; // 点
                cNum = (px+sx) + (py+sy) * WIDTH; // 另一个点
                tNums[aNum]=pNum;
                tNums[bNum]=tNums[cNum];
                tNums[cNum]=EMP_NUM;
                tEmps[i] = [(px+sx),(py+sy)];// 更新空格的位置 // 这儿其实不用new一个数组
                cmp2 = new Cmp(tNums,tEmps,this,this.count+1);
            } else if(tnps.length===1){ // 挨着条状的,且跟移动方向不在一条直线,
                if(this.getIntAt(x+tnps[0][0],y+tnps[0][1]) === EMP_NUM){ // 判断空格旁边是否也是空格
                    // 可以移动
                    aNum = x+y*WIDTH; // 当前空格
                    bNum = px+py*WIDTH; // 挨着的点
                    cNum = (px+tnps[0][0]) + (py+tnps[0][1]) * WIDTH; // 另一个点
                    dNum = (x+tnps[0][0]) + (y+tnps[0][1]) * WIDTH; // 另一个空格
                    tNums[aNum]=pNum;
                    tNums[bNum]=EMP_NUM;
                    tNums[dNum]=tNums[cNum];
                    tNums[cNum]=EMP_NUM;
                    tEmps[i] = [px,py]; // 更新空格的位置
                    tEmps[1-i] = [(px+tnps[0][0]),(py+tnps[0][1])]; // 另一个空格
                    cmp2 = new Cmp(tNums,tEmps,this,this.count+1);
                }
            }else{ // 曹操 //这儿的 tnps.length === 3
                var x2 = this.emps[1-i][0]; // 另一个空格挨着的点
                var y2 = this.emps[1-i][1];
                if((aNum=this.getIntAt(x2+sx,y2+sy))!==undefined && NEAR_POINT_SKEWS[aNum].length===3){ // 判断另一个空格是否也挨着曹操
                    aNum = x+y*WIDTH; // 空格
                    bNum = px+py*WIDTH; // 点
                    cNum = (px+sx) + (py+sy) * WIDTH; // 另一个点
                    tNums[aNum]=tNums[bNum];
                    tNums[bNum]=tNums[cNum];
                    tNums[cNum]=EMP_NUM;
                    tEmps[i] = [(px+sx),(py+sy)];// 更新空格的位置 // 这儿其实不用new一个数组
                    
                    aNum = x2+y2*WIDTH; // 空格2
                    bNum = (x2+sx)+(y2+sy)*WIDTH; // 点2
                    cNum = (x2+sx+sx) + (y2+sy+sy) * WIDTH; // 另一个点2
                    tNums[aNum]=tNums[bNum];
                    tNums[bNum]=tNums[cNum];
                    tNums[cNum]=EMP_NUM;
                    tEmps[1-i] = [(x2+sx+sx),(y2+sy+sy)];// 更新空格的位置 // 这儿其实不用new一个数组
                    
                    cmp2 = new Cmp(tNums,tEmps,this,this.count+1);
                }
            }
        }
        return cmp2;
    }

	// 最开始的地图
	var mpNums = [
		"0010","0110","0011","0010", // 2632
		"1000","1100","1001","1000", // 8c98
		"0010","0100","0001","0010", // 2412
		"1000","0000","0000","1000", // 8008
		"0000","1111","1111","0000", // 0ff0
	].map(function(v){
		return parseInt(v,2);//.toString(16);
    }); // 26328c98241280080ff0
    var startCmp = new Cmp(mpNums,[[1,4],[2,4]],null,0);
    // 清空界面
	function clear(){
        var tables = document.getElementsByTagName("table");
        for(var i=tables.length-1;i>=0;i--){ // 清空table
            document.body.removeChild(tables[i]);
        }
    }
    // 打印地图
    function print(cmp){
        if(cmp) {
            var tt = cmp.toDomTable();
            document.body.appendChild(tt);
        }
    }
    function getBestPath(){
        document.getElementById("btn1").setAttribute("disabled",true);
        var time0 = new Date(), count = 0;

        var stateMp={},i=j=ij=0,DEATH_MP={},OK_MP={},PATH_MP={},mps=[startCmp],cmp,cmp2;
        while(mps.length){
            count++;
            i = ij>>2;
            j = ij&3;
            // 从队列里拿出来一个
            cmp = mps[0];
            cmp2 = cmp.next(i,j);
            if(cmp2 !== null){
                if(stateMp[cmp2.mpStr] === undefined){
                    stateMp[cmp2.mpStr] = PATH_MP;
                    cmp2.parentMp = cmp;
                    cmp2.count = cmp.count+1;
                    if(cmp2.isOk())break;
                    mps.push(cmp2);
                }
            }
            ij++;
            if(ij===8){
                ij=0;
                mps.shift();
            }
        }
        var time1 = new Date();
        document.getElementById("useTime").innerText = time1-time0;
        cmp = cmp2;
        mps.length = 0;
        mps.push(cmp);
        while(cmp=cmp.parentMp){
            mps.push(cmp);
        }
        document.getElementById("stepNum").innerText = mps.length;
        document.getElementById("count").innerText = count;
        document.getElementById("info").style.display = "inline";
        while(cmp=mps.pop()){
            print(cmp);
        }
    }

</script>
</body>
</html>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值