首頁 > 軟體

Vue2+JS實現掃雷小遊戲

2022-06-18 18:02:06

實現步驟

1、場景佈局實現

佈局就是經典的方格佈局,對於場景的美觀度可以自行找幾個配色網站作為參考。

出現問題: 先初始化一個二維陣列對應方塊座標,然後依次渲染 or 直接通過預期的行、列數渲染空白方塊

區別: 直接初始化二維陣列,可以對座標進行一些屬性操作,例如標記、是否為地雷等等,之後操作的時候會方便很多,缺點在初始化的時候需要進行大量的計算工作(因為在點開一個安全座標時需要顯示周圍的地雷個數,還要考慮邊緣情況),而渲染空白方塊就可以在點選座標的時候再去做計算,並且在點選的時候只需要計算該方塊的屬性。

這裡我選擇了渲染空白方塊的形式。

程式碼實現

使用了 element-ui元件

template

<div class="layout">
  <div class="row" v-for="row in layoutConfig.row" :key="row">
    <div
      class="cell"
      :style="{ width:  edgeLength, height: edgeLength }"
      v-for="col in layoutConfig.cell"
      :key="col">
      <div
        class="block"
        @click="open(row, col, $event)"
        @contextmenu.prevent="sign(row, col, $event)"
      >
        // 這裡的邏輯現在可以暫時不用管,只需要先做好佈局
        <template v-if="areaSign[`${row}-${col}`] === 'fail'">
          <img src="../../assets/svg/fail.svg" alt="">
        </template>
        <template v-else-if="areaSign[`${row}-${col}`] === 'tag'">
          <img src="../../assets/svg/Flag.svg" alt="">
        </template>
        <template v-else-if="areaSign[`${row}-${col}`] === 'normal'">
        </template>
        <template v-else>
          {{areaSign[`${row}-${col}`]}}
        </template>
      </div>
    </div>
  </div>
</div>

style:

<style scoped lang="less">
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  margin-top: 100px;
  .typeChoose {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    margin-bottom: 20px;
    .item {
      margin: 0 10px;
    }
  }
  .layout {
    width: 500px;
    height: 500px;
    .row {
      display: flex;
      justify-content: center;
      align-items: center;
      .cell {
        border: 1px solid #735E30;
        caret-color: transparent;
        cursor: pointer;
        line-height: 50px;
        .block {
          height: 100%;
          background: #292E17;
        }
        .block:hover {
          background: #96875F;
        }
        .opened {
          height: 100%;
          background: #E8E0D8;
        }
      }
    }
  }
}
</style>

2、初始化事件

生成地雷隨機二維陣列

因為佈局已經通過空白方塊生成了,所以我們只需要關心生成隨機的地雷座標就可以了

程式碼實現

/*
 *  type: 當前模式的地雷個數(自己定義數量)
 *  mineList: 地雷座標陣列
 *  layoutConfig: {
 *    row: 佈局的行數
 *    col: 佈局的列數
 *  }
 */   

// 生成隨機地雷座標陣列
initMineListRange () {
  while (this.mineList.length < this.type) {
    this.initMineItem()
  }
},
// 生成單個地雷座標並且放入地雷座標陣列(mineList)中
initMineItem () {
  const position = this.initPositionRange([1, this.layoutConfig.row], [1, this.layoutConfig.cell])
  if (!this.hasPositionIn(position, this.mineList)) {
    this.mineList.push(position)
  }
},
// 生成一個在給定範圍內的隨機座標
initPositionRange ([xStart, xEnd], [yStart, yEnd]) {
  return [this.numRange(xStart, xEnd), this.numRange(yStart, yEnd)]
},
// 生成一個在給定範圍內的隨機整數
numRange (start, end) {
  return Math.floor((Math.random() * (end - start + 1))) + start
},
// 判斷引數中的 position 是否已經存在與 引數中的 positionList 中
hasPositionIn (position, positionList) {
  console.assert(position.length === 2, 'position length < 2, not a position item')
  return positionList.some(p => {
    return p[0] === position[0] && p[1] === position[1]
  })
}

3、遊戲動作(action)

指的是遊戲中的一些操作以及某個操作導致的一系列變化

點選方塊

分析:點選方塊之後會出現三種情況

  • 該方塊的九宮格範圍內沒有地雷
  • 該方塊的九宮格方位內有地雷
  • 踩雷了(game over)

對應這三種情況需要分別有不同的表現形式

第一種情況:(方塊的九宮格範圍內沒有地雷)

這種情況只需要將該方塊的樣式改為點選過的樣式即可(class="opened"

第二種情況:(方塊的九宮格方位內有地雷)

修改樣式為opened,並且需要計算周圍的地雷數量(需要考慮邊緣情況,即當前座標是否在邊緣)

第三種情況:(踩雷)

修改樣式為opened, 並且展示地雷,提示使用者遊戲結束

程式碼實現

因為在點選之前該方塊是空白物件,所以需要一個物件來儲存該方塊的屬性或者狀態(areaSign

/*
*  areaSign: Object  key: 座標('1-2') value: 狀態
*  gameProcess:當前遊戲是否處於進行狀態
*  statusEnum: 列舉 方塊狀態列舉值(fail,normal,tag)
*/

// 方塊點選事件 (傳入座標以及點選事件物件)
 open (rowIndex, colIndex, e) {
   // 判斷當前遊戲是否 
   if (!this.gameProcess) {
     this.gameEndConfirm()
     return
   }
   // 判斷當前座標是否被標記,被標記則不能被點開
   if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) {
     this.confirmMessageBox('該區域已經被標記,請選擇其他區域點選')
     return
   }
   
   e.target.className = 'opened'
   if (this.hasTouchMine([rowIndex, colIndex])) {
     // 踩雷
     this.mineTouched([rowIndex, colIndex])
   } else {
     // 第一、二種情況
     this.safeTouched([rowIndex, colIndex])
   }
 },
 // 通過傳入的座標判斷是否存在地雷座標陣列中
 hasTouchMine ([xPosition, yPosition]) {
   return this.hasPositionIn([xPosition, yPosition], this.mineList)
 },
 mineTouched (position) {
   this.setSvg(position, statusEnum.fail)
   // 遊戲失敗提示
   this.gameProcess = false
   this.gameEndConfirm()
 },
 safeTouched (position) {
   this.setTips(position)
 },
 // 把傳入座標通過判斷是否有雷設定對應提示
 setTips (position) {
   const total = this.positionAroundMineTotal(position)
   this.$set(this.areaSign, `${position[0]}-${position[1]}`, total || '')
 },
 // 把傳入座標設定為對應狀態的svg圖示
 setSvg (position, type) {
   this.$set(this.areaSign, `${position[0]}-${position[1]}`, type)
 },
 // 傳入座標與地雷座標陣列判斷是否其周圍存在雷
 positionAroundMineTotal (position) {
   const aroundPositionList = this.getAroundPosition(position[0], position[1])
   return aroundPositionList.filter(item => this.hasTouchMine(item)).length
 },
 // 獲取傳入座標的周圍九宮格座標
 getAroundPosition (xPosition, yPosition) {
   const aroundPositionList = [
     [xPosition - 1, yPosition - 1],
     [xPosition - 1, yPosition],
     [xPosition - 1, yPosition + 1],
     [xPosition, yPosition - 1],
     [xPosition, yPosition + 1],
     [xPosition + 1, yPosition - 1],
     [xPosition + 1, yPosition],
     [xPosition + 1, yPosition + 1]
   ]
   return aroundPositionList.filter(position => isInRange(position[0]) && isInRange(position[1]))
   // 判斷傳入數位是否在對應範圍中
   function isInRange (num, range = [1, 10]) {
     return num >= range[0] && num <= range[1]
   }
 }

標記座標

左鍵為點選方塊,右鍵為標記座標(第二次點選為取消標記),當該座標為標記的時候,無法進行點選,並且當剛好標記的座標陣列和地雷陣列一樣時,則遊戲結束,玩家勝利

程式碼實現

/*
*  hasWin 見下文的 vue computed
*/
sign (rowIndex, colIndex, e) {
// 判斷遊戲當前狀態
  if (!this.gameProcess) {
    this.gameEndConfirm()
    return
  }
  if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === undefined ||
    this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.normal) {
    // 當前座標 為被標記過或者以及被取消標記 觸發:新增標記
    this.setSvg([rowIndex, colIndex], statusEnum.tag)
  } else if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) {
    // 當前座標 被標記 觸發:取消標記
    this.setSvg([rowIndex, colIndex], statusEnum.normal)
  }
  console.log(this.tagList, this.mineList)
  // 檢測遊戲是否結束
  this.gameInspector()
},
// 遊戲提示
gameEndConfirm () {
  const message = this.hasWin ? '恭喜你通關,是否繼續?' : '遊戲失敗,是否重新開始?'
  this.confirmMessageBox(message, {
    callback: () => {
      this.resetGame()
    },
    cancelCallback: () => {}
  }, 'confirm')
},
// 遊戲狀態檢測員(判斷當前遊戲是否結束)
gameInspector () {
  if (this.hasWin) {
    this.gameEndConfirm()
  }
},
// 通過傳入座標返回對應格式的字串(areaSign的key值)
getAreaSignAttrWithPosition (xPosition, yPosition) {
  return `${xPosition}-${yPosition}`
},
// 通過傳入座標返回areaSign的value值(獲取該座標的狀態)
getAreaSignValueWithPosition (xPosition, yPosition) {
  return this.areaSign[this.getAreaSignAttrWithPosition(xPosition, yPosition)]
}
// 被標記列表
tagList () {
  return Object.keys(this.areaSign)
    .filter(item => this.areaSign[item] === 'tag')
    .map(attrStr => attrStr.split('-').map(str => parseInt(str)))
},
// 判斷所有的地雷是否已經被標記
hasSignAllMine () {
  return this.tagList.length === this.mineList.length &&
    this.tagList.every(tagPosition => this.hasPositionIn(tagPosition, this.mineList))
},
// 遊戲是否勝利
hasWin () {
  return this.hasSignAllMine
}

遊戲收尾

遊戲失敗或者勝利的時候需要重置遊戲

程式碼實現

resetGame () {
  this.gameProcess = true
  this.areaSign = {}
  this.mineList = []
  this.resetOpenedClass()
  // 初始化遊戲
  this.initMineListRange()
},
// 將class = "opened" 的元素改回 "block" 狀態
resetOpenedClass () {
  document.querySelectorAll('.opened').forEach(node => {
    node.className = 'block'
  })
}

總結

掃雷的實現並不複雜,首先需要對掃雷這個遊戲的機制有思路,並且可以將一些邏輯捋清楚就可以了,實現的時候再將一些邊緣狀態考慮一下。可以更多關注一下對於程式碼的封裝,對於程式碼的提煉很重要,這樣在之後繼續開發或者需要修改的時候很容易上手。

以上就是Vue2+JS實現掃雷小遊戲的詳細內容,更多關於Vue掃雷遊戲的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com