Vue2模拟贪吃蛇小游戏

目录

一、效果展示

二、代码展示

三、原理讲解

3.1、页面创建

3.2、创建蛇与食物

3.3、移动与边界判断

3.4、吃、得分总结

二、代码展示

view的本地文件:可直接运行。

<template>
  <div class="game">
    <div class="game-div">
      <div class="game-min">
        <div v-for="(e, i) in frame" :key="i" class="row">
          <p
            v-for="(b, i) in e"
            :key="i"
            class="element"
            :style="{ background: b.bg }"
          ></p>
        </div>
      </div>
      <!-- 分数计算 -->
      <div class="right-div">
        <div>
          <p>
            <span>得分:</span>&emsp;<span style="color: red">{{ score }}</span>
          </p>
        </div>
        <div>
          <p>
            <span>等级:</span>&emsp;<span style="color: red">{{ level }}</span>
          </p>
        </div>
        <div>
          <p>
            <span>吃:</span>&emsp;<span style="color: red">{{ times }}</span>
          </p>
        </div>
        <div class="ztks" @click="autoMove">暂停/开始</div>
      </div>
    </div>
    <!-- 控制台 -->
    <div class="control">
      <p @click="moveTop">向上</p>
      <div class="control-center">
        <div @click="moveLeft">向左</div>
        <div @click="moveRight">向右</div>
      </div>
      <p @click="moveBottom">向下</p>
    </div>
  </div>
</template>

<script>
import { color } from "@/utils/color.js";
export default {
  data() {
    return {
      row: 20,
      col: 20, //列
      frame: [], //界面
      bg: "#eee", //默认背景色
      snake: {}, //蛇
      food: {}, //食物
      color: color[0],
      over: false, //边界判断
      timer: "",
      speed: 600, //速度
      fx: 0, //方向:0左 1右 2上 3下
      times: 0,
      level: 1,
      score: 0,
      site: 0, //食物生成位置
    };
  },
  mounted() {
    this.gameFrame();
    this.initSnake();
    this.initFood();
    this.autoMove();
  },
  methods: {
    //   游戏框架
    gameFrame() {
      for (let i = 0; i < this.row; i++) {
        let a = [];
        for (let j = 0; j < this.col; j++) {
          let b = {
            bg: this.bg,
          };
          a.push(b);
        }
        this.frame.push(a);
        // console.log(this.frame);
      }
    },
    //初始化蛇
    initSnake() {
      this.snake = {
        // site: [9, 0, 9, 1, 9, 2],
        site: [9, 8, 9, 9, 9, 10],
        color: this.color[6],
      };
      this.renderBlock(this.snake, this.frame, 1);
    },
    //方块渲染
    //a:方块  b:渲染位置  n:类型(0清除 1渲染)
    renderBlock(a, b, n) {
      let c = a.site;
      if (n == 1) {
        for (let i = 0; i < c.length; i += 2) {
          b[c[i]][c[i + 1]].bg = a.color;
        }
      } else if (n == 0) {
        for (let i = 0; i < c.length; i += 2) {
          b[c[i]][c[i + 1]].bg = this.bg;
        }
      }
    },
    // 食物
    initFood() {
      this.sFood();
      let site = this.site;
      let color = this.color[Math.floor(Math.random() * 7)];
      this.food = {
        site,
        color,
      };
      this.renderBlock(this.food, this.frame, 1);
    },
    // 防止食物生成在蛇身shang
    sFood() {
      this.site = [
        Math.floor(Math.random() * this.row),
        Math.floor(Math.random() * this.col),
      ];
      for (let i = 0; i < this.snake.site.length; i += 2) {
        if (
          this.snake.site[i] == this.site[0] &&
          this.snake.site[i + 1] == this.site[1]
        ) {
          this.sFood();
        }
      }
    },
    // 向左
    moveLeft() {
      if (!this.over && this.fx != 1) {
        this.fx = 0;
        clearInterval(this.timer); //避免定时器造成死循环
        this.timer = setInterval(() => {
          this.renderBlock(this.snake, this.frame, 0); //同时清空,否则的蛇长一直加1
          //   for (let i = 0; i < this.snake.site.length; i += 2) {
          //     this.snake.site[i + 1]--;
          //   }//一起动
          this.eat(this.snake.site[0], this.snake.site[1] - 1);
          this.move(); //先判断再动
          this.snake.site[1]--;
          if (this.snake.site[1] < 0) {
            this.over = true;
            console.log("撞墙了");
            this.snake.site[1]++;
            this.oneself();
            clearInterval(this.timer);
          }
          this.renderBlock(this.snake, this.frame, 1); //向左动
        }, this.speed);
      }
    },
    // 向右
    moveRight() {
      if (!this.over && this.fx != 0) {
        this.fx = 1;
        clearInterval(this.timer);
        this.timer = setInterval(() => {
          this.renderBlock(this.snake, this.frame, 0); //同时清空
          this.eat(this.snake.site[0], this.snake.site[1] + 1);
          this.move();
          this.snake.site[1]++;
          if (this.snake.site[1] >= this.col) {
            this.over = true;
            this.snake.site[1]--;
            this.oneself();
            clearInterval(this.timer);
          }
          this.renderBlock(this.snake, this.frame, 1);
        }, this.speed);
      }
    },
    // 向上
    moveTop() {
      if (!this.over && this.fx != 3) {
        this.fx = 2;
        clearInterval(this.timer);
        this.timer = setInterval(() => {
          this.renderBlock(this.snake, this.frame, 0);
          this.eat(this.snake.site[0] - 1, this.snake.site[1]);
          this.move();
          this.snake.site[0]--;
          if (this.snake.site[0] < 0) {
            this.over = true;
            this.snake.site[0]++;
            this.oneself();
            clearInterval(this.timer);
          }
          this.renderBlock(this.snake, this.frame, 1);
        }, this.speed);
      }
    },
    // 向下
    moveBottom() {
      if (!this.over && this.fx != 2) {
        this.fx = 3;
        clearInterval(this.timer);
        this.timer = setInterval(() => {
          this.renderBlock(this.snake, this.frame, 0);
          this.eat(this.snake.site[0] + 1, this.snake.site[1]);
          this.move();
          this.snake.site[0]++;
          if (this.snake.site[0] >= this.row) {
            this.over = true;
            this.snake.site[0]--;
            this.oneself();
            clearInterval(this.timer);
          }
          this.renderBlock(this.snake, this.frame, 1);
        }, this.speed);
      }
    },
    // 蛇身运动
    move() {
      //原理:上面4个方法是第一个方块移动,当第一个移动,后面的方块依次转化为前面的坐标
      for (let i = this.snake.site.length - 1; i > 1; i -= 2) {
        this.snake.site[i] = this.snake.site[i - 2];
        this.snake.site[i - 1] = this.snake.site[i - 3]; //纵坐标
      }
    },
    // 碰到自己
    oneself() {
      //拿当前头部来与身体对比
      let t = [this.snake.site[0], this.snake.site[1]];
      for (let i = this.snake.site.length - 1; i > 1; i -= 2) {
        if (this.snake.site[i] == t[1] && this.snake.site[i - 1] == t[0]) {
          clearInterval(this.timer);
          this.over = true;
          console.log("碰到自己");
        }
      }
    },
    // 吃
    eat(i, j) {
      if (i == this.food.site[0] && j == this.food.site[1]) {
        // 从蛇头部插入该点
        this.snake.site.unshift(this.food.site[0], this.food.site[1]);
        // 重新生成下一个食物
        this.initFood();
        this.times++;
        let lev = Math.floor(this.times / 10) + 1;
        if (lev > this.level) {
          this.level = lev;
          // 算速度
          if (this.level < 15) {
            // 20级以内用简单的减法
            this.speed = 600 - (this.level - 1) * 40;
          } else {
            // 当大于该等级时速度不变
            this.speed = 30;
          }
          clearInterval(this.timer);
          this.autoMove();
        }
        // 算分
        this.score += this.level * 100;
      }
    },
    // 自动移动
    autoMove() {
      if (this.timer) {
        // 暂停
        clearInterval(this.timer);
        this.timer = "";
      } else {
        //   移动
        if (this.fx == 0) {
          this.moveLeft();
        } else if (this.fx == 2) {
          this.moveTop();
        } else if (this.fx == 1) {
          this.moveRight();
        } else if (this.fx == 3) {
          this.moveBottom();
        }
      }
    },
  },
};
</script>

<style lang='less' scoped>
.game {
  .control {
    width: 300px;
    p {
      width: 300px;
      height: 40px;
      line-height: 40px;
      background-color: yellow;
    }
    .control-center {
      align-items: center;
      display: flex;
      justify-content: space-between;
      div {
        width: 100px;
        height: 40px;
        line-height: 40px;
        background-color: yellow;
      }
    }
  }
  .game-div {
    display: flex;
    .game-min {
      p {
        padding: 0;
        margin: 0;
      }
      .row {
        display: flex;
        padding-bottom: 2px;
        .element {
          width: 15px;
          height: 15px;
          margin-right: 2px;
        }
      }
    }
    .right-div {
      div {
        height: 20px;
        line-height: 20px;
      }
      .ztks {
        width: 100px;
        height: 40px;
        background-color: palevioletred;
        text-align: center;
        line-height: 40px;
      }
    }
  }
}
</style>

 上面代码引入的工具值:utils里的js文件

这里是刷新页面会获取的不同渐变色

// 渐变色
export const color = [
  [
    'linear-gradient(180deg,#FFA7EB 0%,#F026A8 100%)',
    'linear-gradient(180deg,#DFA1FF 0%,#9A36F0 100%)',
    'linear-gradient(180deg,#9EAAFF 0%,#3846F4 100%)',
    'linear-gradient(180deg,#7BE7FF 0%,#1E85E2 100%)',
    'linear-gradient(180deg,#89FEDB 0%,#18C997 100%)',
    'linear-gradient(180deg,#FFED48 0%,#FD9E16 100%)',
    'linear-gradient(180deg,#FFBA8D 0%,#EB6423 100%)',
  ],
]

三、原理讲解

3.1、页面创建

20x20方格阵:由双层for循环得到一个20x20的数组进行渲染,即代码的gameFrame()方法。

3.2、创建蛇与食物

(1)创建蛇:见initSnake()方法,通过坐标渲染。

这里的site: [9, 8, 9, 9, 9, 10]原理见下图:

(2)创建食物:见initFood()()方法,通过坐标渲染。

这里在随机生成食物位置时,要考虑食物会生成在蛇身上,导致食物看不见的情况,所以在sFood()方法里排除蛇身体的所有位置,再生成食物。

(3)统一渲染:

renderBlock(a, b, n):a:方块  b:渲染位置  n:类型(0清除 1渲染)。通过考虑行、列进行渲染。

3.3、移动与边界判断

  moveLeft()、moveRight()、moveTop()、moveBottom()四个方向的移动对应fx: -1, //方向:0左 1右 2上 3下的4个方向。

以moveLeft()方法为例:

  this.over是边界判断,即是否到达矩阵的边界,this.fx是向左对应的方向(0);

  在使用循环定时器时要先进行停止的条件,不然代码会一直执行;

  先让蛇的第一个方块移动,后面的方块依次转化为前面的坐标,这样就会有蛇‘移动’的效果,这里方块移动一个,后面的就得少一个(即变为背景色,不然蛇身长度会一直加1,这种情况应该 在蛇‘吃掉食物’后再进行变化),这个参考move()方法;

  在一个方向移动的过程中应考虑‘碰到自己’的情况,即oneself()方法,就是拿当前头部与身体对比。

3.4、吃、得分总结

在eat(i, j)方法里如果蛇头即将与食物触碰,就要考虑将食物吃掉时,把食物变成蛇的一部分,即从蛇头部插入该点,重新生成下一个食物。每吃掉一次,右侧的等级与分数就会增加,30级截止。

现在页面加载出来,蛇是运动的,通过autoMove()方法可以将运动‘暂停/开始’。

总结:目前,这个模拟游戏在向右时有一个方块会先消失,再变为正常,而且在任何一个方向‘碰到自己’/'到达边界'时就无法再触发四个方法的函数,属于bug。希望大家可以提出建议!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值