使用vue3重構拼圖遊戲的實現示例
前言
花了兩天時間,重構了專案中的一個拼圖小遊戲(又名數字華容道),為了方便使用抽離成了獨立元件,效果如下:
線上體驗
原始碼地址在文章最後哦!
主要重構點
原有拼圖遊戲是通過開原始碼加以改造,使用的是 vue2 。在實際專案使用一切正常,但還是存在以下痛點
- 原始碼臃腫,暴露的配置項不足,特備是和專案現有邏輯結合時體現的更加明顯
- 生成的遊戲可能出現無解情況,為了避免無解,只好寫死幾種情況然後隨機生成
- 原始碼是vue2版本,不支援vue3
最後決定使用 vue3 重新實現拼圖遊戲,著重注意以下細節
- 元件使用起來足夠簡單
- 可以自定義遊戲難度
- 支援圖片和陣列兩種模式
實現思路
無論是拼圖片還是拼數字,其原理都是要把原本打亂的陣列移動成有序狀態。網上也有很多實現數字華容的的演算法,演算法主要需要解決的就是如何生成一組 隨機且有解 的陣列,有的人可能有疑問,陣列華容道還有可能無解嗎?
如果生成的遊戲像上面這樣,那就是無解了。原理就像我們玩魔方一樣,正常情況下不管我們打亂的多亂都可以還原,但是如果我們把 某幾個塊摳出來換換位置 ,那就可能還原不了了
網上也有很多演算法可以避免生成無解的情況,但是寫的都比較高深,加上自己閱讀演算法的能力有限,最後決定按照自己的思路實現。
我的思路其實比較簡單,一句話總結就是逆向推理法,我們可以先從排列好的陣列開始,然後隨機的通過 移動 打亂其順序,這樣肯定可以保證生成的題目有解,同時可以根據打亂的步數來控制遊戲的難度,真是個一舉兩得的idear。
原始碼實現
資料存放
首先我考慮的是使用一個一維陣列來存放資料
let arr = [1,2,3,4,5,6,7,8,0] // 0 代表空白
但是這樣會出現一個問題,就是移動的時候邏輯變的相當麻煩,因為一維陣列只能記錄資料並不能記錄豎直方向的位置,這個時候我們自然就想到使用二維陣列了,比如,當我們需要生成一個3*3的遊戲時資料是這樣的:
let arr [ [1,3],[4,6],[7,0] ]
這樣我們就可以通過下面來模擬x,y軸來表示每個數字的位置。比如0的位置是(2,2),6的位置是(1,2),如果我想移動6和0的位置,只需要把他們的座標做調換即可。
移動函式
數字華容道最關鍵的互動就是使用者點選那個塊就移動哪個塊,但是需要注意的是隻有0附近的陣列可以移動。下面我們先完成移動函式
function move(x,y,moveX,moveY) { const num = state.arr[x][y]; state.arr[x][y] = state.arr[moveX][moveY]; state.arr[moveX][moveY] = num; }
是不是很簡單,其實就是把要移動的兩個數的下標給交換下位置
有了移動函式,我們就可以實現上,下,左,右的移動了
// 上移動 function moveTop(x,y) { if (x <= 0) return -1; // 開始交換位置 const okx = x - 1; move(x,okx,y); return { x: okx,}; } //下移動 function moveDown(x,y) { if (x >= level - 1) return -1; const okx = x + 1; move(x,}; } // 左移動 function moveLeft(x,y) { if (y <= 0) return -1; const oky = y - 1; move(x,x,oky); return { x,y: oky,}; } // 右移動 function moveRight(x,y) { if (y >= level - 1) return -1; const oky = y + 1; move(x,}; }
現在我們再實現一個判斷移動方向的方法,如下所示:
function shouldMove(x,y) { // 判斷向哪移動 const { emptyX,emptyY } = seekEmpty(); if (x === emptyX && y !== emptyY && Math.abs(y - emptyY) === 1) { // 說明在一個水平線上 可能是左右移動 if (y > emptyY) { moveLeft(x,y); } else { moveRight(x,y); } } if (y === emptyY && x !== emptyX && Math.abs(x - emptyX) === 1) { // 說明需要上下移動 if (x > emptyX) { moveTop(x,y); } else { moveDown(x,y); } } }
if裡面判斷的意思是如果我們點選的塊是空白快或者不是和空白快挨著的那個,那我們就不做任何處理
生成遊戲面板
其實就是隨機呼叫上移,下移,左移,右移函式,把陣列打亂
// 隨機打亂 function moveInit(diffic) { state.arr = creatArr(level); const num = diffic ? diffic : state.diffec; const fns = [moveTop,moveDown,moveLeft,moveRight]; let Index = null; let fn; for (let i = 0; i < num; i++) { Index = Math.floor(Math.random() * fns.length); // moveConsole(Index); fn = fns[Index](startX,startY); if (fn != -1) { const { x,y } = fn; startX = x; startY = y; } } }
短短几個函式,就完成了核心邏輯,還有幾個函式沒有介紹到比如判斷遊戲完成,尋找空白塊的位置,建立二維陣列大家可自行閱讀原始碼
使用vue3重構
以上邏輯貌似和vue3沒什麼關係,但是我們忽略了最重要的一點,就是 改變陣列後,檢視也就改變了 這不就是響應式嗎,使用vue3後我們可以把關於遊戲的所有邏輯放到一個js裡面,大大減少了程式碼的耦合度
const { arr,shouldMove,moveInit } = useCreateGame( gamedata.level,gamedata.difficulty,gameEndCallback );
可能有的人會有疑問?把所有邏輯抽離出來這不是很正常的操作嗎?難道用vue2的時候都不能抽離了?
但是大家不要忘記了,我們的這個陣列需要是響應式的,如果我們單獨把邏輯抽離出來那我們在js檔案裡面改變陣列還是響應式的嗎
但當我們使用vue3的composition-api時就可以再js檔案中宣告一個響應式變數,且在元件中使用時它還是響應式的
export default function useCreateGame() { //宣告一個響應式變數 ... const state = reactive({ arr: [],}); ... return { ...toRefs(state) ... } }
const { arr,gameEndCallback ); // 這個時候 arr 還是響應式的
這正是composition-api強大所在,有了composition-api我們可以任意組裝我們的邏輯程式碼了
在vue2 如果要維護一個響應式變數我們是不是就要使用vuex這種狀態管理器了,這樣就增加了程式碼的耦合度
關於vite2
現在vite已經到了vite2版本,並且官方還在飛快迭代中,使用vite2建立的專案預設是可以使用setup新特性的,比如我們可以這樣寫:
<template> <div> {{ name }} </div> </template> <script setup> import { ref } from "vue"; const name = ref('"公眾號碼不停息"'); </script>
等價於這樣寫
<template> <div> {{ name }} </div> </template> <script> import { ref } from "vue"; export default { setup() { const name = ref("公眾號碼不停息"); return { name,}; },}; </script>
看著就簡單了很多,並且在setup版本中vue又出了幾個api,感興趣的可以去官網看下,個人感覺還是挺香的。因為新語法還是實驗性質的本次程式碼重構並未使用。
原始碼地址
原始碼地址:數字華容道拼圖遊戲 歡迎start😍
最後
你可能感興趣:
基於vue-router思考🕓實現一個簡易版vue-router
基於webpack打包多頁應用,對前端工程化的思考
到此這篇關於使用vue3重構拼圖遊戲的實現示例的文章就介紹到這了,更多相關vue3重構拼圖內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!