效果预览

项目结构


共有Food,GameControl,ScorePanel,Snake四个类

食物类

//食物类
class Food {
   
  //食物所对应的元素
  element: HTMLElement;
  constructor() {
   
    this.element = document.getElementById('food');
  }

  //获取食物位置的方法
  get X() {
   
    return this.element.offsetLeft;
  }
  get Y() {
   
    return this.element.offsetTop;
  }

  //食物位置的修改
  change() {
   
    //生成随机位置
    //0~290的随机数,一格是10px
    //Math.round(Math.random() * 29) * 10
    let Left = Math.round(Math.random() * 29) * 10;
    let Top = Math.round(Math.random() * 29) * 10;
    this.element.style.left = Left + 'px';
    this.element.style.top = Top + 'px';
  }
}

export default Food;

记分牌类

记分牌只需要控制自己的分数增长和等级提高

/** * 记分牌 */

 class ScorePanel {
   
  //分数和等级
  score = 0;
  level = 1;
  scoreEle: HTMLElement;
  leveEle: HTMLElement;

  //等级限制
  maxLevel: number;

  //多少分升级
  upLevel: number;

  constructor(maxLevel: number = 10, upLevel: number = 10) {
   
    this.scoreEle = document.getElementById('score');
    this.leveEle = document.getElementById('level');
    this.maxLevel = maxLevel;
    this.upLevel = upLevel;
  }

  //分数增加方法
  addScore() {
   
    this.scoreEle.innerHTML = ++this.score + '';

    //每10分升1级
    if(this.score % this.upLevel == 0) {
   
      this.addLevel();
    }
  }

  //等级提升
  addLevel() {
   
    if(this.level < 10) {
   
      this.leveEle.innerHTML = ++this.level + '';
    }
  }
}

export default ScorePanel;

蛇类

蛇类最主要的是身体如何根据蛇头来改变位置
这里是对蛇身体除了蛇头进行遍历,将前一个div的位置赋值给后一个div的位置

/** * snake */

class Snake {
   
  //蛇头
  head: HTMLElement;

  //蛇身体
  bodies: HTMLCollection;

  //蛇的容器
  element: HTMLElement;

  constructor() {
   
    this.element = document.getElementById("snake");
    this.head = document.querySelector("#snake > div") as HTMLElement;
    this.bodies = document.getElementById("snake").getElementsByTagName("div");
  }

  //获取蛇头的坐标
  get X() {
   
    return this.head.offsetLeft;
  }
  get Y() {
   
    return this.head.offsetTop;
  }

  //给蛇头坐标赋值
  set X(value: number) {
   
    if (this.X === value) {
   
      return;
    }
    if (value < 0 || value > 290) {
   
      throw new Error("游戏结束");
    }
    if (
      this.bodies[1] &&
      value === (this.bodies[1] as HTMLElement).offsetLeft
    ) {
   
      if (value > this.X) {
   
        value = this.X - 10;
      } else {
   
        value = this.X + 10;
      }
    }
    this.bodyMove();
    this.head.style.left = `${
     value}px`;
    this.checkHead();
  }
  set Y(value: number) {
   
    if (this.Y === value) {
   
      return;
    }
    if (value < 0 || value > 290) {
   
      throw new Error("游戏结束");
    }
    if (this.bodies[1] && value === (this.bodies[1] as HTMLElement).offsetTop) {
   
      if (value > this.Y) {
   
        value = this.Y - 10;
      } else {
   
        value = this.Y + 10;
      }
    }
    this.bodyMove();
    this.head.style.top = `${
     value}px`;
    this.checkHead();
  }

  //蛇的身体增加
  addBody() {
   
    this.element.insertAdjacentHTML("beforeend", "<div></div>");
  }

  bodyMove() {
   
    //遍历到除了蛇头意外的div
    for (let i = this.bodies.length - 1; i > 0; i--) {
   
      //获取前一个div的位置
      let preX = (this.bodies[i - 1] as HTMLElement).offsetLeft;
      let preY = (this.bodies[i - 1] as HTMLElement).offsetTop;

      //将前一个的位置赋值给当前div的位置
      (this.bodies[i] as HTMLElement).style.left = `${
     preX}px`;
      (this.bodies[i] as HTMLElement).style.top = `${
     preY}px`;
    }
  }

  //检查蛇头有没有撞到自己
  checkHead() {
   
    for(let i = 1; i < this.bodies.length; i++) {
   
      let bd = (this.bodies[i] as HTMLElement);
      if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
   
        throw new Error("蛇撞到自己的身体了");
      }
    }
  }
}

export default Snake;

整个游戏控制类

import Food from "./Food";
import ScorePanel from "./ScorePanel";
import Snake from "./Snake";

//游戏控制器
class GameControl {
   
  //蛇
  snake: Snake;
  food: Food;
  scorePanel: ScorePanel;

  //存储蛇的移动方向
  direction: string;

  //记录游戏是否结束
  isLive: boolean = true;

  constructor() {
   
    this.snake = new Snake();
    this.food = new Food();
    this.scorePanel = new ScorePanel(10, 2);

    this.init();
  }

  //初始化
  init() {
   
    document.addEventListener("keydown", this.keyDownHandler.bind(this));
    this.run();
  }

  keyDownHandler(event: KeyboardEvent) {
   
    //判断按键是否合法
    this.direction = event.key;
  }

  //让蛇移动的方法
  run() {
   
    let X = this.snake.X;
    let Y = this.snake.Y;

    switch (this.direction) {
   
      case "ArrowUp":
        Y -= 10;
        break;
      case "ArrowDown":
        Y += 10;
        break;
      case "ArrowLeft":
        X -= 10;
        break;
      case "ArrowRight":
        X += 10;
        break;
    }
    //检查蛇是否吃到食物
    this.checkEat(X, Y);

    try {
   
      this.snake.X = X;
      this.snake.Y = Y;
    } catch (err) {
   
      alert(err);
      this.isLive = false;
    }
    this.isLive &&
      setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 50);
  }

  checkEat(x: number, y: number) {
   
    if (x === this.food.X && y === this.food.Y) {
   
      this.food.change();
      this.scorePanel.addScore();
      this.snake.addBody();
    }
  }
}

export default GameControl;

完整项目代码

https://gitee.com/tao-yuhan/type-script-snake.git