likes
comments
collection
share

<羊了个羊>第二关过不去,那就自己写一个吧。

作者站长头像
站长
· 阅读数 31

<羊了个羊>第二关过不去,那就自己写一个吧。 “我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情

随着<羊了个羊>的爆火,我们随处可见的是。地铁上的时候,做核酸排队的时候,吃饭的时候,走路的时候。无时无刻大家都在绞尽脑汁的想着咋样通过第二关。网上很多传言说为了广告,游戏方控制了通关率,是过不去的。不过,这些不重要,无所谓。既然过不去,那我就自己写一个。 王者荣耀版 海绵宝宝版 英雄联盟版... 灵动版本。

1. 先看效果

<羊了个羊>第二关过不去,那就自己写一个吧。 我是用Vue3写的,下面的话我就用Vue的代码来说这个实现思路了。 由于时间比较少,写的地方有的没有注意。有的可以优化的代码,暂时没有优化,先贴出来,后面在改喽。

2. 基础骨架

代码不少,都写在了一页里面,配合着结构图还有注释来看就会清晰很多。

<羊了个羊>第二关过不去,那就自己写一个吧。

 //  HTML 部分
 <template>
  <main
    // 一键换肤 切换背景图
    :style="{
      background: `url(/src/assets/${skinType}.jpg) no-repeat 100% 100%`,
    }"
  >
    // 一键换肤 标题
    <header>{{ headTitle }}</header>
    // 设置按钮 一键换肤
    <div class="set-img-box" @click="setEvent">
      <img src="/src/assets/set.png" alt="" class="set-class" />
    </div>
    // 主体盒子 主要的游戏区域 渲染我们的小格子
    <div class="content">
      <div class="card-box" v-if="cardList.length > 0">
     // 注意:1. 格子是否被覆盖 被覆盖就变暗 2. 格子的坐标
        <div
          v-for="item in cardList"
          :key="item.id"
          :class="!item.isShow ? 'card shadow ' : 'card'"
          :style="{
            top: item.top + 'px',
            left: item.left + 'px',
          }"
          @click="onChange(item)"
        >
        //  动态渲染格子图片
          <img :src="`/src/assets/${skinType}${item.type}.jpg`" alt="" />
        </div>
      </div>
    </div>
    <footer>
        //  清理得时候 显示的格子
      <div class="clear-box">
        <div
          class="card"
          v-for="item in clearList"
          :key="item.id"
          @click="onEvent(item)"
        >
          <img :src="`/src/assets/${skinType}${item.type}.jpg`" alt="" />
        </div>
      </div>
      //  选中的格子 满7个游戏失败
      <div class="sheep-box">
        <div v-for="item in storageBox" :key="item.id" class="card">
          <img :src="`/src/assets/${skinType}${item.type}.jpg`" alt="" />
        </div>
      </div>
      // 工具栏
      <div class="tools-box">
        <div
          class="button"
          v-for="item in buttonList"
          :key="item.id"
          @click="onTool(item.id)"
        >
          {{ item.label }}
        </div>
      </div>
    </footer>
    //  Mask 蒙版 可以使用 dialog 
    <div class="mask-box" v-if="isShowMask">
      <div class="succee-box box" v-if="gameState === 'succee'">
        <div>
          <p>恭喜您! 通关了。</p>
          <div class="button" @click="againGame">那必须的</div>
        </div>
      </div>
      <div class="error-box box" v-if="gameState === 'error'">
        <div>
          <p>又失败了,小垃圾。</p>
          <div class="button" @click="againGame">艹,再来。</div>
        </div>
      </div>
      <div class="set-box box" v-if="gameState === 'set'">
        <div>
          <p>一键换肤</p>
          <div
            class="button"
            @click="skinEvent(item)"
            v-for="item in skinList"
            :key="item.id"
          >
            {{ item.label }}
          </div>
        </div>
      </div>
    </div>
  </main>
</template>

3. 逻辑交互

重要的地方在这里我们分几个模块说一下我的思路。

3.1 基础字段

存放我们的一些基础使用字段

    const count = 72; // 总卡片数
    const cardTypeList = [1, 2, 3, 4, 5, 6];
    const buttonList = [
      { label: "清理", id: 1 },
      { label: "回退", id: 2 },
      { label: "洗牌", id: 3 },
    ];
    const skinList = [
      { label: "王者荣耀峡谷", id: 1, type: "wz" },
      { label: "海绵宝宝海洋世界", id: 2, type: "hm" },
      { label: "英雄联盟Penta Kill", id: 3, type: "yx" },
    ];

3.2 需要操作的字段

    let cardList = ref([]); // 主体格子列表
    let storageBox = ref([]); // 羊圈格子列表
    let clearList = ref([]);  // 清理到主体的列表
    let headTitle = ref("");  // 头部标题
    let skinType = ref("");   // 一键换肤类型
    const isShowMask = ref(false); // 弹出层
    const gameState = ref("");  // 游戏状态

3.3 生成card格子

   function createCard() {
      let arr = [];
      for (let i = 1; i <= count; i++) {
        let num = i / 12;
        // 一共六种card 我们一共72张 那我们就要一种12张 保证是3的倍数
        let index = Number.isInteger(num) && num != 0 ? num - 1 : Math.trunc(num);
        let card = {
          type: cardTypeList[index],
          isShow: false,
          id: i,
          // 区域内 随机生成坐标 我们通过定位展示
          top:
            (Math.floor(Math.random() * 6) + 1) * 50 -
            Math.floor(Math.random() * 50),
          left:
            (Math.floor(Math.random() * 6) + 1) * 50 -
            Math.floor(Math.random() * 50),
        };
        arr.push(card);
      }
      return arr;
}

3.4 打乱顺序

    // 传入我们 createCard() 生成的列表 进行打乱
    function randomList(arr) {
      const temp = arr.concat();
      for (let i = 0; i < temp.length; i++) {
        const idx = Math.floor(Math.random() * (temp.length - i)) + i;
        const tmp = temp[idx];
        temp[idx] = temp[i];
        temp[i] = tmp;
      }
      return temp;
    }

3.5 判断是否被覆盖被遮挡 控制可点击 高亮

    function isShowCard(arr) {
      let temp = arr.concat();
      for (let i = 0; i < temp.length; i++) {
        var _iItem = temp[i];
        for (let j = i + 1; j < temp.length; j++) {
          var _jItem = temp[j];
          let top = Math.abs(_iItem.top - _jItem.top);
          let left = Math.abs(_iItem.left - _jItem.left);
          if (top < 50 && left < 50) {
            break;
          }
          if (j === temp.length - 1) {
            temp[i].isShow = true;
          }
        }
      }
      temp[temp.length - 1].isShow = true;
      return temp;
    }

首先,外面要想。我们的数组是按照打乱后的顺序进行渲染的。也就是说不管是什么位置,是否有重叠,后渲染的层级就会在上方。然后我们就需要知道在它的后面渲染的格子,有没有和它的坐标角差的。注意:我们在初始化生成格子的时候已经默认了 false了,所以我们只需要控制高亮就可以了。

  1. 去遍历寻找当前格子后面的格子
  2. 拿到这些盒子的坐标,对比top left 。因为我们的盒子一个是 50px ,所以只要两个盒子的top left进行相减。都满足小时 50 的时候,就得出他们俩属于重叠关系。我们就可以根据这个去让它是否高亮。
  3. 因为最后一个渲染的时候,后面已经没有数据了,它也是在最前面的,所以直接高亮。

3.6 onMounted初始化渲染

    onMounted(() => {
      skinType.value = "wz";
      headTitle.value = "王者荣耀峡谷";
      initCard();
    });
    
   function initCard() {
      isShowMask.value = false;
      gameState.value = "";
      cardList.value = isShowCard(randomList(createCard()));
      storageBox.value = [];
      clearList.value = [];
 }

3.7 点击格子事的事件

    function onChange(v) {
      // 非高亮不能点击
      if (!v.isShow) return;
      // 在cardList删除这一条 
      cardList.value.forEach((element, index) => {
        if (element.id === v.id) {
          cardList.value.splice(index, 1);
        }
      });
      if (cardList.value.length > 0) {
      // 再次刷新当前层级状态 让该元素后面被遮挡的高亮
        isShowCard(cardList.value);
      }
      // 在羊圈添加它
      storageBox.value.push(v);
      // 执行一下事件判断羊圈状态
      updateStorageBox();
    }
    
    function onEvent(v) {
      clearList.value.forEach((element, index) => {
        if (element.id === v.id) {
          clearList.value.splice(index, 1);
        }
      });
      // 在羊圈添加它
      storageBox.value.push(v);
      // 执行一下事件判断羊圈状态
      updateStorageBox();
    }

3.8 点击后检测羊圈是否有满足三张的 有的话就消除

  function updateStorageBox() {
      for (let i = 0; i < storageBox.value.length; i++) {
        var count = 0;
        for (let c = i; c < storageBox.value.length; c++) {
          if (storageBox.value[i].type === storageBox.value[c].type) {
            count++;
          }
          if (count === 3) {
            var index = storageBox.value[i].type;
            storageBox.value = storageBox.value.filter(
              (item) => item.type != index
            );
          }
        }
      }
      updateStatus();
}

3.9 检测当前羊圈格子状态 是否成功或者失败

function updateStatus() {
  if (storageBox.value.length === 7) {
    isShowMask.value = true;
    gameState.value = "error";
    return;
  }
  if (
    !cardList.value.length &&
    !storageBox.value.length &&
    !clearList.value.length
  ) {
    isShowMask.value = true;
    gameState.value = "succee";
  }
}

到这一步我们的流程就算基本走完了,好好看一下,我写的其实比较简洁并不难。没有百分百还原。

3.10 工具类函数

function onTool(v) {
  if (v === 1) {
    //  清场
    clearList.value.push(...storageBox.value);
    storageBox.value.length = 0;
  }
  // 回退历史暂时还没写
  if (v === 3) {
    // 洗牌
    cardList.value = isShowCard(randomList(refreshCard(cardList.value)));
  }
}

3.11 设置函数

function skinEvent(v) {
  skinType.value = v.type;
  isShowMask.value = false;
  skinList.forEach((element) => {
    if (element.type === v.type) {
      headTitle.value = element.label;
    }
  });

  initCard();
}

3.12 动态配置图片部分

三个风格,图片路径名配置好,好动态渲染。 <羊了个羊>第二关过不去,那就自己写一个吧。

4. 样式部分

<style lang="scss" scoped>
    main {
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      padding: 15px;
    }
    header {
      text-align: center;
      font-size: 26px;
      background-color: #000000;
      color: #fff;
      border-radius: 8px;
      margin-bottom: 20px;
    }
    .set-img-box {
      width: 35px;
      height: 35px;
      .set-class {
        width: 35px;
        height: 35px;
      }
    }
    .content {
      flex: 1;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
      .card-box {
        position: relative;
        width: 100%;
        height: 400px;
        .card {
          width: 50px;
          height: 50px;
          text-align: center;
          line-height: 50px;
          font-size: 18px;
          font-weight: 700;
          border-radius: 4px;
          overflow: hidden;
          position: absolute;
          img {
            width: 100%;
            height: 100%;
          }
        }
      }
    }
    footer {
      .clear-box {
        margin: 0 auto;
        width: 300px;
        height: 50px;
        margin-bottom: 20px;
        .card {
          width: 50px;
          height: 50px;
          text-align: center;
          line-height: 50px;
          font-size: 28px;
          font-weight: 700;
          border: 1px solid #cccccc;
          float: left;
          img {
            width: 100%;
            height: 100%;
          }
        }
      }
      .sheep-box {
        margin: 0 auto;
        width: 355px;
        height: 52px;
        border: 2px solid gray;
        .card {
          width: 50px;
          height: 50px;
          text-align: center;
          line-height: 50px;
          font-size: 28px;
          font-weight: 700;
          border: 1px solid #cccccc;
          float: left;
          img {
            width: 100%;
            height: 100%;
          }
        }
      }
      .tools-box {
        width: 350px;
        height: 50px;
        margin: 0 auto;
        display: flex;
        align-items: center;
        justify-content: space-between;
        .button {
          width: 70px;
          height: 40px;
          border-radius: 8px;
          background-color: #000000;
          color: #fff;
          line-height: 40px;
          text-align: center;
          font-weight: 600;
          font-size: 18px;
        }
      }
    }
    .shadow {
      filter: brightness(40%);
    }
    .mask-box {
      position: fixed;
      width: 100%;
      height: 100%;
      left: 0;
      top: 0;
      z-index: 10;
      background: #ffffff;
      background: rgba(23, 27, 31, 0.4);
      display: flex;
      align-items: center;
      justify-content: center;
      .box {
        width: 280px;
        height: 280px;
        border-radius: 15px;
        padding: 20px;
        text-align: center;
      }
      .succee-box {
        background-color: #48bb78;
        div {
          width: 100%;
          height: 100%;
          background-color: #fff;
          display: flex;
          flex-direction: column;
          justify-content: space-around;
          align-items: center;
          border-radius: 5px;
          p {
            font-size: 25px;
            color: #48bb78;
          }
          .button {
            width: 100px;
            height: 50px;
            border-radius: 5px;
            background-color: #2182ea;
            color: #fff;
            line-height: 40px;
            text-align: center;
            font-size: 18px;
          }
        }
      }
      .error-box {
        background-color: #ff7974;
        div {
          width: 100%;
          height: 100%;
          background-color: #fff;
          display: flex;
          flex-direction: column;
          justify-content: space-around;
          align-items: center;
          border-radius: 5px;
          p {
            font-size: 25px;
            color: ff7974;
          }
          .button {
            width: 100px;
            height: 50px;
            border-radius: 5px;
            background-color: #2182ea;
            color: #fff;
            line-height: 40px;
            text-align: center;
            font-size: 18px;
          }
        }
      }
      .set-box {
        background-color: #000000;
        div {
          width: 100%;
          height: 100%;
          background-color: #fff;
          display: flex;
          flex-direction: column;
          justify-content: space-around;
          align-items: center;
          border-radius: 5px;
          p {
            font-size: 25px;
            color: ff7974;
          }
          .button {
            width: 150px;
            height: 50px;
            border-radius: 5px;
            background-color: #2182ea;
            color: #fff;
            line-height: 40px;
            text-align: center;
            font-size: 16px;
          }
        }
      }
    }
</style>

5. 结尾

源码和文件已更新到github地址: github.com/xiaoming16

转载自:https://juejin.cn/post/7145420292234412046
评论
请登录