Vue3+Ts实现贪吃蛇小游戏

前言

前段时间学习了vue3和typescript,跟着B站李立超老师的视频做了一下这个贪吃蛇的小游戏,简单的记录一下。

编写食物类

class Food {
    
    
  element: HTMLElement;
  //每个食物的分值 1-3的随机数
  score: number;
  constructor() {
    
    
    this.element = document.getElementById("food")!;
    this.score = 1;
  }
  // 获取食物的位置
  get X() {
    
    
    return this.element.offsetLeft;
  }
  get Y() {
    
    
    return this.element.offsetTop;
  }
  // 获取食物的得分
  get Score() {
    
    
    return this.score;
  }
  // 随机生成食物
  change(): void {
    
    
    let top = Math.round(Math.random() * 29) * 10;
    let left = Math.round(Math.random() * 29) * 10;
    this.element.style.left = left + "px";
    this.element.style.top = top + "px";
    this.score = Math.round(Math.random() * 3);
  }
}

编写蛇类

class Snake {
    
    
  element: HTMLElement;
  // 蛇头元素 蛇这个div中的第一个子元素
  head: HTMLElement;
  // 包括蛇头 整个蛇 HTMLCollection会实时更新
  bodys: HTMLCollection;
  constructor() {
    
    
    this.element = document.getElementById("snake")!;
    // 断言数据类型 获取snake的第一个div子元素
    this.head = document.querySelector("#snake > div") as HTMLElement;
    //head属于body的一部分
    this.bodys = 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("蛇撞墙了x");
    }
    // 判断是否发生了调头
    
    if (this.bodys[1] && (this.bodys[1] as HTMLElement).offsetLeft === value) {
    
    
      // 如果发生调头 让蛇继续向反方向走
      if (value > this.X) {
    
    
        value = this.X - 10;
      } else {
    
    
        value = this.X + 10;
      }
    }
    this.move();
    this.head.style.left = value + "px";
    this.checkHeadBody();
  }
  set Y(value: number) {
    
    
    if (this.Y == value) {
    
    
      return;
    }
    if (value < 0 || value > 290) {
    
    
      throw new Error("蛇撞墙了Y");
    }
    // 判断是否发生了调头
    if (this.bodys[1] && (this.bodys[1] as HTMLElement).offsetTop === value) {
    
    
      // 如果发生调头 让蛇继续向反方向走
      if (value > this.Y) {
    
    
        value = this.Y - 10;
      } else {
    
    
        value = this.Y + 10;
      }
    }
    this.move();
    this.head.style.top = value + "px";
    this.checkHeadBody();
  }
  move() {
    
    
    for (let i = this.bodys.length - 1; i > 0; i--) {
    
    
      // 获取前边身体位置 类型断言
      let X = (this.bodys[i - 1] as HTMLElement).offsetLeft;
      let Y = (this.bodys[i - 1] as HTMLElement).offsetTop;

      // 身体改了
      (this.bodys[i] as HTMLElement).style.left = X + "px";
      (this.bodys[i] as HTMLElement).style.top = Y + "px";
    }
  }
  addBody() {
    
    
    body.push({
    
    });
  }
  checkHeadBody() {
    
    
    //   获取所有身体 检查是否和蛇头坐标重叠
    for (let i = 1; i < this.bodys.length; i++) {
    
    
      let bd = this.bodys[i] as HTMLElement;
      if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
    
    
        throw new Error("撞到自己了");
      }
    }
  }
}

编写游戏控制类

class Controller {
    
    
  snake: Snake;
  food: Food;
  direction: string = "";
  isAlive: boolean = true;
  constructor() {
    
    
    this.snake = new Snake();
    this.food = new Food();
    this.food.change();
    this.init();

  }
  init(): void {
    
    
    document.addEventListener<"keydown">(
      "keydown",
      this.keydownHandler.bind(this)
    );
    this.run();
  }
  keydownHandler(event: KeyboardEvent): void {
    
    
    this.direction = event.key;
  }
  run(): void {
    
    
    let X = this.snake.X;
    let Y = this.snake.Y;
    switch (this.direction) {
    
    
      case "ArrowUp":
      case "Up":
        Y -= 10;
        break;
      case "ArrowDown":
      case "Down":
        Y += 10;
        break;
      case "ArrowLeft":
      case "Left":
        X -= 10;
        break;
      case "ArrowRight":
      case "Right":
        X += 10;
        break;
    }
    this.checkEat(X, Y);
    try {
    
    
      this.snake.X = X;
      this.snake.Y = Y;
    } catch (error) {
    
    
      throw new Error("游戏结束");
    }

    this.isAlive &&
      setTimeout(() => {
    
    
        this.run();
      }, 100 - (panel.level - 1) * 30);
  }
  checkEat(X: number, Y: number): void {
    
    
    if (this.food.X === X && this.food.Y === Y) {
    
    
      changeScore(this.food.Score);
      this.food.change();
      this.snake.addBody();
    }
  }
}

编写计分板函数

let panel: {
    
     score: number; level: number };
panel = reactive({
    
    
  score: 0,
  level: 1,
});
let levelUp: () => void;
levelUp = function () {
    
    
  if (panel.level <= 10) {
    
    
    panel.level++;
  }
};

let changeScore: (a: number) => void;
changeScore = function (a: number) {
    
    
  panel.score += a;
  if (panel.score % 10 == 0) {
    
    
    levelUp();
  }
};

启动

启动必须在DOM渲染完成之后再启动即Mounted,否则无法获取到DOM元素

onMounted(() => {
    
    
  const controller = new Controller();
});

页面

<template>
  <div class="main-container">
    <div class="body-wrapper">
      <div id="snake">
        <div v-for="(item, index) in body" :key="index"></div>
      </div>
      <div id="food">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>
    <div class="footer-panel">
      <div>
        SCORE: <span>{
    
    {
    
     panel.score }}</span>
      </div>
      <div>
        level: <span>{
    
    {
    
     panel.level }}</span>
      </div>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.main-container {
    
    
  height: 420px;
  width: 360px;
  background-color: #b7d4a8;
  border: 10px solid #000;
  border-radius: 20px;
  margin: 100px auto;
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: space-around;
  .body-wrapper {
    
    
    height: 304px;
    width: 304px;
    border: 2px solid #000;
    position: relative;
    #snake {
    
    
      & > div {
    
    
        height: 10px;
        width: 10px;
        background: #000;
        position: absolute;
      }
    }
    #food {
    
    
      height: 10px;
      width: 10px;
      position: absolute;
      display: flex;
      flex-wrap: wrap;
      top: 0;
      left: 0;
      justify-content: space-between;
      align-content: space-between;
      & > div {
    
    
        background: #000;
        width: 4px;
        height: 4px;
      }
    }
  }
  .footer-panel {
    
    
    font-size: 20px;
    font-weight: bold;
    display: flex;
    justify-content: space-between;
    font-family: "Courier New", Courier, monospace;
    width: 300px;
  }
}
</style>

效果

请添加图片描述

猜你喜欢

转载自blog.csdn.net/qq_46258819/article/details/129175851