使用JavaScript写的汉诺塔小游戏

Posted Montai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用JavaScript写的汉诺塔小游戏相关的知识,希望对你有一定的参考价值。

汉诺塔有些,是将A柱子上的盘子,借助B柱子,移动到C柱子,移动过程中要求,小盘子,必须放在大盘子上面。

移动过程是采用递归调用的方式。

 

程序运行界面:如下图:

 

 

 

 

 

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>汉诺塔</title>
</head>
<body>

<h1>  汉诺塔 </h1>

<canvas id = "canvas" width = 400, height = 300">


</canvas>
<div>
<button id = "ps" onclick=" pStep()"> <=== Preview Step </button>
<button id = "ns" onclick= "nStep()"> Next Step  ===> </button>
</div>
<script>

    function Stack(name) {
        this.dataStore = [];
        this.top = 0;
        this.name = name;

        this.push = push;
        this.pop = pop;
        this.peek = peek;
        this.clear = clear;
        this.length = length;
        this.get = get;
        this.drawDish = drawDish;
        this.myCloneobj= myCloneobj;
    }

    function push(ele) {
        this.dataStore[this.top ++] = ele;
    }

    function peek() {
        return this.dataStore[this.top - 1];
    }

    function pop() {
        return this.dataStore[-- this.top];
    }

    function clear() {
        this.top = 0;
    }

    function length() {
        return this.top;
    }

    function get(n){
        return this.dataStore[n-1];
    }

    function Block(size, location){
        this.size = size;
        this.location = location;
        this.setLocation = setLocation;
        this.getSize = getSize;
    }

    function setLocation(location)
    {
        this.location = location;
    }

    function getSize()
    {
        return this.size;
    }

    function drawDish(){
        var canvas = document.getElementById(\'canvas\');
        if (canvas.getContext) {
            var ctx = canvas.getContext(\'2d\');
            var width, height, layer, pole, xPos, yPos;
            //x基准点125, y基准点205
            //画一个柱子
            pole = this.name;
            n = this.length();
            b = new Block();
            console.log(" draw pole %d, length %d", pole , n);
            while (n>0){
                b = this.get(n);
                var size = b.size;
                width = (size+2)*10;
                height = 20;
                layer = n - 1;
                xPos = 125 + pole * 75 - width/2;
                yPos = 205 - layer*25 - height/2 ;

                roundedRect(ctx, xPos, yPos, width, height, 15);
                ctx.fillStyle = "red";
                ctx.fill();
                n--;
            }
         }
    }

    function myCloneobj()
    {
        o = new Stack(this.name);
        console.log("clone pole %d", this.name);
        n = this.length();
        var i=1;
        while (i <= n)
        {
            var bt = this.get(i);
            var size = bt.size;
            var location = bt.location;
            b = new Block(size, location);
            o.push(b);
            console.log("insert new b size %d, location %d", size, location);
            i=i+1;
        }
        return o;
    }

    //深复制对象方法
    function cloneObj(obj) {
        //1
        var newJsonObj = {};
        newJsonObj = JSON.parse(JSON.stringify(obj));
        //2
        for (items in obj) {
            if (typeof obj[items] == "function" || typeof obj[items] == "undefined" || obj[items] instanceof RegExp) {
                newJsonObj[items] = obj[items];
            }
        }
        //3
        var newObj = new obj.constructor;
        for (items in newJsonObj) {
            newObj[items] = newJsonObj[items]
        }
        return newObj;
    }

    //深复制对象方法
    var cloneObj2 = function (obj) {
        var newObj = {};
        if (obj instanceof Array) {
            newObj = [];
        }
        for (var key in obj) {
            var val = obj[key];
            //newObj[key] = typeof val === \'object\' ? arguments.callee(val) : val; //arguments.callee 在哪一个函数中运行,它就代表哪个函数, 一般用在匿名函数中。
            newObj[key] = typeof val === \'object\' ? cloneObj2(val): val;
        }
        return newObj;
    };

    function roundedRect(ctx, x, y, width, height, radius){
          ctx.beginPath();
          ctx.moveTo(x, y + radius);
          ctx.lineTo(x, y + height - radius);
          ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
          ctx.lineTo(x + width - radius, y + height);
          ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
          ctx.lineTo(x + width, y + radius);
          ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
          ctx.lineTo(x + radius, y);
          ctx.quadraticCurveTo(x, y, x, y + radius);
          ctx.stroke();
    }

    function drawRect(){
          var canvas = document.getElementById(\'canvas\');
          if (canvas.getContext) {
            var ctx = canvas.getContext(\'2d\');
            ctx.fillStyle = "black";
            //画边框
            ctx.strokeRect(10, 10, 380, 280);
            //画左柱子
            ctx.fillRect(120, 100, 10, 120);
            ctx.fillRect(100, 220, 50, 20);
            //画中柱子
            ctx.fillRect(195, 100, 10, 120);
            ctx.fillRect(175, 220, 50, 20);
            //画右柱子
            ctx.fillRect(270, 100, 10, 120);
            ctx.fillRect(250, 220, 50, 20);

            //ctx.clearRect(125, 25, 10, 200);
            //ctx.strokeRect(50, 50, 50, 50);
          }
    }

    function drawText(str) {
        var canvas = document.getElementById(\'canvas\');
        if (canvas.getContext){
            var ctx = canvas.getContext(\'2d\');
            ctx.font = "16px serif";
            ctx.fillText(str, 20, 40);
        }
    }

    function clear(){
        var canvas = document.getElementById(\'canvas\');
            if (canvas.getContext) {
                  var ctx = canvas.getContext(\'2d\');
                  ctx.clearRect(20,20,360,260);
                  ctx.fillStyle = "black";
            }
    }

    function moveBlock(s1,s2){
        if(s1.length() > 0){
            if( (s2.length() == 0) || ((s2.length() > 0) && (s2.peek().size > s1.peek().size))){
                b = s1.pop();
                b.location = s2.name;
                s2.push(b);
                console.log("block %d move form %d to %d", b.size, s1.name, s2.name);
                //如果是简单变量,会直接创建并复制,如果是类变量,特别是有复杂结构的类变量,只是复制一个地址。
                process.push(s1.name);
                process.push(s2.name);
            }
        }
    }

    function hanoi(n, a, b, c){
        if (n == 1){
            moveBlock(a, c);
        }
        else{
            hanoi(n - 1, a, c, b);
            moveBlock(a, c);
            hanoi(n - 1, b, a, c);
        }
    }

    var step = 0;


    function draw(a, b, c) {
        var str = " Hanota process ";
        var s = step + 1;
        str = str.concat( s.toString(), " steps");
        clear();
        drawText(str);
        drawRect();
        a.drawDish();
        b.drawDish();
        c.drawDish();
    }

    var level = 4;

    var process = [];

    var leftPole = new Stack(0);//0 - leftPole
    var middlePole = new Stack(1);//1 - middlePole
    var rightPole = new Stack(2);//2 - rightPole

    var i = level;
    while (i>0){
        var block = new Block(i, 0);
        leftPole.push(block);
        i--;
    }

    var leftPolebk = leftPole.myCloneobj();
    var middlePolebk = middlePole.myCloneobj();
    var rightPolebk = rightPole.myCloneobj();

    draw(leftPolebk, middlePolebk, rightPolebk);

    hanoi(level, leftPole, middlePole, rightPole);


    var n = level;
    var maxStep = 1;
    while (n>0) {
        maxStep = maxStep*2;
        n=n-1;
    }
    maxStep = maxStep - 1;

    function pStep()
    {
        step = step - 1;//退回上一个节点

        if (step >= 0){
            var p1 = process[step*2];
            var p2 = process[step*2+1];
            console.log(" step %d move from %d to %d ",step, p2, p1);
            if (p2 == 0)
                var b = leftPolebk.pop();
            else if (p2==1)
                b = middlePolebk.pop();
            else if (p2==2)
                b = rightPolebk.pop();
            else
                alert("wrong1");

            if (p1 == 0)
                leftPolebk.push(b);
            else if (p1 == 1)
                middlePolebk.push(b);
            else if (p1 == 2)
                rightPolebk.push(b);
            else
                alert("wrong2");

            draw(leftPolebk, middlePolebk, rightPolebk);
        }
        else
        {
            step=0
        }
    }

     function nStep()
    {
        if (step <= maxStep){
            var p1 = process[step*2];
            var p2 = process[step*2+1];
            console.log(" step %d move from %d to %d ",step, p1, p2);
            if (p1 == 0)
                var b = leftPolebk.pop();
            else if (p1==1)
                b = middlePolebk.pop();
            else if (p1==2)
                b = rightPolebk.pop();
            else
                return;

            if (p2 == 0)
                leftPolebk.push(b);
            else if (p2 == 1)
                middlePolebk.push(b);
            else if (p2 == 2)
                rightPolebk.push(b);
            else
                return;

            draw(leftPolebk, middlePolebk, rightPolebk);

            step = step + 1;//指向下一个节点
        }
    }

</script>
</body>
</html>

 

代码说明:

1.  Canvas画布,画塔,盘子和移动说明文字

2. 用Stack类,存放柱子信息,类是用堆栈来实现的, 类的名称属性,表示是哪一根柱子,左,中,还是右?

    function Stack(name) {
        this.dataStore = [];
        this.top = 0;
        this.name = name;

3. 用Block类,存放盘子信息,size表示盘子的大小,Location标明盘子在那个柱子上。

  function Block(size, location){
        this.size = size;
        this.location = location;
        this.setLocation = setLocation;
        this.getSize = getSize;
    }

4. 初始化时候,生成了3个盘子,3个柱子,并把3个盘子push到最左边的柱子上。

5. 调用画图函数,画出背景信息,三个柱子信息。

6. 调用递归函数,实现盘子移动

  function hanoi(n, a, b, c){
        if (n == 1){
            moveBlock(a, c);
        }
        else{
            hanoi(n - 1, a, c, b);
            moveBlock(a, c);
            hanoi(n - 1, b, a, c);
        }
    }  

程序本身没有什么好说的,这里需要说明的是如何将移动的步骤保存下来。
尝试了三种方法:
  1、在递归调用过程中,复制对象到一个数组中
  2、在递归调用中,将程序停下来
  3. 保存移动过程信息,到一个数组中

由于JavaScript对于对象的复制,对于Obj,只复制一个链接。这样导致的结果,复制下来的对象,在数组中都是一样的。
后来采用深度复制的方式,将obj中的obj,全部new来,但是这种方法,对于静态操作有效,在递归迭代过程中,第一层的还能复制,但是第二次以后的,都复制不了了。

在递归调用过程中,尝试alert, promote, sleep方法,暂停程序的执行。但是这种方法,只对异步的操作有效(你给我消息,我相应),但是对于这种代码执行迭代过程,是卡不住的。

最后只好采用一个数组,只记录移动盘子的原和目的地址信息。

7. 移动过程演示

因为递归调用过程,是不能暂停程序的。用了数组将盘子移动的过程录制下来。
在演示过程中,读取录制的移动过程数据,让操作过程再执行一遍。

这里克隆了三个对象,用于操作过程的演示。
    var leftPolebk = leftPole.myCloneobj();
    var middlePolebk = middlePole.myCloneobj();
    var rightPolebk = rightPole.myCloneobj();
8. 

以上是关于使用JavaScript写的汉诺塔小游戏的主要内容,如果未能解决你的问题,请参考以下文章

汉诺塔游戏规则

JavaScript递归函数解“汉诺塔”

汉诺塔递归

河内塔游戏

汉诺塔游戏

《C#零基础入门之百识百例》(五十)嵌套类和嵌套方法 -- 汉诺塔游戏