Bootstrap

原生js简单实现贪吃蛇

js写个简单的贪吃蛇案例,用于复习一下js的原型链呀还有闭包沙箱知识等.
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title>标题</title>
  <style>
    .map {
      width: 800px;
      height: 600px;
      background-color: #cccccc;
      position: relative;
      top: 0px;
      left: 0px;
    }
  </style>
</head>
<body>
<div class="map" id="map"></div>
<script src="Food.js"></script>
<script src="Snake.js"></script>
<script src="Game.js"></script>
<script>


  //1.获取地图对象
  var map1 = document.getElementById("map");


  //2.创建游戏控制器对象.
  var game1 = new Game(map1);
  //3.调用游戏控制器对象的开始方法.
  game1.start();
上面代码是贪吃蛇的静态页面,也是我们js入口函数位置.这里主要写了地图的样式.
//所有关于游戏逻辑的代码写在这里.
(function (window) {
  //声明一个全局变量that,用来记录游戏控制器对象.
  var that = null;

  //1.游戏控制器对象,所以也有构造函数.
  function Game(map){
    this.snake = new Snake();
    this.food = new Food();
    this.map = map;
    //对that赋值.
    that = this;
  }

  //2.游戏开始方法.
  Game.prototype.start = function () {
    //2.1 渲染出蛇和食物.
    this.snake.render(this.map);
    this.food.render(this.map);

    // //2.2 调用蛇移动的方法让蛇移动.
    // this.snake.move();
    // //2.3 蛇移动产生了新坐标,要重新渲染蛇.
    // this.snake.render(this.map);

    snakeAutoMove();

    bindKey();
  }


  //4.让蛇不停的动起来.
  function snakeAutoMove(){
    //让蛇不停的动起来,就是用计时器不停的去调用下面这两句话.
    var timerId = setInterval(function () {
      //为什么这里的this是window.
      //console.log(this);//window
      //console.log(this.snake); //undefined
      // //调用蛇移动的方法让蛇移动.
      // this.snake.move(); //报错.
      // //蛇移动产生了新坐标,要重新渲染蛇.
      // this.snake.render(this.map);


      //--------------------------------------------------
      //如何解决这个问题? 让这个回调函数中的this从window改成游戏控制器对象.
      //调用蛇移动的方法让蛇移动.
      this.snake.move(this.food,this.map);
      //蛇移动产生了新坐标,要重新渲染蛇.
      this.snake.render(this.map);


      //---------------------------------------------------
      //蛇移动有可能超出了边界.
      var snakeHeadX = this.snake.body[0].x * this.snake.width; //找到蛇头的坐标x.
      var snakeHeadY = this.snake.body[0].y * this.snake.height; //找到了蛇头的坐标y.
      if(snakeHeadX < 0 || snakeHeadY < 0 || snakeHeadX >= this.map.offsetWidth || snakeHeadY >= this.map.offsetHeight ){
        //超出边界,停止计时器停止游戏.
        alert("Game Over!");
        clearInterval(timerId);
      }


    }.bind(that),400);
  }


  //5.声明一个方法:让蛇根据键盘按键来改变方向移动.
  function bindKey(){
    window.onkeydown = function (e) {
      e = e || window.event;
      //获取按的是那个键(按得是上键,还是下键,还是左键还是右键.)
      //console.log(e.keyCode); //左37  上38  右39  下40
      switch (e.keyCode){
        case 37:
          //改变蛇的方向为左.
          if(this.snake.direction != "right"){
            this.snake.direction = "left";
          }
          break;
        case 38:
          //改变蛇的方向为上.
          if(this.snake.direction != "bottom"){
            this.snake.direction = "top";
          }
          break;
        case 39:
          //改变蛇的方向为右.
          if(this.snake.direction != "left"){
            this.snake.direction = "right";
          }
          break;
        case 40:
          //改变蛇的方向为下.
          if(this.snake.direction != 'top'){
            this.snake.direction = "bottom";
          }
          break;
      }
    }.bind(that);
  }






  //3.把Game构造函数给暴露出去.
  window.Game = Game;
}(window));
    Game.js主要写我们游戏的主逻辑代码,这里我们写了游戏控制器对象的构造函数,同时创建Food,Snake,并传入Map,
    因为这三者就是我们游戏的三大要素,游戏逻辑都围绕这三者展开,写好我们的start方法,调用此方法就开始我们的游戏,
    还有一些别的方法,都跟另外两个js文件有关系,在这不赘述,仅对一些比较重要的部分做一些说明.
//所有的关于食物的代码,写在这个自执行函数里面.
(function (w){
  //用一个数组list存放食物.
  var list = [];
  //1.食物有宽高有背景色,有定位坐标的对象,有对象就有创建对象的构造函数.
  function Food(width,height,bgColor){
    this.width = width || 20;
    this.height = height || 20;
    this.bgColor = bgColor || "green"
  }
  //2.食物要显示的这些代码封装成一个函数,函数写在原型中: a.所有的食物都要显示,显示的方法共有的所以写在原型里 b.写在原型中的方法食物对象可以直接调用.
  Food.prototype.render = function (map) {
    //渲染新食物之前删掉老食物
    remove(map);

    //2.1 食物随机的坐标.
    //this.x = "随机的坐标"; //不能超出地图,还应该是蛇宽高的倍数.
    this.x = Math.floor(Math.random()*map.offsetWidth/this.width)*this.width;
    this.y = Math.floor(Math.random()*map.offsetHeight/this.height)*this.height;
    //2.2 创建一个div,让这个div拥有这个食物的所有的显示信息.
    var div1 = document.createElement("div");
    div1.style.position = "absolute"
    div1.style.left = this.x+"px";
    div1.style.top = this.y+"px";
    div1.style.width = this.width + "px";
    div1.style.height = this.height+"px";
    div1.style.backgroundColor = this.bgColor;
    //2.3 把这div添加到地图中.
    map.appendChild(div1);
    //2.4 把这个div保存到list数组中.
    list.push(div1);
  }


  //4.删除 显示老食物div的方法.
  function remove(map){
    //从map地图div中删除食物子div.
    for(var i = 0 ; i < list.length; i++){
       map.removeChild(list[i]);
    }
    //清空list
    list.length = 0;
  }


  //3.由于这个Food构造函数,在外面要用,所有把他暴露出去.
  w.Food = Food;


}(window));
Food.js主要是对Food对象的创建,还有对应的方法的撰写,主要用到Food的render渲染方法,因为每一次吃掉食物或者是初始化的
时候都要调用这个函数产生新的食物,产生新食物还要调用remove方法删去之前的旧食物.
//所有的关于蛇的代码写在这里.
(function (window) {
  //随机产生一个十六进制的颜色的函数.
  function getColorForRandom(){
    var arr = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];  //下标0-15
    var str = "#";
    //循环产生 6个 0-15的数.
    for(var i = 0 ; i < 6; i++){
      var num = Math.floor(Math.random()*16);
      //根据这个随机数,在arr数组中去取值.
      str += arr[num];
    }
    return str;   //"#985700"
  }




  //声明一个数组,用来保存表示蛇的每一节身体的div.
  var list = [];

  //1.蛇也有宽高,也有背景色,也有坐标还有方向,所以蛇应该也是一个对象,也有构造函数.
  function Snake(width,height,direction){
    this.width = width || 20;
    this.height = height || 20;
    this.direction = direction || "right";
    //用数组描述蛇的身体:因为蛇有很多节,还有吃食物长身体就可以直接push方法.
    //把每一节共同的宽高方向提炼出来,body里面就方法每一节不同的信息.比如坐标,还有背景色.
   this.body = [
      {x:3,y:1,bgColor:'red'},
      {x:2,y:1,bgColor:'green'},
      {x:1,y:1,bgColor:'purple'},
    ];
  }

  //2.蛇显示地图的函数写在原型中.
  //思路:把蛇的每一节身体都渲染出来,整条蛇不就出来了吗?
  Snake.prototype.render = function (map) {
    //渲染新蛇之前,删掉老蛇'
    remove(map);

    //2.1 用for循环遍历出每一节蛇身体
    for(var i = 0 ; i < this.body.length; i++){
      var snakeUnit = this.body[i];
      //2.2 创建div,让div拥有所有这一节蛇身体的显示信息
      var div1 = document.createElement("div");
      div1.style.position = "absolute";
      div1.style.left = snakeUnit.x * this.width + "px";
      div1.style.top = snakeUnit.y * this.height+"px";
      div1.style.width = this.width + "px";
      div1.style.height = this.height + "px";
      div1.style.backgroundColor = snakeUnit.bgColor;
      //2.3 把div添加进map地图中.
      map.appendChild(div1);
      //2.4 把div添加到数组list中.
      list.push(div1);
    }
  }

  //5.声明一个方法,移除老蛇.
  function remove(map){
    //就是找到老蛇的那些div,从map中移除.   : 老蛇那些div在渲染的时候已经保存在数组list中了.
    for(var i = 0 ; i < list.length; i++){
       map.removeChild(list[i]);
    }
    //清空数组list
    list.length = 0;
  }



  //4.蛇移动的方法写在原型中比较好,蛇对象直接可以调用.
  Snake.prototype.move = function (food,map) {
    //4.1 除了蛇头之外的蛇身体移动: 移动之后的坐标是他的上一节身体移动之前的坐标
    for(var i = this.body.length-1; i > 0; i--){
      this.body[i].x = this.body[i-1].x;
      this.body[i].y = this.body[i-1].y;
    }
    //4.2 蛇头移动: 根据方向来改变坐标.
    switch (this.direction){
      case "right":
        this.body[0].x++;
        break;
      case "left":
        this.body[0].x--;
        break;
      case "top":
        this.body[0].y--;
        break;
      case "bottom":
        this.body[0].y++;
        break;
    }

    //4.3 判断蛇有没有吃到食物: 蛇头的坐标和食物的坐标重合(x和y都相等).
    var snakeHeadX = this.body[0].x * this.width; //蛇头的x坐标.
    var snakeHeadY = this.body[0].y *this.height; //蛇头的y坐标.
    var foodX = food.x;//食物的x坐标.
    var foodY = food.y;//食物的y坐标.

    //在长身体之前,把原来的蛇尾巴取出来.
    var lastSnakeUnit = this.body[this.body.length-1];

    //判断:如果蛇头的坐标和食物的坐标重合,就表示蛇吃了食物.
    if(snakeHeadX == foodX && snakeHeadY == foodY){
      //吃了食物,应该长身体.
      this.body.push({
        x:lastSnakeUnit.x,
        y:lastSnakeUnit.y,
        bgColor:getColorForRandom()
      });
      //食物吃了,重新生成一个食物.
      food.render(map); //调用食物对象的render方法,重新的生成一个坐标再渲染,就感觉产生了一个新的食物.
    }

  }





  //3.把Snake构造函数给暴露出去.
  window.Snake = Snake;

}(window));
Snake.js创建蛇对象,这里渲染函数还有删除函数就不赘述了,move函数是游戏逻辑的重要部分,我们控制我们的蛇移动,
除了蛇头部分,可以把蛇的身体分成一节一节的,那么观察很容易得知,蛇每移动一步,后一节的部分,总会等于上一节的位置,
所以我们用一个循环就可以解决了,剩下蛇头的部分,我们可以通过switch方法来控制,这里面还有一个重要的逻辑就是,
贪吃蛇玩法的特点之一,就是蛇吃了食物之后会变成蛇身体的一部分(也就是蛇身变长),所以,move方法需要做一个检测,
也就是当蛇头的坐标等于食物的坐标时,即判定为吃到食物,此时,蛇身就会变长,观察得知,吃了食物后,蛇多出来的那一节,
就是蛇最后一节身体的部分.
;