Vue2 simulation snake game

Table of contents

1. Effect display

2. Code display

3. Principle explanation

3.1. Page creation

3.2. Create snakes and food

3.3. Movement and boundary judgment

3.4. Summary of eating and scoring

2. Code display

View’s local file: can be run directly.

<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>

 The tool value introduced by the above code: js file in utils

Here are the different gradient colors you will get when refreshing the page

// 渐变色
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. Principle explanation

3.1. Page creation

20x20 square array: A 20x20 array is obtained for rendering through a double-layer for loop, which is the gameFrame() method of the code.

3.2. Create snakes and food

(1) Create a snake: see initSnake() method, rendered through coordinates.

The principle of site: [9, 8, 9, 9, 9, 10] is shown in the figure below:

(2) Create food: see initFood()() method, rendered through coordinates.

When randomly generating food positions , we must consider that the food will be generated on the snake, resulting in the food being invisible. Therefore, in the sFood() method, all positions on the snake's body are excluded and food is generated again.

(3) Unified rendering:

renderBlock(a, b, n): a: block b: rendering position n: type (0 clear 1 render). Rendering takes into account rows and columns.

3.3. Movement and boundary judgment

  The movement in the four directions of moveLeft(), moveRight(), moveTop(), and moveBottom() corresponds to fx: -1, // Directions: 0 left, 1 right, 2 up, 3 down .

Take the moveLeft() method as an example:

  this.over is the boundary judgment, that is, whether it reaches the boundary of the matrix, this.fx is the direction corresponding to the left (0);

  When using a loop timer, you must first set the stop condition, otherwise the code will continue to execute;

  Let the first square of the snake move first, and the subsequent squares will be converted to the previous coordinates in turn , so that there will be the effect of the snake 'moving'. If the square moves one here, the following square will be reduced by one (that is, it will become the background color, otherwise The length of the snake's body will always increase by 1. In this case, it should be changed after the snake 'eats the food'). Please refer to the move() method;

  In the process of moving in one direction, you should consider the situation of 'encountering yourself', that is, the oneself() method, which compares the current head with the body.

3.4. Summary of eating and scoring

In the eat(i, j) method, if the snake's head is about to touch the food, it is necessary to consider turning the food into a part of the snake when the food is eaten, that is, inserting the snake's head into this point to regenerate the next food . Every time you eat it, the level and score on the right side will increase, ending at level 30.

Now that the page is loaded, the snake is moving, and the movement can be 'paused/started' through the autoMove() method.

Summary: Currently, in this simulation game, when moving to the right, a block will disappear first and then become normal. Moreover, when 'touching itself'/'reaching the boundary' in any direction, the functions of the four methods can no longer be triggered, which is bug. Hope you all can make suggestions!

Guess you like

Origin blog.csdn.net/qq_44930306/article/details/131033194
Recommended