ES6面向对象版贪吃蛇

   最近项目告一段落,难得空闲下来,就随手写了个贪吃蛇,作为一个立志要成为一个前端大牛的我,当然是js实现啦,哈哈哈。话不多说,贴上代码,欢迎同行批评指正。

演示地址:www.yirujet.club 

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Snake</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    canvas {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      margin: auto;
    }
  </style>
</head>
<body>
  <canvas id="container" width="500" height="500"></canvas>
</body>
</html>
<script>
  const container = document.getElementById('container');
  const DIRECTION_CODE = {
    ArrowUp: 'up',
    ArrowRight: 'right',
    ArrowDown: 'down',
    ArrowLeft: 'left'
  };
  const ROUTES = ['up', 'right', 'down', 'left'];
  const ROUTES_ENCODE = {
    '0': 'up',
    '-1': 'down',
    '1': 'left',
    '-2': 'right'
  };
  const ROUTES_DECODE = {
    up: 0,
    down: -1,
    left: 1,
    right: -2
  };
  const HORIZONTAL = 25;
  const VERTICAL = 25;
  const SPACING = 20;
  class Area {
    constructor(horizontal, vertical, spacing) {
      this.horizontal = horizontal;   //  长
      this.vertical = vertical;   //  宽
      this.spacing = spacing;   //  间距
      this.points = [];   //  地图所有点集合
      this.food_point = [];   //  食物位置
      this.snake_point = [];    //  蛇占据点
      this.init();
    }
    init() {
      this.points = Array.from({length: this.horizontal * this.vertical}).map((e, i) => e = [(i % this.horizontal) * this.spacing, ~~(i / this.vertical) * this.spacing]); 
      this.draw();  
    }
    createFoodPoint() {
      this.food_point = [~~(Math.random() * (this.horizontal - 1)) * this.spacing, ~~(Math.random() * (this.vertical - 1)) * this.spacing];
    }
    draw() {
      ctx.clearRect(0, 0, this.horizontal * this.spacing, this.vertical * this.spacing);
      ctx.strokeStyle = `rgb(0, 0, 0)`;
      ctx.lineWidth = .5;
      this.points.forEach(e => ctx.strokeRect(e[0], e[1], this.spacing, this.spacing));
    }
    detect() {
      if(this.head[0][0] < 0 || this.head[0][1] < 0 || this.head[0][0] >= this.horizontal * this.spacing || this.head[0][1] >= this.vertical * this.spacing) {
        clearInterval(this.animation);
        this.animation = null;
        this.freeze = true;
        return true;
      } else {
        return false;
      }
      return true;
    }
  }
  class Snake extends Area {
    constructor(horizontal, vertical, spacing, head, body, freeze, direction, speed, animation) {
      super(horizontal, vertical, spacing);
      this.head = head;   //  蛇头位置
      this.body = body;   //  蛇身点集合
      this.freeze = freeze;   //  是否被冻结
      this.direction = direction;   //  [[face]]->当前前进方向;[[to]]->要调整的方向;[[routes]]->可选方向
      this.speed = speed;   //  速度
      this.animation = animation;   //  移动动画
      this.initSnake();
    }
    initSnake() {
      this.head = [
        [(this.horizontal & 1) ? (this.horizontal - 1) / 2 * this.spacing : this.horizontal / 2 * this.spacing,
        (this.vertical & 1) ? (this.vertical - 1) / 2 * this.spacing : this.vertical / 2 * this.spacing]
      ];
      this.body = [];
      this.freeze = false;
      this.direction = {
        face: '',
        to: '',
        routes: []
      }; 
      this.createFood();
      this.drawSnake();
    }
    createFood() {
      super.createFoodPoint();
      this.snake_point = this.head.concat(this.body);
      while(this.snake_point.join('|').includes(this.food_point.toString())) {
        super.createFoodPoint();
      };
      ctx.fillRect(this.food_point[0], this.food_point[1], this.spacing, this.spacing);
    }
    drawSnake() {
      super.draw();
      if(this.head[0][0] == this.food_point[0] && this.head[0][1] == this.food_point[1]) {
        this.eat();
      } else {
        if(this.food_point.length > 0) {
          //  保留没有被吃掉的果实
          ctx.fillRect(this.food_point[0], this.food_point[1], this.spacing, this.spacing);
        }
      }
      if(!this.detect()) {
        let cur_head_x = this.head[0][0];
        let cur_head_y = this.head[0][1];
        let direction_code = ROUTES_DECODE[this.direction.face];
        let direction_code_calc = direction_code >= 0 ? (-~-direction_code) : (-~(-~direction_code));
        this.head[0][direction_code_calc] = direction_code >= 0 ? (this.head[0][direction_code_calc] - this.spacing) : (this.head[0][direction_code_calc] + this.spacing);
        if(this.body.length > 0) {
          if(this.body.length > 1) {
            for(let i = this.body.length - 1; i > 0; i--) {
              this.body[i] = this.body[i - 1];  //  移动时,后一个点占据前一个点
            }
          }
          this.body[0] = [cur_head_x, cur_head_y];
          this.snake_point = this.head.concat(this.body);
        }
      } else {
        if(window.confirm(`It's over! Restart?`)) {
          super.init();
          this.initSnake();
        }
      }
      this.snake_point.forEach(e => {
        ctx.fillRect(e[0], e[1], this.spacing, this.spacing);
      });
    }
    eat() {
      this.body.push([this.head[0][0], this.head[0][1]]);
      this.createFood();
    }
    move() {
      if(!this.freeze) {
        if(this.direction.face === '' || (this.direction.face != this.direction.to && this.direction.routes.includes(this.direction.to))) {
          this.direction.routes = ROUTES.filter(e => e != ROUTES_ENCODE[~ROUTES_DECODE[this.direction.to]]);
          this.direction.face = this.direction.to;
          this.drawSnake();   //  这步重绘是必要的,否则在关闭定时器时有短暂停顿动作
          clearInterval(this.animation);
          this.animation = setInterval(() => {    
            this.drawSnake();
          }, 1000 / this.speed);
        }
      }
    }
  }
  function moveSnake(e) {
    snake.direction.to = DIRECTION_CODE[e.key];
    snake.move();
  }
  if(container.getContext('2d')) {
    var ctx = container.getContext('2d');
    document.body.addEventListener('keydown', moveSnake);
    var area = new Area(HORIZONTAL, VERTICAL, SPACING);
    var snake = new Snake(HORIZONTAL, VERTICAL, SPACING, [], [], true, {face: '', to: '', routes: []}, 10, null);
  }
</script>

 

 

 

 

 

利用面向对象的方法,实现贪吃蛇。 1. 利用面向对象的思想实现——一个食物对象、一个蛇对象、一个游戏总控对象。 2. 在使用&times;&times;.prototype= {}重写原型对象的时候,一定要加上一句constructor:该对象。不然会造成实例化出来的实例的constructor为object。 3. 在underscore中,使用_.random(a,b)即可获得a-b中的一个随机数。 4. 在求食物的随机位置的时候,用到了panel.clientHeight/this.height - 1) * this.height。 原理是使用盒子的高度/小球的高度,可以算得最多放多少个小球。因为要控制小球不能超过边界,所以总数量要减去1,数量&times;高度即为随机位置的最大值。 5. 在蛇对象中,用body数组存放蛇身体每一个部分对象。蛇的绘制过程就是遍历body,在面板上绘制。 6. 蛇的移动分为两部分。 ① 蛇节移动到前一个蛇节的位置。直到蛇头后一个蛇节移动到蛇头的位置。 ② 根据direction判断蛇头如何移动。 注意:在游戏绘制的过程中,界面的每一次绘制都要**删除**之前的绘制,不然会叠加到一起。 7. 在蛇的闭包中建一个局部数组,存储蛇对象,可以更加方便的删除操作。 8. 只有在原型对象中的方法和属性,外界是可以调用的。 9. 蛇的移动(动画)必然需要定时器协助。定时器的时间,即象征着刷新速度,也就是难度。 10. this所在的函数在哪一个对象中,this就指向谁。单独写一个函数的时候,如果调用之前对象的this,需要备份指针(将对象的this赋值给另一个变量)。 11. JavaScript原生的键盘按下事件(keydown) 中,事件有一个keyCode属性,其值代表按下的键。其中:37—left、38—top、39—right、40—bottom。 12. 边界控制。通过判断蛇头与最大X和Y的关系,判断是否碰到边界。 13. confirm()方法用于显示一个带有指定消息和确认及取消按钮的对话框。 14. window.location.reload(); 重新加载当前文档 15. window.close() 方法用于关闭浏览器窗口。 16. 与食物的碰撞检测:如果蛇头和食物坐标重叠,将蛇尾添加到body中。并重新绘制一个食物点,将之前的食物删掉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值