1. 程式人生 > >紳士向純原生250行拼圖小遊戲

紳士向純原生250行拼圖小遊戲

ati eight 開心 所有 rec 數據 etc 關註 切片

拼圖小遊戲

先來預覽,咳咳,這個比上次那個地鼠會好看點……

技術分享圖片

代碼是可以設置難度的,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 {
  width: 50%;
}

header {
  display: flex;
  justify-content: space-between;
}

main {
  position: relative;
  height: auto;
}

.game-area {
  position: relative;
  height: auto;
}

#background-img {
  max-width: 100%;
  max-height: 100%;
  vertical-align: top;
  opacity: 0;
}

#cut-imgs {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-wrap: wrap;
  width: 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 {
      width: ${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源碼

在線試玩

紳士向純原生250行拼圖小遊戲