Vue2模拟贪吃蛇小游戏

news2024/11/25 22:36:19

目录

一、效果展示

二、代码展示

三、原理讲解

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。希望大家可以提出建议!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/608568.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【架构基础】SOLID原则

SOLID原则是一套坚实而有效的软件设计原则&#xff0c;它由Robert C. Martin&#xff08;也称为 Uncle Bob&#xff09;在2000年提出&#xff0c;旨在帮助软件开发者设计出高内聚低耦合的软件&#xff0c;构建易于测试、可维护和可扩展的软件系统&#xff0c;降低软件后期的维护…

青春永不散场

虽然人生总是在不断的离别与相遇&#xff0c;但请相信这一次的离别是为了下次更美好的相遇。 一.毕业感想 四年的大学生活即将画上句号&#xff0c;让我不由得感慨万千。这四年里&#xff0c;我经历了无数的挑战和机遇&#xff0c;也结交了一群志同道合的朋友&#xff0c;收获…

抖音小程序+抖音矩阵系统开发:新玩法,新趋势

抖音seo优化源码&#xff0c;抖音seo矩阵系统搭建&#xff0c;抖音账号矩阵系统开发&#xff0c;企业在做账号矩阵过程中&#xff0c;最头疼的莫过于私域线索转化&#xff0c;作为开发者都知道&#xff0c;目前市面上我们了解的矩阵系统除了挂载POI信息外&#xff0c;无法挂载留…

【剑指offer】旋转数组的最小数字

文章目录 题目思路代码实现 题目 题目链接入口&#xff1a;JZ11 旋转数组的最小数字 思路 1.核心考点 &#xff08;1&#xff09;数组理解&#xff0c;二分查找&#xff0c;临界条件。 2.解题思路 &#xff08;1&#xff09;题目要求查找出一维数组的最小值&#xff0c;本…

变分模态分解(VMD)学习

目录 概述构造变分问题变分求解问题(引入拉格朗日)关于变分构造中的函数理解关于Uk(t)关于希尔伯特变换关于频谱调制 VMD算法(python) 概述 变分模态分解由Konstantin Dragomiretskiy于2014年提出&#xff0c;可以很好抑制EMD方法的模态混叠现象&#xff08;通过控制带宽来避免…

信息竞赛笔记(2)––快速幂

目录 快速幂 定义 分析 代码 递归实现 非递归实现(通用方法) 模意义下取幂 快速幂 定义 快速幂,二进制取幂(Binary Exponentiation,也称平方法),是一个在的时间内计算的小技巧&#xff0c;而暴力的计算需要的时间。 这个技巧也常常用在非计算的场景&#xff0c;因为它可…

【论文】通过基准分析优化联邦人员重新识别的性能

论文链接 目录 摘要1. 绪论2. 相关工作2.1 人员重新识别2.2 联邦学习 3. 联邦 个人REID基准3.1 数据集3.2 联合方案3.3 模型结构3.4 联邦学习算法3.5 性能指标3.6 参考实现4.1 通过相机联合方案4.2 按数据集联合方案 5. 性能优化5.1 知识蒸馏5.2 权重调整5.3 知识蒸馏和体重调…

提高代码质量的秘诀:类、方法、字段和包注释

&#x1f9d1;‍&#x1f4bb;CSDN主页&#xff1a;夏志121的主页 &#x1f4cb;专栏地址&#xff1a;Java基础进阶核心技术专栏 目录 &#x1f362; 一、注释的插入 &#x1f363; 二、类注释 &#x1f364; 三、方法注释 &#x1f365; 四、字段注释 &#x1f96e; 五、…

GreenPlum集群部署之抽丝剥茧

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

【stable diffusion原理解读通俗易懂,史诗级万字爆肝长文,喂到你嘴里】

文章目录 一、前言&#xff08;可跳过&#xff09;二、stable diffusion1.clip2.diffusion modelforward diffusion &#xff08;前向扩散&#xff09;逆向扩散&#xff08;reverse diffusion&#xff09;采样图阶段小结 3.Unet modeltimestep_embedding采用正余弦编码 三、sta…

华为OD机试真题 Java 实现【求符合要求的结对方式】【2023Q1 100分】,附详细解题思路

一、题目描述 用一个数组A代表程序员的工作能力&#xff0c;公司想通过结对编程的方式提高员工的能力&#xff0c;假设结对后的能力为两个员工的能力之和&#xff0c;求一共有多少种结对方式使结对后能力为N。 二、输入描述 6 2 3 3 4 5 1 6 第一行为员工的总人数&#xff…

精简总结:一文说明软件测试基础概念

基础概念-1 基础概念-2 目录 一、什么是软件测试&#xff1f; 二、软件测试的特点 三、软件测试和开发的区别 1、内容&#xff1a; 2、技能区别 3、工作环境 4、薪水 5、发展前景 6、繁忙程度 7、技能要求 四、软件测试与调试的区别 1、角色 2、目的 3、执行的阶…

Lecture 7 Deep Learning for NLP: Feedforward Networks

目录 Deep LearningFeedforward Neural Network 前馈神经网络Neuron 神经元Output Layer 输出层OptimizationRegularization 正则化Topic Classification 主题分类Language Model as Classifiers 语言模型作为分类器Word Embeddings 词嵌入Training a Feed-Forward Neural Netw…

RVOS操作系统协作式多任务切换实现-03

RVOS操作系统协作式多任务切换实现-03 任务&#xff08;task&#xff09;多任务 &#xff08;Multitask&#xff09;任务上下文&#xff08;Context&#xff09;多任务系统的分类协作式多任务 创建和初始化第 1 号任务切换到第一号任务执行协作式多任务 - 调度初始化和任务创建…

虚拟机-安装与使用2023

虚拟机-安装与使用 前言 一、虚拟机 1.VMware 2.Virtualbox 二、VMware 的下载 三、VMware 的安装 四、验证是否安装成功 五、运行 VMware 六、VMware 上安装其它操作系统 安装 Windows 10安装 CentOS-Linux安装 Kali-Linux 七、VMware 常用功能同步时间系统备份克隆快照内存设…

黑马Redis视频教程高级篇(多级缓存案例导入说明)

目录 一、安装MYSQL 1.1、准备目录 1.2、运行命令 1.3、修改配置 1.4、重启 二、导入SQL 三、导入Demo工程 3.1、分页查询商品 3.2、新增商品 3.3、修改商品 3.4、修改库存 3.5、删除商品 3.6、根据id查询商品 3.7、根据id查询库存 3.8、启动 四、导入商品查询…

Maven高级——私服(完结撒花!)

作用与介绍 一个公司内有两个项目组&#xff0c;如果其中一个开发了一个依赖tlias-utils,另一个项目组要使用的话要么就是传过来直接install放到自己的本地仓库里面的。 但是也可以搭建一个公共仓库&#xff0c;专门供公司局域网内部使用&#xff0c;也就是所谓私服。 然后在…

chatgpt赋能python:Python反向函数:在编程中的威力

Python反向函数&#xff1a;在编程中的威力 在Python中&#xff0c;反向函数是一个强大且常用的工具&#xff0c;可以帮助程序员在编写代码时更加高效和精确地处理数据。在本文中&#xff0c;我们将讨论Python反向函数的用途和实现&#xff0c;并详细介绍如何在您的代码中使用…

Java007——Java注释介绍

围绕以下3点介绍&#xff1a; 1、什么是Java注释&#xff1f; 2、Java注释的作用&#xff1f; 3、Java注释长什么样&#xff0c;以及怎么使用Java注释&#xff1f; 一、什么是Java注释&#xff1f; Java注释是在Java程序中用来描述代码的特殊语句。 注释被忽略并且不被编译器…

MySQL表的增删改查

目录 一、Create 1.insert 2.更新 3.替换 二、Retrieve&#xff08;查找&#xff09; 1.select 2.where 3. 结果排序 4. 筛选分页结果 三、Update 四、Delete 1.删除数据 2.截断表 五、聚合函数 1.count&#xff1a; 2.avg 3.sum 4.max 5.min 六、Group …