ES6面向對象版貪吃蛇

   最近項目告一段落,難得空閒下來,就隨手寫了個貪吃蛇,作爲一個立志要成爲一個前端大牛的我,當然是js實現啦,哈哈哈。話不多說,貼上代碼,歡迎同行批評指正。

<!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>

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章