当前位置 : 主页 > 网络编程 > JavaScript >

基于JavaScript编写一个翻卡游戏

来源:互联网 收集:自由互联 发布时间:2023-03-22
目录 前言 翻卡动画 生成随机分布数组 均匀元素下的随机算法 不均匀元素下的随机算法 生成最终数组 点击事件 完整代码 前言 首先将这个游戏需求拆分成三个部分: 翻卡动画 生成随
目录
  • 前言
  • 翻卡动画
  • 生成随机分布数组
    • 均匀元素下的随机算法
    • 不均匀元素下的随机算法
    • 生成最终数组
  • 点击事件
    • 完整代码

      前言

      首先将这个游戏需求拆分成三个部分:

      • 翻卡动画
      • 生成随机分布数组
      • 点击事件

      翻卡动画

      假如我们的盒子模型不是个二维的平面,而是有个三维的体积,让它可以有正反两面,那我们在做的时候是不是只要将它真实的翻个面就可以了。让我们来想想将它变成三维的方法。 之后发现了这个属性:

      transform: translateZ(1px);
      

      使用了它,就可以把盒子内部的元素与盒子的底部撑出个高度。

      <!-- html --> 
      <div class="card">
          <div class="top">我是正面哦~</div>
      </div>

      只用给叫做“top”的子盒子一个“距离父亲的距离”,再将叫做“card”的父盒子预先翻转180度rotateY(180deg),等到点击的时候给它翻回来transform: rotateY(0)就可以了。

      .card{
        ...
        height: 100%;
        width: 100%;
        position: relative;
        transform-style: preserve-3d;
        transform: rotateY(180deg);
        transition: all 600ms;
        background: pink;
        &.select {
          transform: rotateY(0);
        }
        .top{
          ...
          height: 100%;
          width: 100%;
          position: absolute;
          top: 0;
          left: 0;
          box-sizing: border-box;
          background: white;
          border: 2px solid #b6a6dc;
          transform: translateZ(1px);
        }
      }

      生成随机分布数组

      我们先来说下在理想环境中,每个元素都能匀均出现(次数相等)的情况。再来说下不能均匀出现的情况下,怎样最大限度的均匀。

      均匀元素下的随机算法

      此算法脑内模型由西塔(θ)先生友情提供

      假设我们一共需要20个元素,有5个不同类型的格子,正好每个格子出现4次。我们就有了一个待分配元素的集合W:

      const total = 20
      const icons = ['a', 'b', 'c', 'd', 'e']
      // => 得到集合W
      const W = ['a', 'a', 'a', 'a',
                 'b', 'b', 'b', 'b',
                 'c', 'c', 'c', 'c',
                 'd', 'd', 'd', 'd',
                 'e', 'e', 'e', 'e']
      

      混淆集合

      有个指针p从下标0开始,在长度为20的数组格子里面负责填图案,填图案的规律是从集合w中随机取一个元素,取完后删除该元素,p移动到下一个格子里,迭代至完成。

      function createRandomList(W: string[], total: number) {
          const list: any[] = []
          function it(time: number): any {
              if (time === 0) return list
              // 随机每次集合元素下标
              const randomNum = Math.floor(Math.random() * (W.length))
              list.push(W[randomNum]) // 新数组中加入随机到的元素
              W.splice(randomNum, 1) // 删除集合中的元素
              return it(--time)
          }
          return it(total)
      }

      我们再让这个方法灵活一点,使它的返回结果能够随便指定格式:

      // fn非必传项,默认返回原数据
      function createRandomList(W: string[], total: number, fn: (<T>(icon: string, index?: number) => T) = icon => icon) {
          const list: any[] = []
          // 迭代器
          function it(time: number): any {
              if (time === 0) return list
              // 随机每次集合元素下标
              const randomNum = Math.floor(Math.random() * (W.length))
              list.push(fn(W[randomNum], total-time)) // 将元素和下标传入fn中,将fn的计算结果加入到新数组中
              W.splice(randomNum, 1) // 删除集合中的元素
              return it(--time)
          }
          return it(total)
      }

      不均匀元素下的随机算法

      const W = []
      

      不均匀元素,其实就是集合W里的元素分布规则改变了,混淆算法仍然不受影响。之后,让我们来思考下怎么定义一个“不均匀中的最大程度均匀”的集合。 将集合W分为两个部分: 最大可均匀分布部分 + 随机部分

      最大可均匀分布的部分,它代表着icons中的每个元素都能出现相同的最多偶数次。可以这样得到它:

      • icons个数x2,得到完整一次配对需要多少格子
      • 总格子数 / 一次完整格子数,得到可以完整配对的最大次数n
      • 循环n次,每次循环往W里添加icons x 2
      // 得到最大重复次数
      const times = Math.floor(total / (icons.length * 2))
      for (let index = 0; index < times; index++)
          W.push(...icons, ...icons)
      

      剩下的是需要随机分布的部分,它代表着,某几个元素可以在这里出现2次,剩下的则不会出现。

      • 总格子数 % icons个数x2, 得到剩下未分配的格子
      • 未分配格子 / 2, 就是需要随机从icons中取出的元素个数n,这个n一定小于icons的个数
      • 从icons中随机取n个数,可以采用每取一个数,将该数从原集合删除,重复n次的方法
      • 将得到的n个数x2,往W里添加

      第(3)条是不是听起来很耳熟,好像前面做过,没错就是前面写的createRandomList函数,W集合变成了icons,total变成了需要的个数n。

      // 剩下未分配的格子个数
      const lastCount = total % (icons.length * 2)
      // 从icons中随机获取n个数
      const lastList = createRandomList(icons, lastCount / 2)
      W.push(...lastList, ...lastList)
      

      合在一起就是就是创建W的方法:

      function createW(icons: string[], total: number) {
          const times = Math.floor(total / (icons.length * 2))
          const lastCount = total % (icons.length * 2)
          const W = []
          for (let index = 0; index < times; index++)
              W.push(...icons, ...icons)
          const lastList = createRandomList(icons, lastCount / 2)
          W.push(...lastList, ...lastList)
          return W
      }

      生成最终数组

      完整的生成随机数组代码:

      function createW(icons: string[], total: number) {
          const times = Math.floor(total / (icons.length * 2))
          const lastCount = total % (icons.length * 2)
          const W = []
          for (let index = 0; index < times; index++)
              W.push(...icons, ...icons)
          const lastList = createRandomList(icons, lastCount / 2)
          W.push(...lastList, ...lastList)
          return W
      }
      function createRandomList(W: string[], total: number, fn: (<T>(icon: string, index?: number) => T) = icon => icon) {
          const list: any[] = []
          function it(time: number): any {
              if (time === 0) return list
              const randomNum = Math.floor(Math.random() * (W.length))
              list.push(fn(W[randomNum], total-time))
              W.splice(randomNum, 1)
              return it(--time)
          }
          return it(total)
      }
      
      // ['a', 'b', 'c', "d"] => ['c', 'd'...x15...'b', 'c', 'a']
      createRandomList(createW(icons, total), total)
      

      点击事件

      乱序的随机数组有了,点一点还不简单吗! 先让生成的数组属性更丰富一些,来帮助我们展示内容。

      type CardItem = { icon: string; isDel: boolean; isSelect: boolean, index: number }
      
      let list: CardItem[] = []
      
      // isSelect属性判断是否翻转,isDel属性判断是否已经消除,icon属性标注元素属性,index用来快速找到点击元素位于数组中的位置
      list = createRandomList(createW(icons, total), total, (icon: string, index) => ({ icon, isDel: false, isSelect: false, index }))
      

      这下可以用生成的数组去展示了。接下来我们写个点击事件,接收参数是点击的数组元素:

      // isLock用来锁定动画完成前不能进行别的操作
      function handlerTap(card: CardItem) {
          if (isLock) return
          list[card.index].isSelect = true
          const selectors = list.filter(item => item.isSelect && !item.isDel)
          // 假如选择元素<2,直接返回,不走之后流程
          if (selectors.length <= 1) return
          isLock = true
          const [item1, item2] = selectors
          // 翻转动画完成后进行操作
          setTimeout(() => {
              // 如果选择的元素相同,则消除属性等于true
              if (item1.icon === item2.icon) {
                  list[item1.index].isDel = true
                  list[item2.index].isDel = true
              }
              //将所有卡牌翻转过背面
              list = list.map(item => ({...item, isSelect: false}))
              isLock = false
              // 判断是否所有卡牌都已经翻转完成
              if (list.every(item => item.isDel)) console.log( "your win!")
          }, 800)
      }

      完整代码

      100行整)。

      <script lang="ts">
          type CardItem = { icon: string; isDel: boolean; isSelect: boolean, index: number }
          const icons = ['a', 'b', 'c', "d"]
          const total = 20
          let list: CardItem[] = []
          let isLock = false
      
          function handlerTap(card: CardItem) {
              if (isLock) return
              list[card.index].isSelect = true
              const selectors = list.filter(item => item.isSelect && !item.isDel)
              if (selectors.length <= 1) return
              isLock = true
              const [item1, item2] = selectors
              setTimeout(() => {
                  if (item1.icon === item2.icon) {
                      list[item1.index].isDel = true
                      list[item2.index].isDel = true
                  }
                  list = list.map(item => ({...item, isSelect: false}))
                  isLock = false
                  if (list.every(item => item.isDel)) console.log( "your win!")
              }, 800)
          }
          function createW(icons: string[], total: number) {
              const times = Math.floor(total / (icons.length * 2))
              const lastCount = total % (icons.length * 2)
              const W = []
              for (let index = 0; index < times; index++)
                  W.push(...icons, ...icons)
              const lastList = createRandomList(icons, lastCount / 2)
              W.push(...lastList, ...lastList)
              return W
          }
          function createRandomList(W: string[], total: number, fn: (<T>(icon: string, index?: number) => T) = icon => icon) {
              const list: any[] = []
              function it(time: number): any {
                  if (time === 0) return list
                  const randomNum = Math.floor(Math.random() * (W.length))
                  list.push(fn(W[randomNum], total-time))
                  W.splice(randomNum, 1)
                  return it(--time)
              }
              return it(total)
          }
      
          list = createRandomList(createW(icons, total),
                  total,
                  (icon: string, index) => ({ icon, isDel: false, isSelect: false, index }))
      </script>
      <div class="game-box">
          {#each list as item}
              <div class="grid">
                  {#if !item.isDel}
                      <div class="card {item.isSelect && 'select'}" on:click="{() => handlerTap(item)}">
                          <div class="top">{item.icon}</div>
                      </div>
                  {/if}
              </div>
          {/each}
      </div>
      <style lang="less">
          .game-box{
            margin: 10px auto 0;
            width: 90vw;
            height: 80vh;
            display: grid;
            grid-template-columns: repeat(4, calc(100% / 4 - 3px));
            grid-template-rows: repeat(5, calc(100% / 5 - 3px));
            grid-row-gap:3px;
            grid-column-gap: 3px;
            .card{
              height: 100%;
              width: 100%;
              box-sizing: border-box;
              position: relative;
              transform-style: preserve-3d;
              transform: rotateY(180deg);
              transition: all 600ms;
              background: pink;
              &.select {
                transform: rotateY(0);
              }
              .top{
                height: 100%;
                width: 100%;
                position: absolute;
                top: 0;
                left: 0;
                box-sizing: border-box;
                display: flex;
                justify-content: center;
                align-items: center;
                background: white;
                border: 2px solid #b6a6dc;
                transform: translateZ(1px);
              }
            }
          }
      </style>

      以上就是基于JavaScript编写一个翻卡游戏的详细内容,更多关于JavaScript翻卡游戏的资料请关注自由互联其它相关文章!

      上一篇:uniapp实现下拉刷新的几种方式小结
      下一篇:没有了
      网友评论