vue+ts类实现简易版贪吃蛇游戏

<template>
 <div>
    <p>贪吃蛇</p>
<!--   <el-button @click="cookFood">生成食物</el-button>-->
<!--   <el-button @click="addScore">添加分数</el-button>-->
   <div class="game-box">
     <div class="inner-content">
       <div class="food">
         <div class="food-dot"></div>
         <div class="food-dot"></div>
         <div class="food-dot"></div>
         <div class="food-dot"></div>
       </div>
       <div class="snack" id="snack">
         <div class="head"></div>
       </div>
     </div>
     <div class="score-bar">
       <span>score: 0</span>
       <span>level: 1</span>
     </div>
   </div>
 </div>
</template>

<script lang="ts" setup>
/*
* 思路: 有哪些内容 游戏面板 角色蛇 食物 记分板
*        内容组成: 蛇头蛇身体 分数等级
*        内容状态: 静态 动态(位置)
*        影响状态的因素: 键盘事件 边界情况 食物被吃 蛇身体的移动
* 实现:  获取页面元素
*        构造函数内初始化对象 定义对象的属性方法行为
*        同时操纵dom实现游戏效果
* 功能: 简易版贪吃蛇(目前只有 基础的功能 后期会进行功能补充)
*       学习地址:b站超哥的视频用原生ts实现 我用vue结合ts实现的主要是为了学习ts的类和实例化
*       https://www.bilibili.com/video/BV1Xy4y1v7S2/?spm_id_from=333.337.search-card.all.click&vd_source=065c02a4345d661813d7286c388468e6
* */

   // 初始化dom元素
  const foodDom = ref<HTMLElement | null>(); // 食物dom
  const scoreDom = ref<HTMLElement | null>(); // 分数dom
  const levelDom = ref<HTMLElement | null>(); // 等级dom
   const snackDom = ref<HTMLElement | null>(); // 蛇dom
   const snackHeadDom = ref<HTMLElement | null>(); // 蛇头dom
   const snackBodies = ref<HTMLElement[]>([]); // 蛇身体dom数组
   // 下面俩是辅助按钮做测试的和游戏本身没关系
  // const food = ref<Food>(); // 食物类实例
  // const score = ref<Score>(); // 分数类实例

  // 获取dom元素 用get获取的是动态节点
  const getDom=()=>{
    foodDom.value = document.getElementsByClassName("food")[0] as HTMLElement;
    scoreDom.value = document.getElementsByClassName("score-bar")[0] as HTMLElement;
    levelDom.value = document.getElementsByClassName("score-bar")[0] as HTMLElement;
    snackDom.value = document.getElementsByClassName("snack")[0] as HTMLElement;
    snackHeadDom.value = document.getElementsByClassName("head")[0] as HTMLElement;
    snackBodies.value = document.getElementById("snack")!.getElementsByTagName("div") as HTMLElement[];
    //开始游戏
    new Game();
  }
// 食物类。食物dom 食物位置 获取位置 生成食物
class Food{
  element: HTMLElement | null;
  constructor() {
    this.element = foodDom.value!;
  }
  // 获取食物位置
  getPosition() {
    return {
      x: this.element?.offsetLeft,
      y: this.element?.offsetTop
    };
  }

  // sui随机生成食物位置 在父盒子容器内 且每次移动距离为10的整数倍
  cook () {
    const x = Math.floor(Math.random() * 30) * 10;
    const y = Math.floor(Math.random() * 30) * 10;
    if(this.element) {
      this.element.style.left = `${x}px`;
      this.element.style.top = `${y}px`;
    }
  }
}

// 分数类 分数 等级 分数等级dom 最大等级 最大分数 添加分数 添加等级
class Score{
    scoreElement: HTMLElement | null;
    levelElement: HTMLElement | null;
    score: number = 0;
    level: number = 1;
    maxLevel: number = 100;
    maxScore: number = 10;
    // 分数dom 元素
    constructor() {
        this.scoreElement = scoreDom.value as HTMLElement;
        this.levelElement = levelDom.value as HTMLElement;
    }
    // 添加分数
    addScore(maxLevel: number = 100, maxScore: number = 10) {
        if (this.scoreElement && this.levelElement) {
            this.maxLevel = maxLevel;
            this.maxScore= maxScore;
            this.score=++this.score
            this.scoreElement.children[0].innerHTML = `score: ${this.score}`;
            this.addLevel()
        }
    }

  // 添加等级
   addLevel() {
    if (this.scoreElement && this.levelElement) {
      if (this.score % 10 === 0 && this.level < this.maxLevel) {
        this.level=++this.level
        this.levelElement.children[1].textContent = `level: ${this.level}`;
      }
    }
  }
}

// 蛇类 蛇头 身体 蛇 获取位置  移动 增加身体 判断是不是撞墙了抛出异常 蛇位置移动(不能掉头) 身体位置移动(每一节身体到前一节身体的位置 从后往前改)
class Snake{
    head: HTMLElement | null;
    bodies: HTMLElement[] = [];
    snack: HTMLElement | null;
    constructor() {
      this.head =  snackHeadDom.value
      this.bodies = snackBodies.value
      this.snack = snackDom.value!
    }
    // 获取蛇头位置
    getPosition() {
      return {
        x: this.head?.offsetLeft,
        y: this.head?.offsetTop
      };
    }

    // 移动
    move(x: number, y: number) {
        this.head!.style.left = `${x}px`;
        this.head!.style.top = `${y}px`;
        this.moveBody(); // 移动身体
        // 检查是否撞墙
        if (this.getPosition().x < 0 || this.getPosition().x > 290 || this.getPosition().y < 0 || this.getPosition().y > 290) {
          throw new Error("Game Over: Snake hit the wall!");
        }
    }


    // 增加身体
    addBody() {
      // 添加子元素 appendChild  innerHTML +=  insertAdjacentHTML  insertAdjacentElement
     this.snack?.insertAdjacentHTML("beforeend", "<div style='position: absolute; width:10px;height:10px;background:#000000;'></div>");
    }

    // 蛇位置移动
    moveBody() {
      // this.bodies=document.getElementById("snack")!.getElementsByTagName("div") as HTMLElement[];
      // 新增的身体需要移动到前一节身体的位置
      for(let i = this.bodies.length - 1; i > 0; i--) {
        this.bodies[i].style.left = this.bodies[i - 1].offsetLeft + 'px';
        this.bodies[i].style.top = this.bodies[i - 1].offsetTop + 'px';
      }
    }
}

// 游戏类 蛇 食物类 蛇移动方向 监听键盘事件 蛇移动 蛇状态是不是shi了捕获异常提示游戏结束
   // 判断是否吃到食物 食物位置更新 蛇啀要变长 分数增加
class Game {
   snack: Snake;
   food: Food;
   score: Score;
   direction: string = 'ArrowRight'; // 初始方向
   isLive: boolean = true; // 游戏状态
   constructor() {
     this.snack = new Snake();
     this.food = new Food();
     this.score = new Score();
     this.init();
   }

   init(){
     document.addEventListener("keydown", this.handleKeyDown.bind(this));
     //游戏开始蛇一直跑
     this.run()
   }

   handleKeyDown(event: KeyboardEvent) {
    this.direction = event.key
  }

  run(){
    const directions=['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
    let x= this.snack.getPosition().x;
    let y= this.snack.getPosition().y;
    console.log(`当前蛇头位置: x=${x}, y=${y}, 方向: ${this.direction}`);
    if(directions.includes(this.direction)){
      switch (this.direction) {
        case 'ArrowUp':
          y = y - 10;
          break;
        case 'ArrowDown':
          y = y + 10;
          break;
        case 'ArrowLeft':
          x = x - 10;
          break;
        case 'ArrowRight':
          x = x + 10;
          break;
      }
     this.isFoodEaten(); // 检查是否吃到食物
     try {
       this.snack.move(x, y);
     } catch (error) {
        alert("Game Over: Snake hit the wall!");
        this.isLive = false;
     }
    }
    // 如果蛇还活着就继续移动  等级越高,速度越快
    this.isLive && setTimeout(this.run.bind(this), 500 - (this.score.level - 1) * 30);
  }

  // 判断蛇是否吃到食物
  isFoodEaten() {
    const snakePosition = this.snack.getPosition();
    const foodPosition = this.food.getPosition();
    if (snakePosition.x === foodPosition.x && snakePosition.y === foodPosition.y) {
      this.food.cook(); // 生成新的食物
      this.score.addScore(); // 分数增加
      this.snack.addBody(); // 蛇变长
    }
  }
}

// 引入nextTick是为了确保DOM更新后再执行
// const cookFood=()=>{
//  nextTick(() => {
//    if(food.value) {
//      food.value.cook();
//    }else{
//      food.value = new Food();
//      food.value.cook();
//    }
//   });
// }
//
// const addScore=()=>{
//   nextTick(() => {
//     if(score.value) {
//       score.value.addScore();
//     }else{
//       score.value = new Score();
//       score.value.addScore();
//     }
//   });
// }

onMounted(()=>{
   getDom();
})
</script>

<style lang="scss" scoped>
.game-box {
  margin: 0 auto;
  box-sizing: border-box;
  height: 420px;
  width: 360px;
  border: 18px solid #000000;
  background-color: #b7d4a8;
  border-radius: 40px;
  display: flex;
  flex-flow: column nowrap;
  justify-content: space-around;
  align-items: center;
  .inner-content {
    border: solid 1px #000000;
    width: 300px;
    height: 300px;
    position: relative;
    margin-top: 24px;
    & > div{
      position: absolute;
    }
    .food {
      top: 20px;
      left: 30px;
      display: flex;
      width: 10px;
      flex-flow: row wrap;
      justify-content: space-between;
      & > div{
        width: 5px;
        height: 5px;
        background-color: #000000;
        transform: rotate(45deg);
     }
    }
    .snack {
      & > div {
        position: absolute;
        top: 0;
        left: 0;
        width: 10px;
        height: 10px;
        background-color: #000000;
      }
    }
  }
  .score-bar {
    display: flex;
    justify-content: space-between;
    width: 304px;
    font-family: Courier New, Courier, monospace;
    font-size: 20px;
    color: #000000;
    font-weight: bold;
  }
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值