JavaScript实现的贪吃蛇算法及其研究(二)

内容接上一章JavaScript实现的贪吃蛇算法及其研究(一).

现在我们的Head类是这样的:

function Head(){  
    this.x=9;
    this.y=9;
    this.direction=undefined;
    this.move=function(){};
    this.eat=function(){};
    this.hitCheck=function(){};
    this.eatSelfCheck=function(){};
}

其中eat,hitCheck,eatSelfCheck不会被外部访问到,所以我们将这三个方法转为静态方法,并且这三个方法都用来判断坐标是否重合(蛇头与食物是否重合,蛇头与墙壁是否重合,蛇头与蛇身是否重合),于是给这三个方法都添加一对坐标作为参数.

经过修改后,新的Head类变成了这样:

function Head(){  
    this.x=9;
    this.y=9;
    this.direction=undefined;
    this.move=function(){};
    function eat(x,y){

    }
    function hitCheck(x,y){

    }
    function eatSelfCheck(x,y){

    }
}

eat方法

eat方法用来判断蛇头是否与食物在坐标上重合,上一章中食物的坐标为(World.Food.x,World.Food.y).

于是我们用以下代码来判断是否eat,并将结果以布尔值进行返回.

function eat(x,y){  
    if(x==World.Food.x && y==World.Food.y){
        return true;
    }else{
        return false;
    }
}

hitCheck方法

hitCheck方法用来判断蛇头是否与墙壁重合(即撞墙或超出世界范围),上一章中世界范围被记录在World.Size的两个属性中.

用以下代码判断是否撞墙并以布尔值返回结果.

function hitCheck(x,y){  
    if(x<0 || y<0 || x==World.Size.width || y==World.Size.height){
        return true;
    }else{
        return false;
    }
}

eatSelfCheck方法

eatSelfCheck方法用于判断蛇头是否与蛇身重合(即相撞),上一章中Body.part是保存蛇身数据的数组,我们暂定蛇身数组中的元素以{x:value,y:value}的格式存储,编写循环进行判断.

function eatSelfCheck(x,y){  
    var part=self.body.part;
    for(var i=part.length;i--;){
        if(x==part[i].x && y==part[i].y){
            return true;
        }
    }
    return false;
}

这段代码中用到了self.body.part,其中self是Snake类的this指针,我们需要在Snake类中加上self的赋值:

var self=this;  

move方法

move方法不像前面三个方法那么好写,因为move方法涉及到以下几个方面:

  • 蛇头的移动
  • 通知蛇身移动
  • 对移动结果进行处理

我们让move方法接受一个direction参数,用来决定蛇头移动的方向,为了容易区分direction的值,我们创建一组Direction常量:

var Direction={  
    up:1,
    right:2,
    down:-1,
    left:-2
};

其中up和down,right和left互为相反数,这是为了方便判断蛇头移动的方向是否正确(无法往回走).

考虑到Direction可能用在多个地方,所以把Direction的代码移到全局中.

判断方向是否正确的代码如下,可以有效的防止蛇调头:

if(direction+this.direction==0){  
}

然后我们要保存这次蛇头移动的方向,并修改蛇头的坐标.

this.direction=direction;  
switch(direction){  
    case Direction.up:
        this.y--;
        break;
    case Direction.right:
        this.x++;
        break;
    case Direction.down:
        this.y++;
        break;
    case Direction.left:
        this.x--;
}

由于蛇头的方向即使错误也要进行移动(朝原方向运动),在刚才那段判断方向的代码中加入一行代码修改direction参数.

direction=this.direction;  

此时move方法应该是这样的:

this.move=function(direction){  
    if(direction+this.direction==0){
        direction=this.direction;
    }
    this.direction=direction;
    switch(direction){
        case Direction.up:
            this.y--;
            break;
        case Direction.right:
            this.x++;
            break;
        case Direction.down:
            this.y++;
            break;
        case Direction.left:
            this.x--;
    }
};

接下来完成对移动结果的处理,调用eat,hitCheck,eatSelfCheck这三个方法:

if(eat(this.x,this.y)){  
    World.Food.generate();
    self.body.increase();
}else    if(hitCheck(this.x,this.y) || eatSelfCheck(this.x,this.y)){

}else{
    self.body.move();
}

这段代码的逻辑首先判断是否吃到食物,如果吃到,则重新生成食物并且增加蛇身长度,否则检验是否撞墙和自食,如果都没有发生,就通知蛇身进行一次移动.

这里实际上有些混乱,在这段代码之前,我们已经对Head的属性进行了修改,调用self.body.increase和self.body.move时这两个方法不知道原先的坐标,导致蛇身无法跟随蛇头运动,为了解决这一问题,我们给这两个方法传递一个名为head的参数,并且在move之前保存原先的坐标.

最终move方法的代码如下:

this.move=function(direction){  
    var head={
        x:this.x,
        y:this.y
    };
    if(direction+this.direction==0){
        direction=this.direction;
    }
    this.direction=direction;
    switch(direction){
        case Direction.up:
            this.y--;
            break;
        case Direction.right:
            this.x++;
            break;
        case Direction.down:
            this.y++;
            break;
        case Direction.left:
            this.x--;
    }
    if(eat(this.x,this.y)){
        self.body.increase(head);
        World.Food.generate();
    }else   if(hitCheck(this.x,this.y) || eatSelfCheck(this.x,this.y)){

    }else{
        self.body.move(head);
    }
};

Next

现在,我们的代码是这样的,Head类已经大致完成.

var World={  
    Size:{
        width:20,
        height:20
    }
    Food:{
        x:undefined,
        y:undefined,
        generate:function(){}
    }
}

var Direction={  
    up:1,
    right:2,
    down:-1,
    left:-2
};

function Snake(){  
    var self=this;

    function Head(){
        this.x=9;
        this.y=9;
        this.direction=undefined;
        this.move=function(direction){
            var head={
                x:this.x,
                y:this.y
            };
            if(direction+this.direction==0){
                direction=this.direction;
            }
            this.direction=direction;
            switch(direction){
                case Direction.up:
                    this.y--;
                    break;
                case Direction.right:
                    this.x++;
                    break;
                case Direction.down:
                    this.y++;
                    break;
                case Direction.left:
                    this.x--;
            }
            if(eat(this.x,this.y)){
                self.body.increase(head);
                World.Food.generate();
            }else   if(hitCheck(this.x,this.y) || eatSelfCheck(this.x,this.y)){

            }else{
                self.body.move(head);
            }
        };
        function eat(x,y){
            if(x==World.Food.x && y==World.Food.y){
                return true;
            }else{
                return false;
            }
        }
        function hitCheck(x,y){
            if(x<0 || y<0 || x==World.Size.width || y==World.Size.height){
                return true;
            }else{
                return false;
            }
        }
        function eatSelfCheck(x,y){
            var part=self.body.part;
            for(var i=part.length;i--;){
                if(x==part[i].x && y==part[i].y){
                    return true;
                }
            }
            return false;
        }
    }

    function Body(){
        this.part=[];
        this.move=function(){};
        this.increase=function(){};
    }

    this.body=new Body();
    this.head=new Head();
}
var snake=new Snake();  

在下一章中,将完成Body类.

JavaScript实现的贪吃蛇算法及其研究(三)