跳至主要內容

🎮 快来翻翻看你的记忆怎么样

Azil大约 4 分钟

我正在参加 码上掘金体验活动,详情:show出你的创意代码块open in new window

我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛open in new window

前言

  • 实在没怎么写过文章,上次的游戏阳光都没普照到,希望这次能有点参与感,哈哈

简介

游戏开发契机,网上冲浪时发现一个小游戏 https://jaimegonzalezjr.com/games/memory/#/ 玩了几盘后发现跟我以前做的游戏流程几乎一致 https://juejin.cn/post/7055982382268022821 所以在很短的时间内实现出来了,技术有限代码不涉及到啥算法😓

玩法介绍

  • 🎮 经典游戏应该不用过多介绍吧
  • 👆 开始游戏尽可能多的记住棋子互相对应的位置,点击某个棋子,其余棋子将关闭显示
  • 😈 石乐志模式:降低翻开错误显示时间

在线试玩

https://code.juejin.cn/pen/7085889586542411810

代码实现

游戏涉及到的图标

https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=35711 从iconfont挑选图标添加到项目后,导入图标在线包,作为棋子图案

<script src="//at.alicdn.com/t/font_3322468_le15gvj35l.js?spm=a313x.7781069.1998910419.52&file=font_3322468_le15gvj35l.js" type="text/javascript" charset="utf-8"></script>

棋子打乱

把所有棋子图案的名称复制出来后随机打乱,注意:棋盘生成大小不能超过棋子图案*2

听说Array.sort(()=>0.5 - Math.random()) 这样洗牌是不严谨的,我也不知道对不对,能跑就行

// 打乱棋子图案,不打乱其实也行
const ICONS = ['xiangsu_aixin', 'xiangsu_xiaoya', 'xiangsu_biaoqing', 'xiangsu_meiguihua',
'xiangsu_tangguo','xiangsu_xianrenzhang', 'xiangsu_caomei', 'xiangsu_tuzi', 'xiangsu_qizhi',
'xiangsu_wuqi','xiangsu_xigua', 'xiangsu_jiangbei', 'xiangsu_yaoshui', 'xiangsu_fangzi',
'xiangsu_youxiji','xiangsu_jinbi', 'xiangsu_shu', 'xiangsu_mao', 'xiangsu_zuanshi',
'xiangsu_pijiu'].sort(() => Math.random() - 0.5)

生成棋盘

  • 棋子图标的获取与放置位置困扰了我很久,渲染棋子的时候如何获取、另一个相同的棋子放在哪
  • 后面另辟蹊径的不如根据棋盘大小先把图标出来,再渲染的时候按顺序取不就行了
const 难度 = [[3, 2], [3, 4], [5, 4], [6, 6]]

// 生成棋子图案可用区间
const max = 难度[0] * 难度[1] / 2
// 从可用棋子图案中随机抽取不重复的棋子
let iconArr = getRandomArrayValue(ICONS, max)
// 复制一份用于游戏规则并继续打乱
iconArr = [...iconArr, ...iconArr].sort(() => Math.random() - 0.5)

// 根据难度生成棋盘,渲染行
Array.from({length: 难度[0]}).map((row, index) => {
  const rowEl = doc.createElement('div')
  rowEl.className = 'game-row'
  this.el.appendChild(rowEl)
  // 渲染列
  return Array.from({length: 难度[1]}).map((child, cindex) => {
    const colEl = doc.createElement('div')
    colEl.className = 'game-col'
    rowEl.appendChild(colEl)
    const chess = doc.createElement('div')
    const id = `game-chess-${index}-${cindex+1}`
    chess.id = id
    chess.className = 'game-chess'
    // 按顺序从棋子图案可用区间抽取棋子放置
    let icon = iconArr[cindex]
    if (index) {
      icon = iconArr[index * chessboard[1] + cindex]
    }
    chess.innerHTML = `<svg class="icon" aria-hidden="true">
      <use xlink:href="#icon-${icon}"></use>
    </svg>`
    colEl.appendChild(chess)

    const obj = {
      position: [index, cindex + 1], // 记录棋子位置,用于记录翻棋记录
      el: chess, // 棋子dom对象
      status: false, // 是否正确翻开
      icon // 棋子图标,用于判断两次翻开是否正确
    }
    // 给棋子添加点击事件
    chess.addEventListener('click', () => {
      if (!this.__noClick) { // 翻棋错误的时候不允许继续点击
        this.step(obj, chess)
      }
    })
    return obj
  })
})

翻开棋子

step(chess, el) { // 棋子添加点击事件 step 函数,参数:棋子对象,点击的dom节点
 if (this.__paintInit) { // 开始游戏
   this.time()
   this.__paintInit = false
   this.el.classList.remove('loading-start')
   // TODO: 开始游戏清空图标(防止作弊功能,因动画问题未解决暂未开放)
   // const svgNodes = this.el.querySelectorAll('svg')
   // svgNodes.forEach(_ => _.remove())
 }
}

if (!chess.status) { // 落点不能是有棋子的位置
 // 翻开棋子加个动画
 el.classList.add('game-chess__focus')
 if (this.__chessA) { // 翻开第二个
   this.__chessZ = chess
   // 计算是否符合游戏规则
   const toStep = this.toStep(chess)
   if (toStep) {
     // 翻开成功就清理掉翻开的变量,把成功的棋子记录下来
     this.__chessZ = chess
     this.__chessA.status = true
     this.__chessA.el.classList.add('game-chess__close')
     this.__chessA = undefined
     this.__chessZ.status = true
     this.__chessZ.el.classList.add('game-chess__close')
     this.__chessZ = undefined
     // 翻开成功就判断是否成功完成游戏
     if (this.isSucessGame()) {
       if (this.__clearTime) {
         clearTimeout(this.__clearTime)
       }
       Object.prototype.toString.call(this.opt.success) === '[object Function]' && this.opt.success({
          ms: this.__ms,
          steps: this.__recordSteps
        })
      }
    } else {
      // 翻棋错误时清理掉翻开的棋子
      this.clearFoucs()
    }
} else {
  this.__chessA = chess
}
// 记录翻开的步骤
this.recordStep(this.__chessA, this.__chessZ)

清理翻开的棋子

clearFoucs() { // 清理翻开的棋子
  return new Promise(res => {
    // 清理时不允许点击棋子
    this.__noClick = true
    window.setTimeout(() => {
      this.__chessA?.el.classList.remove('game-chess__focus')
      this.__chessA = undefined
      this.__chessZ?.el.classList.remove('game-chess__focus')
      this.__chessZ = undefined
      this.__noClick = false
      res()
    }, this.__levelHard ? 100 : 500) // __levelHard 石乐志模式减少显示时间
  })
}

判断是否成功完成游戏

isSucessGame() { // 是否成功完成游戏
  // 验证棋子是否全部被翻开
  let num = this.opt.chessboard[0] * this.opt.chessboard[1]
  isSucess:
  for (let i = 0; i < this.chessboard.length; i++) {
  const child = this.chessboard[i]
    for (let j = 0; j < child.length; j++) {
      if (!child[j].status) { // 有一个棋子不成功的直接判定为未完成游戏,退出多重循环
        break isSucess
      } else {
        num--
      }
    }
  }
  if (num == 0) { // 剩余未翻开棋子为0则游戏成功
    return true
  }
  return false
}
上次编辑于:
贡献者: Azil