使用vue3重構拼圖遊戲,真香!

語言: CN / TW / HK

前言

花了兩天時間,重構了項目中的一個拼圖小遊戲(又名數字華容道),為了方便使用抽離成了獨立組件,效果如下:

線上體驗

源碼地址在文章最後哦!

主要重構點

原有拼圖遊戲是通過開源代碼加以改造,使用的是 vue2 。在實際項目使用一切正常,但還是存在以下痛點

  • 源代碼臃腫,暴露的配置項不足,特備是和項目現有邏輯結合時體現的更加明顯
  • 生成的遊戲可能出現無解情況,為了避免無解,只好寫死幾種情況然後隨機生成
  • 源代碼是vue2版本,不支持vue3

最後決定使用 vue3 重新實現拼圖遊戲,着重注意以下細節

  1. 組件使用起來足夠簡單
  2. 可以自定義遊戲難度
  3. 支持圖片和數組兩種模式

實現思路

無論是拼圖片還是拼數字,其原理都是要把原本打亂的數組移動成有序狀態。網上也有很多實現數字華容的的算法,算法主要需要解決的就是如何生成一組 隨機且有解 的數組,有的人可能有疑問,數組華容道還有可能無解嗎?

如果生成的遊戲像上面這樣,那就是無解了。原理就像我們玩魔方一樣,正常情況下不管我們打亂的多亂都可以還原,但是如果我們把 某幾個塊摳出來換換位置 ,那就可能還原不了了

網上也有很多算法可以避免生成無解的情況,但是寫的都比較高深,加上自己閲讀算法的能力有限,最後決定按照自己的思路實現。

我的思路其實比較簡單,一句話總結就是逆向推理法,我們可以先從排列好的數組開始,然後隨機的通過 移動 打亂其順序,這樣肯定可以保證生成的題目有解,同時可以根據打亂的步數來控制遊戲的難度,真是個一舉兩得的idear。

源碼實現

數據存放

首先我考慮的是使用一個一維數組來存放數據

let arr = [1,2,3,4,5,6,7,8,0]  // 0 代表空白
複製代碼

但是這樣會出現一個問題,就是移動的時候邏輯變的相當麻煩,因為一維數組只能記錄數據並不能記錄豎直方向的位置,這個時候我們自然就想到使用二維數組了,比如,當我們需要生成一個3*3的遊戲時數據是這樣的:

let arr [
    [1,2,3],
    [4,5,6],
    [7,8,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, y, okx, y);
    return {
      x: okx,
      y,
    };
  }
  //下移動
  function moveDown(x, y) {
    if (x >= level - 1) return -1;
    const okx = x + 1;
    move(x, y, okx, y);
    return {
      x: okx,
      y,
    };
  }
  // 左移動

  function moveLeft(x, y) {
    if (y <= 0) return -1;
    const oky = y - 1;
    move(x, y, x, oky);
    return {
      x,
      y: oky,
    };
  }

  // 右移動
  function moveRight(x, y) {
    if (y >= level - 1) return -1;
    const oky = y + 1;
    move(x, y, x, oky);
    return {
      x,
      y: oky,
    };
  }

複製代碼

現在我們再實現一個判斷移動方向的方法,如下所示:

  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, shouldMove, moveInit } = useCreateGame(
  gamedata.level,
  gamedata.difficulty,
  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😍

最後

你可能感興趣:

如果幫助,歡迎點贊哦😁