vue贪吃蛇小游戏

偶然间跟一个前端大佬学习了一个用vue写的贪吃蛇小游戏,一个小demo 感觉挺好玩,就纪录下来了

直接全代码

<template>
  <div>
    <div class="title" :class="isShowTitle ? 'titlebg' : ''">
      <span>分数:{{ score }}</span>
    </div>
    <div class="game-box">
      <ul>
        <!-- 每一个小格子 -->
        <li
          v-for="(item, index) in gameMap"
          :key="index"
          :class="{
            tou: item.isTou,
            shou: item.isShou,
            jiao: item.isJiao,
            shen: item.isShen,
            r90: item.isR90,
            r180: item.isR180,
            rm90: item.isRM90,
            cate: item.isCate
          }"
        ></li>
      </ul>
    </div>
    <div class="gameover" v-show="isGameOver">
      <ul>
        <!-- li有多少个是根据分数来的分数有可能是 0 10 100 1000  '150' -->
        <li
          v-for="(item, index) in score.toString()"
          :key="index"
          :style="{
            background:
              'url(' + scoreNumArr[item] + ') center top/auto 100% no-repeat'
          }"
        ></li>
      </ul>
      <p>
        历史最高 <span>{{ historyMax }}</span>
      </p>
      <div class="restart" @click="restart"></div>
    </div>
    <div class="gameaction" v-show="!isShowTitle">
      <div class="btn" @click="action"></div>
    </div>
  </div>
</template>

<script>
import num0 from '../assets/images/numbers/0.png';
import num1 from '../assets/images/numbers/1.png';
import num2 from '../assets/images/numbers/2.png';
import num3 from '../assets/images/numbers/3.png';
import num4 from '../assets/images/numbers/4.png';
import num5 from '../assets/images/numbers/5.png';
import num6 from '../assets/images/numbers/6.png';
import num7 from '../assets/images/numbers/7.png';
import num8 from '../assets/images/numbers/8.png';
import num9 from '../assets/images/numbers/9.png';
export default {
  data() {
    return {
      // 生成地图
      gameMap: [],
      // 生成蛇身数组,记录了蛇的位置在地图上的下标(格子下标的记录)
      snakeArr: [50, 51, 52, 53, 54],
      // 蛇的移动方向
      direction: 'right',
      // 定时器
      timer: null,
      // 月饼的下标
      foodIndexs: [],
      // 分数
      score: 0,
      // 是否游戏结束
      isGameOver: false,
      // 分数数组
      scoreNumArr: [num0, num1, num2, num3, num4, num5, num6, num7, num8, num9],
      // 历史最高
      historyMax: 3000,
      // 是否显示title
      isShowTitle: false
    };
  },
  created() {
    this.gameInit();
  },
  methods: {
    gameInit() {
      // 初始化
      this.gameMap = [];
      this.snakeArr = [50, 51, 52, 53, 54];
      this.score = 0;
      this.direction = 'right';
      this.isGameOver = false;

      // 生成游戏地图,600个小格子
      for (let i = 0; i < 600; i++) {
        this.gameMap.push({});
      }

      // 生成蛇
      this.generSnake();

      // 生成食物
      // this.数组名[parseInt(Math.random()*this.数组名.length)]
      // this.gameMap[parseInt(Math.random()*this.gameMap.length)].isCate = true;
      this.generCates();
    },
    action() {
      this.isShowTitle = true;
      this.toplay();
    },
    toplay() {
      // 蛇的移动
      this.timer = setInterval(this.play, 100);
    },
    restart() {
      this.gameInit();
      this.toplay();
    },
    gameover() {
      this.isGameOver = true;
      clearInterval(this.timer);
      //历史最高分的处理  (历史最高分保存在本地存储中)
      // 1、获取本地历史最高分
      let localMax = localStorage.getItem('historyMax');
      // 2、和this.score进行对比
      // localMax? (真正和score进行对比):this.score   第一次游戏结束的时候拿不到localMax,拿到了才进行对比,拿不到,历史最高分就是这次游戏的分数this.score
      // this.historyMax = localMax? (真正和this.score进行对比):this.score
      this.historyMax = localMax
        ? Number(localMax) > this.score
          ? localMax
          : this.score
        : this.score;
      // 3、把historyMax更新到本地存储
      localStorage.setItem('historyMax', this.historyMax);
    },
    play() {
      // 边界判断
      // 判断是不是达到边界了
      // 如果达到边界了,游戏结束  就要return  (!!! 还要在return前面去关闭定时器)
      // 每一个方向的边界条件不一样,例如: (都是那蛇头的位置来进行分析)
      // 向右this.snakeArr的最后一个元素+1 能被40整除
      // 向下this.snakeArr的最后一个元素  >=600
      // 向上this.snakeArr的最后一个元素  <0
      // 向左this.snakeArr的最后一个元素  能被40整除
      let touIndex = this.snakeArr[this.snakeArr.length - 1];

      let boundaryArr = [
        this.direction == 'right' && (touIndex + 1) % 40 == 0,
        this.direction == 'down' && touIndex >= 600 - 40,
        this.direction == 'up' && touIndex < 40,
        this.direction == 'left' && touIndex % 40 == 0
      ];
      if (boundaryArr.includes(true)) {
        // 游戏结束
        this.gameover();

        return;
      }

      // -------------以下代码就是处理蛇的移动-------------
      // 先清除所有格子的isXxx为false
      this.gameMap.forEach((item) => {
        item.isTou = false;
        item.isShou = false;
        item.isShen = false;
        item.isJiao = false;
        item.isR90 = false;
        item.isRM90 = false;
        item.isR180 = false;
      });

      // 1,处理snakeArr数组(删除第一个元素,追加一个元素进去)
      this.snakeArr.splice(0, 1);
      let steps = 0;
      switch (this.direction) {
        case 'right':
          steps = 1;
          break;
        case 'down':
          steps = 40;
          break;
        case 'up':
          steps = -40;
          break;
        case 'left':
          steps = -1;
          break;
      }
      this.snakeArr.push(this.snakeArr[this.snakeArr.length - 1] + steps);

      // 渲染蛇和界面之前需要判断吃到了没有。吃到了的话,要完成吃食物的逻辑
      // [吃食物的逻辑](多个月饼)思路:蛇的头部的下标在食物数组foodIndexs里面
      let headIndex = this.snakeArr[this.snakeArr.length - 1];
      if (this.foodIndexs.includes(headIndex)) {
        // 1、蛇身要边长(把食物的下标添加到蛇数组中)
        this.snakeArr.push(headIndex);
        // 2、旧的月饼要消失(月饼数组的这个元素要删除)
        this.foodIndexs.splice(this.foodIndexs.indexOf(headIndex), 1);
        this.gameMap[headIndex].isCate = false;
        // 3、随机生成新的月饼
        this.generOneCate();
        // 4、分数+10分
        this.score += 10;
      }

      // 2,调用generSnake(),为了当前的新的snakeArr数组的每一个格子的 isXxx为true
      this.generSnake();
      this.$forceUpdate(); //界面更新
    },
    generOneCate() {
      while (true) {
        let foodIndex = parseInt(Math.random() * this.gameMap.length);
        // 判断在不在蛇身数组上 或者 在月饼数组上
        // if(foodIndex在snakeArr数组中 || 在foodIndexs数组中){
        if (
          this.snakeArr.includes(foodIndex) ||
          this.foodIndexs.includes(foodIndex)
        ) {
          continue;
        }

        // 展示食物
        this.foodIndexs.push(foodIndex);
        this.gameMap[foodIndex].isCate = true;
        break;
      }
    },
    generCates() {
      // 生成食物
      for (let i = 0; i < 20; i++) {
        this.generOneCate();
      }
    },
    generSnake() {
      // 第一件事情: 生成蛇数组
      this.gameMap[this.snakeArr[this.snakeArr.length - 1]].isTou = true;
      this.gameMap[this.snakeArr[this.snakeArr.length - 2]].isShou = true;
      // snakeArr数组中,除了第一个表示蛇脚,最后一个表示蛇头,倒数第二个表示蛇手
      // 其他都是蛇身
      for (let i = 1; i < this.snakeArr.length - 2; i++) {
        this.gameMap[this.snakeArr[i]].isShen = true;
      }
      this.gameMap[this.snakeArr[0]].isJiao = true;
      // 第二件事情: 处理角度
      // 思路: 检查蛇数组的每一个格子是否需要旋转,要旋转多少度【难点】
      // 蛇数组  snakeArr:[50,51,52,53,54]
      // 这格子向右,当前的元素的下标减去下一个元素的下标 为 -1,  要转90度
      // 这格子向左,当前的元素的下标减去下一个元素的下标 为 1 ,  要转-90度
      // 这格子向下,当前的元素的下标减去下一个元素的下标 为 -40,  要转180度
      // 这格子向上,当前的元素的下标减去下一个元素的下标 为 40

      // 遍历snakeArr这个数组(最后一个元素没有下一个元素,所以先忽略最后一个元素),用当前元素减去下一个元素,看看值是多少从而决定转多少度
      for (let i = 0; i < this.snakeArr.length - 1; i++) {
        // 用当前元素减去下一个元素
        let item = this.snakeArr[i];
        let next = this.snakeArr[i + 1];
        switch (item - next) {
          case -1:
            this.gameMap[item].isR90 = true;
            break;
          case 1:
            this.gameMap[item].isRM90 = true;
            break;
          case -40:
            this.gameMap[item].isR180 = true;
            break;
        }
      }
      // 处理最后一个格子,蛇头的旋转角度
      let lastIndex = this.snakeArr[this.snakeArr.length - 1];
      switch (this.direction) {
        case 'right':
          this.gameMap[lastIndex].isR90 = true;
          break;
        case 'left':
          this.gameMap[lastIndex].isRM90 = true;
          break;
        case 'down':
          this.gameMap[lastIndex].isR180 = true;
          break;
      }

      // this.gameMap[this.snakeArr[this.snakeArr.length-1]].isR90=true
    }
  },
  mounted() {
    document.addEventListener('keydown', (e) => {
      // console.log(e.keyCode);
      // 按下按键执行这里的代码
      // 判断对比 e.keyCode  和对应的方向键的键码是否一致 37,38,39,40

      switch (e.keyCode) {
        case 40:
          if (this.direction === 'up') break;
          this.direction = 'down';
          break;
        case 37:
          // 先判断当前方向是不是正在向右,如果是,就得break
          if (this.direction === 'right') break;
          this.direction = 'left';
          break;
        case 39:
          // 先判断当前方向是不是正在向左,如果是,就得break
          if (this.direction === 'left') break;
          this.direction = 'right';
          break;
        case 38:
          if (this.direction === 'down') break;
          this.direction = 'up';
          break;
      }
    });
  }
};
</script>

<style lang = "less" scoped>
.titlebg {
  background: url('../assets/images/title.png') no-repeat center 20px;
}
.title {
  width: 1200px;
  height: 200px;
  /* background-color: skyblue; */
  background-size: auto 160px;
  margin: 0 auto;
  position: relative;
  span {
    color: #fff;
    font-size: 28px;
    position: absolute;
    right: 0;
    bottom: 10px;
  }
}
.game-box {
  width: 1200px;
  height: 450px;
  margin: 0 auto;
  border: 1px solid rgba(255, 255, 255, 0.15);
  ul {
    display: grid; /*网格布局*/
    /* 设置列数, 每一个小格子的像素*/
    grid-template-columns: repeat(40, 30px);
    /* 设置行数, 每一个小格子的像素*/
    grid-template-rows: repeat(15, 30px);
    li {
      width: 30px;
      height: 30px;
      box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.15);
    }
  }
}

.shou::before {
  background: url(../assets/images/wolf/shou.png);
}
.tou::before {
  background: url(../assets/images/wolf/tou.png);
}
.jiao::before {
  background: url(../assets/images/wolf/jiao.png) no-repeat;
}
.shen::before {
  background: url(../assets/images/wolf/shen.png) no-repeat;
}
.cate::before {
  background: url(../assets/images/mooncates/1.png) no-repeat;
}

.tou::before,
.shou::before,
.jiao::before,
.shen::before,
.cate::before {
  content: '';
  width: 30px;
  height: 30px;
  display: block;
  background-size: 30px 30px;
  /* transform: rotate(-90deg); */
}
.jiao::before {
  background-size: 30px 18px;
}

/*向右移动,要转90度*/
.r90::before {
  transform: rotate(90deg);
}
/*向下移动,要转180度*/
.r180::before {
  transform: rotate(180deg);
}
/*向左移动,要转-90度*/
.rm90::before {
  transform: rotate(-90deg);
}
/*向上移动,不用转*/

.gameover {
  width: 1779px;
  height: 875px;
  background: url(../assets/images/end.png) no-repeat;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(0.55);
  ul {
    height: 224px;
    margin-top: 300px;
    text-align: center;
    li {
      width: 164px;
      height: 224px;
      /* background: url(../assets/images/numbers/0.png) no-repeat;*/
      /* background-size: auto 224px;  */
      display: inline-block;
      margin: 0 10px;
    }
  }
  .restart {
    width: 400px;
    height: 126px;
    background: url(../assets/images/restart.png) no-repeat;
    margin: 70px auto 0;
    cursor: pointer;
  }
  p {
    font-size: 40px;
    text-align: center;
    color: #fff;
    margin-top: 50px;
    span {
      color: gold;
    }
  }
}
.gameaction {
  width: 900px;
  height: 809px;
  background: url(../assets/images/title.png) no-repeat;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(0.7);
  .btn {
    width: 400px;
    height: 126px;
    background: url(../assets/images/action.png) no-repeat;
    margin: 519px auto 0;
    cursor: pointer;
  }
}
</style>

``

posted @ 2025-02-25 15:44  刘酸酸sour  阅读(29)  评论(0)    收藏  举报