画出蛇的身体

我需要一个写一个Snake类,用来在地图上创造一条蛇,它有一些基本的属性,这里列举一下,direction(蛇移动的方向)、status(蛇当前的状态)、eps(用于蛇移动时判断精度)、speed(蛇移动的速度)、step(蛇移动的步数)、eyes_direction(蛇眼睛的方向)、next_cell(用于蛇移动的中间变量)等等。

这里给出渲染蛇身体以及蛇眼睛的代码

render() {
    let ctx = this.gameMap.ctx;
    let L = this.gameMap.L;

    ctx.fillStyle = "red";
    if (this.status === "dead") {
      ctx.fillStyle = "white";
    }

    for (let cell of this.cells) {
      ctx.beginPath();
      ctx.arc(cell.x * L, cell.y * L, (L / 2) * 0.8, 0, 2 * Math.PI);
      ctx.fill();
    }

    // 矩形填充蛇身体, 让蛇身看起来饱满
    for (let i = 1; i < this.cells.length; i++) {
      const a = this.cells[i - 1],
        b = this.cells[i];
      if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps)
        continue;
      if (Math.abs(a.x - b.x) < this.eps) {
        ctx.fillRect(
          (a.x - 0.4) * L,
          Math.min(a.y, b.y) * L,
          L * 0.8,
          Math.abs(a.y - b.y) * L
        );
      } else {
        ctx.fillRect(
          Math.min(a.x, b.x) * L,
          (a.y - 0.4) * L,
          Math.abs(a.x - b.x) * L,
          L * 0.8
        );
      }
    }
    // 给蛇头加上眼睛;
    let eye_color = "black";
    ctx.fillStyle = eye_color;
    for (let i = 0; i < 2; i++) {
      let eye_dx =
        (this.cells[0].x + this.eye_dx[this.eyes_direction][i] * 0.2) * L;
      let eye_dy =
        (this.cells[0].y + this.eye_dy[this.eyes_direction][i] * 0.2) * L;
      ctx.beginPath();
      ctx.arc(eye_dx, eye_dy, L * 0.05, 0, Math.PI * 2);
      ctx.fill();
    }
  }

控制蛇的移动

控制蛇在地图上移动的核心函数,update_move()

首先计算出当前蛇头的位置与目标蛇头的位置之间的欧几里得距离,即distance

分别判断当移动完成后的处理以及还在移动中的处理

将移动完成时,status值为"idle",蛇头值为next_cellnext_cell值为null

因为位移是距离与方向的组合,所以要计算move_distance,即每个帧蛇要移动的距离,再乘上方向,也就是角度,即(dx / distance) * move_distance

同时,在适当时尾巴也要移动,具体是根据游戏的规则来。

 update_move() {
    let dx = this.next_cell.x - this.cells[0].x;
    let dy = this.next_cell.y - this.cells[0].y;
    let distance = Math.sqrt(dx * dx + dy * dy);
    // 停止移动
    if (distance < this.eps) {
      this.status = "idle";
      this.cells[0] = this.next_cell;
      this.next_cell = null;

      if (!this.check_tail_increasable()) {
        this.cells.pop();
      }
    } else {
      let move_distance = (this.speed * this.time_delta) / 1000;
      this.cells[0].x += (dx / distance) * move_distance;
      this.cells[0].y += (dy / distance) * move_distance;

      // 尾巴移动
      if (!this.check_tail_increasable()) {
        let k = this.cells.length;
        let tail = this.cells[k - 1];
        let tail_target = this.cells[k - 2];
        let tail_dx = tail_target.x - tail.x;
        let tail_dy = tail_target.y - tail.y;
        tail.x += (tail_dx / distance) * move_distance;
        tail.y += (tail_dy / distance) * move_distance;
      }
    }
  }

控制蛇的下一步移动

next_cell值为direction对应的方向,this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]);

处理一些全局变量,step++,status="move",eyes_direction=d,设置新的蛇头等。

这里给出next_step()的代码

next_step() {
    const d = this.direction;
    this.next_cell = new Cell(
      this.cells[0].r + this.dr[d],
      this.cells[0].c + this.dc[d]
    );
    this.step++;
    this.status = "move";
    this.eyes_direction = d;

    let k = this.cells.length;
    for (let i = k; i > 0; i--) {
      this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));
    } 
  }

canvas事件监听

需要在canvas标签上加上tabindex="0"这个属性,表示在页面加载完成后聚焦键盘事件。

需要在Map类中的start()函数中调用add_event_listender函数,即在地图加载后的第一时间为游戏类注册事件监听。

这里直接给出具体代码

add_listener_event() {
    this.ctx.canvas.focus();
    this.ctx.canvas.addEventListener("keydown", (e) => {
      e.preventDefault();
      let key = e.key;
      let d = -1;
      if (key === "w") d = 0;
      else if (key === "d") d = 1;
      else if (key === "s") d = 2;
      else if (key === "a") d = 3;
      if (d >= 0) this.snake.set_direction(d);
    });
  }

  start() {
    this.add_listener_event();
  }