“我报名参加金石计划1期应战——分割10万奖池,这是我的第1篇文章,点击检查活动概况”

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

前言

这两天社区许多羊了个羊的web完结,尽管各种完结花里花哨,然而,并没有一个一个jy能给他说清楚到底怎样完结的,因为可怕的求知欲,自己来吧!

纲要

羊了个羊这个现象级游戏之所以能成功,不是因为他像原神相同,靠着质量、体验、剧情你爱不释手

他靠的是,让你爱不释手,人家玩的是营销,玩的是人道,或许你压根就过不了关!

他的技能完结,其实适当简略,在技能上历来没有什么高深的东西,

公然,高深的技能总是显得这么朴实无华!

最难的部分也便是算法了,我也大致的研究了一下,可是这个算法坦率的讲不是我发明的, 我只是站在巨人的膀子上

他的算法完结的难点我认为有四方面

  • 1、 初始化的随机方位算法
  • 2、 检查是否被覆算法
  • 3、 三连匹配算法
  • 4、队伍区排序算法

在线演示

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

初始化的随机方位算法

在理解算法之前,咱们先大致看元数据

他需求包含 一些必备的属性, 默许的掩盖状况,是否被选中的状况,icon 图标,icon 的唯一id x 坐标 y坐标

const scene=({
      isCover: false, // 默许都是没有被掩盖的
      status: 0,// 是否被选中的状况
      icon,// 图标
      id: randomString(4), // 生成随机id
      x: column * 100 + offset, //x 坐标
      y: row * 100 + offset,// y坐标
    }

然后再来说算法,他的算法,本质上其实便是限制的画布内,随机生成方位

在当时这个算法中他运用一个8×8的网格中,生成方块,然后运用随机偏移量,来构成随机堆叠的姿态

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

// 以下感谢大佬们供给的算法
const makeScene = (level) => {
  // 获取当时关卡
  const curLevel = Math.min(maxLevel, level);
  // 获取当时关卡应该具有的icon数量
  const iconPool = icons.slice(0, 2 * curLevel);
  // 算出偏移量规模具体细节规模
  const offsetPool = [0, 25, -25, 50, -50].slice(0, 1 + curLevel);
  //  终究的元数据数组
  const scene = [];
  // 确定规模
  //在一般情下 translate 的偏移量,假如是百分比的话,是按照自身的宽度或者高度去核算的,所以最大的偏移规模是百分800%
  // 然后经过Math.random 会小于百分之八百
  // 所以就会构成当时区间的随机数
  const range = [
    [2, 6],
    [1, 6],
    [1, 7],
    [0, 7],
    [0, 8],
  ][Math.min(4, curLevel - 1)];
  const randomSet = (icon: string) => {
    // 求偏移量
    const offset = offsetPool[Math.floor(offsetPool.length * Math.random())];
    // 偏移求列数
    const row = range[0] + Math.floor((range[1] - range[0]) * Math.random());
    // 求偏移行数
    const column = range[0] + Math.floor((range[1] - range[0]) * Math.random());
    console.log(offset, row, column);
    // 生成元数据目标
    scene.push({
      isCover: false, // 默许都是没有被掩盖的
      status: 0,// 是否被选中的状况
      icon,// 图标
      id: randomString(4), // 生成随机id
      x: column * 100 + offset, //x 坐标
      y: row * 100 + offset,// y坐标
    });
  };
  // 假如级别高了就加点icon 花哨一点
  let compareLevel = curLevel;
  while (compareLevel > 0) {
    iconPool.push(...iconPool.slice(0, Math.min(10, 2 * (compareLevel - 5))));
    compareLevel -= 5;
  }
  // 生成元数据,初始状况下 iconPool的内容少生 随着添加,就会越来越难
  for (const icon of iconPool) {
    for (let i = 0; i < 6; i++) {
      randomSet(icon);
    }
  }
  // 返回元数据
  return scene;
};

解释一下, 咱们在初始化的时分, 会生成一个规模,来初始化 他的估计方位

  const range = [
    [2, 6],
    [1, 6],
    [1, 7],
    [0, 7],
    [0, 8],
  ][Math.min(4, curLevel - 1)];

range 最后的成果,就表明格子规模,这里是为了跟关卡结合,在初始化的时分 因为图标少, 所以就会在 在8×8之内的更小的格子

例如这样:

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

当关卡越来越多的时分就会如下图:

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

认为在后面关卡的时分将一切的格子撑满了为8x8

那么如何核算偏移量呢?

  const randomSet = (icon: string) => {
    // 求偏移量
    const offset = offsetPool[Math.floor(offsetPool.length * Math.random())];
    // 偏移求列数
    const row = range[0] + Math.floor((range[1] - range[0]) * Math.random());
    // 求偏移行数
    const column = range[0] + Math.floor((range[1] - range[0]) * Math.random());
    console.log(offset, row, column);
    // 生成元数据目标
    scene.push({
      isCover: false, // 默许都是没有被掩盖的
      status: 0,// 是否被选中的状况
      icon,// 图标
      id: randomString(4), // 生成随机id
      x: column * 100 + offset, //x 坐标
      y: row * 100 + offset,// y坐标
    });
  };

其实偏移量的核心便是 Math.random这个函数,来生成0-1的随机数,咱们需求求 offset根底偏移量 row列的偏移量 column行的偏移量

因为为了导致方位的全体差异,和细节差异,来达到符合预期的乱序作用,所以终究他生成的坐标需求 根底偏移和队伍偏移来结合

检查是否被覆算法

检查是否被掩盖算法其实本质上来说 ,便是祖传的磕碰检测算法

依据是否磕碰,来核算掩盖状况

代码如下:


// 检查是否被掩盖
const checkCover = (value) => {
  // 深复制一份
  const updateScene = value.slice();
  // 是否掩盖算法
  // 遍历一切的元数据
  // 双重for循环来找到每个元素的掩盖状况
  for (let i = 0; i < updateScene.length; i++) {
    // 当时item对角坐标
    const cur = updateScene[i];
    // 先假设他都不是掩盖的
    cur.isCover = false;
    // 假如status 不为0 阐明现已被选中了,不必再判别了
    if (cur.status !== 0) continue;
    // 拿到坐标
    const { x: x1, y: y1 } = cur;
    // 为了拿到他们的对角坐标,所以要加上100
    //之所以要加上100 是因为 他的全体是800% 也便是一个格子的换算宽度是100
    const x2 = x1 + 100,
      y2 = y1 + 100;
    // 第二个来循环来判别他的掩盖状况
    for (let j = i + 1; j < updateScene.length; j++) {
      const compare = updateScene[j];
      if (compare.status !== 0) continue;
      const { x, y } = compare;
      // 处理交集也便是选中状况
      // 两区域有交集视为选中
      // 两区域不堆叠状况取反即为交集
      if (!(y + 100 <= y1 || y >= y2 || x + 100 <= x1 || x >= x2)) {
        // 因为后方呈现的元素会掩盖前方的元素,所以只要后方的元素被选中了,前方的元素就不必再判别了
        // 又因为双层循环第二层从j 开端,所以不必担心会重复判别
        cur.isCover = true;
        break;
      }
    }
  }
  scene.value = updateScene;
};

磕碰检测

所谓磕碰检测,便是核算两个东西的坐标有没有堆叠,也便是求交集

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

主要算法如下,便是比较他们的各个方向的方位

   function isButt(obj1,obj2){
         var l1=obj1.offsetLeft;
         var t1=obj1.offsetTop;
         var r1=l1+obj1.offsetWidth;
         var b1=t1+obj1.offsetHeight;
         var l2=obj2.offsetLeft;
         var t2=obj2.offsetTop;
         var r2=l2+obj2.offsetWidth;
         var b2=t2+obj2.offsetHeight;
       return!(r1<l2||b1<t2||r2<l1||b2<t1)
    }

掩盖算法完结

掩盖算法其实完结也十分简略,便是一个双重for循环 来将每个方块的方位做比较,做一个磕碰检测,从而能筛选出来被遮挡的方块

值得注意的是

  • 1、j的值需求从i+1开端,为了避免现已比较过的方块再次比较
  • 2、因为元数据的渲染,的后方物体天然的会遮挡前方物体,所以当磕碰检测成功之后是只需求遮挡前方方块即可
 for (let i = 0; i < updateScene.length; i++) {
    // 第二个来循环来判别他的掩盖状况
    for (let j = i + 1; j < updateScene.length; j++) {
      // 履行磕碰检测
    }
  }

三连匹配算法

三连匹配其实比较于前两点,就十分简略了

咱们只需求拿到相同的方块的icon名, 凑够三个直接改动方块款式即可

// 点击item
const clickSymbol = async (idx: number) => {
  // 假如现已完结了,就不处理
  if (finished.value || animating.value) return;
  // 复制一份Scene
  const symbol = scene.value[idx];
  // 掩盖了和现已在队伍里的也不处理
  if (symbol.isCover || symbol.status !== 0) return;
  //置为可以选中状况
  symbol.status = 1;
  queue.value.push(symbol);
  // 制造动画作用中避免点击
  animating.value = true;
  //三百毫秒的延迟
  await waitTimeout(300);
  // 拿到与他匹配的一切icon
  const filterSame = queue.value.filter((sb) => sb.icon === symbol.icon);
  // 选中的三个配对成功表明现已是三连了
  if (filterSame.length === 3) {
    // 因为icon的类型相同,留下队伍中的不相同的剩余内容从头赋值
    queue.value = queue.value.filter((sb) => sb.icon !== symbol.icon);
    // 躲藏iocn,dom
    for (const sb of filterSame) {
      const find = scene.value.find((i) => i.id === sb.id);
      // 将他们的状况变为2 经过opacity 属性 来躲藏icon
      if (find) find.status = 2;
    }
  }
  // 当格子沾满了,那么久表明现已失败了
  if (queue.value.length === 7) {
    tipText.value = '失败了'
    finished.value = true;
  }
  if (!scene.value.find((s) => s.status !== 2)) {
    // 假如完结一切关卡,那就过了一切关了
    if (level.value === maxLevel) {
      tipText.value = '完结应战';
      finished.value = true
      return;
    }
    //否则加一关
    level.value = level.value + 1;
    queue.value = []
    // 从头初始化
    checkCover(makeScene(level.value + 1));
  } else {
    // 处理掩盖状况
    checkCover(scene.value);
  }
  // 动画结束
  animating.value = false;
};

以上代码中,咱们只需求 改动元数据的status的状况值即可 ,然后再合作css的视觉作用,来达到消失的作用,其实dom 还是在页面中,并没有消失移除,因为元数据没变

队伍区排序算法

搞定他,就能给面试官吹晕!运用Vue3完结【羊了个羊】的算法方面全面解析!

在队伍中咱们发现假如凑够三个他需求排序,

比如说在有一个叉子,就会排在米饭的前面然后消失

完结如下:


// 队伍区排序
watchEffect(() => {
  const cache = {};
  // 经过当时的icon的标识,将相同的icon归纳到一块
  // 方便后续排序
  for (const symbol of queue.value) {
    if (cache[symbol.icon]) {
      cache[symbol.icon].push(symbol);
    } else {
      cache[symbol.icon] = [symbol];
    }
  }
  const temp = [];
  for (const symbols of Object.values(cache)) {
    temp.push(...(symbols as any));
  }
  const updateSortedQueue = {};
  let x = 50;
  // 拿到更新后的队伍区数据,核算权重
  for (const symbol of temp) {
    updateSortedQueue[symbol.id] = x;
    x += 100;
  }
  //赋值 ,这个是为了将选中的排序后的内容移动到队伍区
  sortedQueue.value = updateSortedQueue
  // 检查掩盖状况
  checkCover(scene.value);
})

他的完结原理其实便是运用缓存对队伍核算先后权重,从而核算他排序的方位,其实他的元数据或者选中顺序并没有变

只是在视觉上更改了css 的款式

总结

我想我现已讲清楚,全体羊了个羊的算法完结了 经历半响的源码检查,将全体的完结算法解读了出来,期望对源码感兴趣的大佬们有些协助!

赋上vue+ts写的一个动效的作用原理解读: vue3+TS完结满天心飘落动效

也是类似于随机生成的比如,希能协助各位大佬理解!