使用vue3重构拼图游戏,真香!

前言

花了两天时间,重构了项目中的一个拼图小游戏(又名数字华容道),为了方便使用抽离成了独立组件,效果以下:javascript

线上体验前端

源码地址在文章最后哦!vue

主要重构点

原有拼图游戏是经过开源代码加以改造,使用的是 vue2 。在实际项目使用一切正常,但仍是存在如下痛点java

  • 源代码臃肿,暴露的配置项不足,特备是和项目现有逻辑结合时体现的更加明显
  • 生成的游戏可能出现无解状况,为了不无解,只好写死几种状况而后随机生成
  • 源代码是vue2版本,不支持vue3

最后决定使用 vue3 从新实现拼图游戏,着重注意如下细节react

  1. 组件使用起来足够简单
  2. 能够自定义游戏难度
  3. 支持图片和数组两种模式

实现思路

不管是拼图片仍是拼数字,其原理都是要把本来打乱的数组移动成有序状态。网上也有不少实现数字华容的的算法,算法主要须要解决的就是如何生成一组 随机且有解 的数组,有的人可能有疑问,数组华容道还有可能无解吗?webpack

若是生成的游戏像上面这样,那就是无解了。原理就像咱们玩魔方同样,正常状况下无论咱们打乱的多乱均可以还原,可是若是咱们把 某几个块抠出来换换位置 ,那就可能还原不了了git

网上也有不少算法能够避免生成无解的状况,可是写的都比较高深,加上本身阅读算法的能力有限,最后决定按照本身的思路实现。github

个人思路其实比较简单,一句话总结就是逆向推理法,咱们能够先从排列好的数组开始,而后随机的经过 移动 打乱其顺序,这样确定能够保证生成的题目有解,同时能够根据打乱的步数来控制游戏的难度,真是个一箭双雕的idear。web

源码实现

数据存放

首先我考虑的是使用一个一维数组来存放数据算法

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😍

最后

你可能感兴趣:

若是帮助,欢迎点赞哦😁

相关文章
相关标签/搜索