原生js贪吃蛇

用原生js做个贪吃蛇

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      #bg {
        width: 600px;
        height: 600px;
        border: solid 5px black;
        background: aliceblue;
        position: relative;
        overflow: hidden;
      }

      .body {
        width: 20px;
        height: 20px;
        background: blue;
        position: absolute;
      }

      .head {
        background: red;
      }

      .head::before {
        content: '';
        display: block;
        width: 1200px;
        height: 0;
        border-top: solid 1px red;
        position: absolute;
        top: 10px;
        left: -600px;
      }

      .head::after {
        content: '';
        display: block;
        width: 0;
        height: 1200px;
        border-left: solid 1px red;
        position: absolute;
        top: -600px;
        left: 10px;
      }

      .food {
        background: green;
        border-radius: 10px;
      }
    </style>
  </head>
  <body>
    <div id="bg"></div>
  </body>
  <script src="snake.js"></script>
</html>
const bg = document.getElementById('bg');

class Snake {
  head = new SnakeHead(0, 0);
  dir = {
    x: 1,
    y: 0,
  };
  body = [];
  food;
  timer;

  get allBody() {
    return [...this.body, this.head];
  }

  nextPosition() {
    let nextX = this.dir.x + this.head.x;
    let nextY = this.dir.y + this.head.y;
    nextX = nextX < 0 ? 29 : nextX < 30 ? nextX : 0;
    nextY = nextY < 0 ? 29 : nextY < 30 ? nextY : 0;
    return [nextX, nextY];
  }

  move() {
    const [nextX, nextY] = this.nextPosition();
    if (this.body.some(x => x.x === nextX && x.y === nextY)) {
      return this.gameOver();
    }
    if (nextX === this.food.x && nextY === this.food.y) {
      return this.eat();
    }
    if (this.body.length) {
      const lastBody = this.body.splice(0, 1)[0];
      lastBody.x = this.head.x;
      lastBody.y = this.head.y;
      this.body.push(lastBody);
    }
    this.head.x = nextX;
    this.head.y = nextY;
  }

  eat() {
    this.food.x = this.head.x;
    this.food.y = this.head.y;
    this.head.x += this.dir.x;
    this.head.y += this.dir.y;
    this.body.push(this.food);
    this.food.toBody();
    this.addFood();
  }

  addFood() {
    const xCount = this.allBody.reduce((m, x) => {
      if (m[x.x]) {
        m[x.x]++;
      } else {
        m[x.x] = 1;
      }
      return m;
    }, {});
    const someX = [];
    for (let i = 0; i < 30; ++i) {
      if (!xCount[i] || xCount[i] < 30) {
        someX.push(i);
      }
    }
    const randomX = someX[Math.floor(Math.random() * someX.length)];
    const bodyOnTargetX = this.allBody.filter(x => x.x === randomX).map(x => x.y);
    const someY = [];
    for (let i = 0; i < 30; ++i) {
      if (!bodyOnTargetX.includes(i)) {
        someY.push(i);
      }
    }
    const randomY = someY[Math.floor(Math.random() * someY.length)];
    this.food = new Food(randomX, randomY);
  }

  run() {
    this.addFood();
    this.timer = setInterval(() => this.move(), 300);
    document.onkeydown = e => {
      const firstBody = this.body[this.body.length - 1];
      switch (e.key) {
        case 'ArrowUp':
          if (firstBody && firstBody.y < this.head.y) return;
          if (this.dir.x === 0 && this.dir.y === -1) {
            return this.move();
          }
          this.dir.x = 0;
          this.dir.y = -1;
          return;
        case 'ArrowDown':
          if (firstBody && firstBody.y > this.head.y) return;
          if (this.dir.x === 0 && this.dir.y === 1) {
            return this.move();
          }
          this.dir.x = 0;
          this.dir.y = 1;
          return;
        case 'ArrowLeft':
          if (firstBody && firstBody.x < this.head.x) return;
          if (this.dir.x === -1 && this.dir.y === 0) {
            return this.move();
          }
          this.dir.x = -1;
          this.dir.y = 0;
          return;
        case 'ArrowRight':
          if (firstBody && firstBody.x > this.head.x) return;
          if (this.dir.x === 1 && this.dir.y === 0) {
            return this.move();
          }
          this.dir.x = 1;
          this.dir.y = 0;
          return;
      }
    }
  }

  gameOver() {
    clearInterval(this.timer);
    document.onkeydown = null;
    alert('game over');
  }
}

class SnakeBody {
  constructor(x, y) {
    this.body = document.createElement('div');
    this.x = x;
    this.y = y;
    bg.append(this.body);
    this.body.classList.add('body');
  }

  get x() {
    return this._x;
  }

  set x(val) {
    this._x = val;
    this.body.style.left = val * 20 + 'px';
  }

  get y() {
    return this._y;
  }

  set y(val) {
    this._y = val;
    this.body.style.top = val * 20 + 'px';
  }
}

class SnakeHead extends SnakeBody {
  constructor(x, y) {
    super(x, y);
    this.body.classList.add('head');
  }
}

class Food extends SnakeBody {
  constructor(x, y) {
    super(x, y);
    this.body.classList.add('food');
  }

  toBody() {
    this.body.classList.remove('food');
  }
}

new Snake().run();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值