• 绅士向纯原生250行拼图小游戏


    拼图小游戏

    先来预览,咳咳,这个比上次那个地鼠会好看点……

    预览

    代码是可以设置难度的,3就是9,9就是81……

    相比来说,此程序难度可是远远高过打地鼠的,希望小伙伴能跟上~

    html

    <header>
      <button ='Game.restart()'>重新开始</button>
      <button id="download" ='Game.openImage()'>新标签打开图片</button>
    </header>
    <main>
      <section class="game-area">
        <img id='background-img' src="#" alt="backgroundImg">
        <div id="cut-imgs"></div>
      </section>
    </main>
    

    header好理解,注意其中的“新标签打开图片”相当于过关福利,平常是隐藏的。

    之所以内容这么少,是因为主逻辑这一块的html代码许多属性都是动态的,所以写死没有价值,需要在js里面动态生成与删除,所以基本都移到js里面了,这里只要看到几个容器就行。其中#cut-imgs是下面游戏的容器。

    css

    .cut-img {
      position: absolute;
      top: 0;
      left: 0;
      border: 0;
      padding: 0;
      transition: transform .3s linear;
      box-sizing: border-box;
    }
    
    html,
    body {
      height: 100%;
      margin: 0;
    }
    
    body {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    
    header,
    main,
    footer {
       50%;
    }
    
    header {
      display: flex;
      justify-content: space-between;
    }
    
    main {
      position: relative;
      height: auto;
    }
    
    .game-area {
      position: relative;
      height: auto;
    }
    
    #background-img {
      max- 100%;
      max-height: 100%;
      vertical-align: top;
      opacity: 0;
    }
    
    #cut-imgs {
      position: absolute;
      top: 0;
      left: 0;
      display: flex;
      flex-wrap: wrap;
       100%;
      height: 100%;
    }
    
    button:focus {
      outline: none;
    }
    
    .selected {
      border: 1px solid blue;
    }
    
    #download {
      display: none;
    }
    

    css里面注意动画的设置,还有切片图像的处理。

    这里相信第一反应下面是把一整张图切9份。其实不然,不过是9个容器(本例用的是button)分别展示了不同图片的一部分,然后控制相关的容器即可。

    所有容器的位置都是左上角,设置偏移量使其在各个位置上,具体设置方法在js里面。

    js

    const Game = {
      // 重新开始游戏
      restart() {
        // 清空已有数据,重置按钮
        this.reset()
    
        const level = this.config.level
        // 计算position的参数
        const positionParam = 1 / (level - 1) * 100
        const imgUrl = this.config.imgUrl = `https://h5games-dom.oss-cn-hangzhou.aliyuncs.com/puzzle/${~~(Math.random() * 5)}.png`
        const backgroundImg = document.querySelector('#background-img')
        backgroundImg.src = imgUrl
    
        // 获取样式表
        const styleSheet = this.config.imgCutStyle = document.styleSheets[0]
    
        // 如果添加过自定义则删除
        let firstRule = styleSheet.rules[0]
        if (firstRule.selectorText === '.custom') styleSheet.deleteRule(0)
        let scale = 1 / this.config.level * 100 + '%'
    
        styleSheet.insertRule(`.custom {
           ${scale};
          height: ${scale};
          background: url(${imgUrl}) no-repeat;
          background-size: ${this.config.level * 100}%; }`, 0)
    
        backgroundImg. = () => {
          for (let i = 0, j = Math.pow(this.config.level, 2); i < j; i++) {
            this.config.cutImgsCountArray.push(i)
          }
          // DOM字符串
          let cutImgsStr = ''
          this.getInitialSort()
          this.config.cutImgsCountArray.forEach((num, index) => {
            // 保存正确的变化,做判断是否获胜的基础
            this.config.trueTransforms.push(`translate(${index % level * 100}%, ${~~(index / level) % level * 100}%)`)
    
            // 这里设置会变动的style
            const transform = `transform: translate(${num % level * 100}%, ${~~(num / level) % level * 100}%);`
            const backgroundPosition = `background-position: ${index % level * positionParam}% ${~~(index / level) % level * positionParam}%;`
    
            // 全部在左上初始位置,设置偏移量即可
            cutImgsStr += `<button class="cut-img custom" data-index=${index} ="Game.click(event)" style="${transform + backgroundPosition}"></button>`
          })
          document.querySelector('#cut-imgs').innerHTML = cutImgsStr
          this.instance.cutImgs = document.querySelectorAll('.cut-img')
        }
      },
      // 点击图片
      click(e) {
        const index = e.target.dataset.index
        // 第一次点击直接结束
        if (this.tool.currentIndex === -1) {
          this.getCutImg(index).classList.add('selected')
          this.tool.currentIndex = index
          return
        }
    
        const oldCutImg = this.getCutImg(this.tool.currentIndex)
        // 如果点击不是同一个再走逻辑
        if (this.tool.currentIndex === index) {
          this.getCutImg(index).classList.remove('selected')
          this.tool.currentIndex = -1
        } else {
          const newCutImg = this.getCutImg(index)
          const [a, b] = [newCutImg.style.transform, oldCutImg.style.transform]
          oldCutImg.style.transform = a
          newCutImg.style.transform = b
    
          this.tool.currentIndex = -1
    
          setTimeout(() => {
            download.style.display = 'none'
            oldCutImg.classList.remove('selected')
            newCutImg.classList.remove('selected')
            if (this.checkNoWin()) console.log('NoWin')
            else {
              download.style.display = 'block'
              alert('win')
            }
          }, 500);
        }
      },
      // 获取实例
      getCutImg(index) {
        return this.instance.cutImgs[index]
      },
      // 获取初始的正确排序
      getInitialSort() {
        const cal = arr => {
          let length = arr.length
          let reverse = 0
          for (let i = 0; i < length - 1; i++) {
            let n = arr[i]
            for (let j = i + 1; j < length; j++) {
              let m = arr[j]
              if (n > m) reverse += 1
            }
          }
          return reverse
        }
    
        // 数组随机排序
        const randomSort = (a, b) => Math.random() > 0.5 ? -1 : 1
    
        // 循环直到获取可还原的排序
        while (1) {
          if (cal(this.config.cutImgsCountArray.sort(randomSort)) % 2 === 0) return
        }
      },
      // 检查是否还没胜利
      checkNoWin() {
        let cutImgs = this.instance.cutImgs
        let trueTransforms = this.config.trueTransforms
        for (let i = 0, j = this.instance.cutImgs.length; i < j; i++) {
          if (cutImgs[i].style.transform !== trueTransforms[i]) return true
        }
      },
      // 清空已有数据
      reset() {
        let resetParam = this.resetParam
        this.config = this.deepCopy(resetParam.config)
        this.instance = this.deepCopy(resetParam.instance)
        this.tool = this.deepCopy(resetParam.tool)
        download.style.display = 'none'
      },
      deepCopy(obj) {
        return JSON.parse(JSON.stringify(obj))
      },
      // 打开图片
      openImage() {
        window.open(this.config.imgUrl)
      },
      // 重置时候的初始化参数
      resetParam: {
        // 配置
        config: {
          level: 3,
          cutImgsCountArray: [],
          trueTransforms: [],
          imgCutStyle: {},
          imgUrl: '',
        },
        // 实例
        instance: {
          // 所有图片的实例
          cutImgs: [],
        },
        // 记录工具
        tool: {
          currentIndex: -1
        },
      }
    }
    
    Game.restart()
    

    js就麻烦许多许多了,逻辑和功能匹配,还要用到一些冷门的知识,比如styleSheets相关知识,一直用框架,都快忘光了。

    说起来简单,就是把前后选中的容器进行transform的替换。但是需要注意是基础的业务逻辑:

    1. 第一次和下一次点击的是同一个,那么是要取消选中。

    2. 交换后,需要两个都取消选中。

    3. 重置游戏需要情况上一轮的样式,重新排版。

    4. 游戏过关的业务逻辑。

    5. 游戏难易度配置。

    6. 过关奖励,嘿嘿嘿。

    7. 等等等等。

    具体基本逻辑都在代码里面,相关注释也有加上,喜欢喜欢的小伙伴仔细看看,试试手,练一练。

    在这里就不长篇赘述了。

    祝你玩的开心。


    GitHub源码

    在线试玩

  • 相关阅读:
    hdoj 2544 最短路径
    树状数组 hdoj1166
    并查集学习
    1402大数相乘 FFT算法学习!
    hdu1014
    动态规划 简单题dp
    迷宫路径
    简单的动态规划dp
    poj 3282 (bFS)
    背包问题,看不懂,啊!!!!!!!!dp
  • 原文地址:https://www.cnblogs.com/ZweiZhao/p/9783938.html
Copyright © 2020-2023  润新知