基于vue编写一个月饼连连看游戏

 更新时间:2023年09月15日 11:40:50   作者:冰镇白干  
中秋节快要到啦,我们一起用Vue创建一个简单的连连看游戏,连连看大家一定都玩过吧,通过消除相同的图案来清理棋盘,小编将一步步引导大家完成整个游戏的制作过程,让我们开始吧,一起为中秋节增添一些互动和娱乐

"月圆花好夜,花好月圆人更圆。"

前言

中秋节快要到啦,我们一起用Vue创建一个简单的连连看游戏。

连连看大家一定都玩过吧,通过消除相同的图案来清理棋盘。上面就是我们今天要做的效果图,看上去也挺像那么回事,不过实现起来也是非常简单。

我将一步步引导大家完成整个游戏的制作过程,让我们开始吧,一起为中秋节增添一些互动和娱乐!

设计思路

在构建连连看游戏之前,首先需要考虑游戏的设计思路。

  • 游戏界面:我们将创建一个网格状的游戏界面,每个格子上都有一个图标或者数字。
  • 游戏逻辑:玩家可以点击两个相同的格子来连接它们,但是连接的路径上下左右不能有障碍物
  • 游戏结束:当所有的格子都被连接后,游戏结束。

初始棋盘

<template>
  <div class="game-board">
    <!-- 棋盘网格 -->
    <div v-for="(row,rowIndex) in grid" :key="rowIndex" class="row">
      <div 
        v-for="(cell,colIndex) in row" 
        class="cell"
        :key="colIndex"
      >
        {{grid[rowIndex][colIndex]}}
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const grid = ref<number[][]>([])
/**
 * 随机生成grid
 */
const randomizeGrid = () => {
  const rows = 10
  const cols = 10
  for(let i = 0 ; i < rows ; i ++) {
    const row = []
    for(let j = 0 ; j < cols ; j ++) {
      row.push(Math.floor(Math.random() * 3 + 1))
    }
    grid.value.push(row)
  }
}
onMounted(() => {
  randomizeGrid()
})
</script>
<style scoped lang="less">
.game-board {
  background-color: palegoldenrod;
  background-size: contain;
  .row {
    display: flex;
    .cell {
      width: 60px;
      height: 60px;
      margin: 5px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: all .3s;
    }
  }
}
</style>

这样就生成了一个随机棋盘,后续我们将数组换成对应的图片就可以达到效果。

但是上面的写法是我前面写的,后续发现一个问题,我们不能简单的随机1、2、3,我们要确保每种值都是成对存在的。为了实现这一点,可以采用以下步骤:

  • 初始化一个存储可用格子坐标的数组 availableCells
  • 遍历棋盘格子,并将每个格子的坐标添加到 availableCells
  • 随机选择一个可用格子,为其分配一个随机图案值,然后从 availableCells 中移除该格子。
  • 再次随机选择一个可用格子,并为其分配相同的图案值。
  • 重复步骤 3 和 4,直到生成了足够的图案对。
  • 将这些图案值填充到游戏棋盘中。
/**
 * 随机生成grid,确保每一个值都是成对存在的
 */
const randomizeGrid = () => {
  const rows = 10;
  const cols = 10;
  const numPairs = (rows * cols) / 2; // 总共需要的值对数  
  const igrid:number[][] = Array(rows).fill(0).map(() => Array(cols).fill(0))
  const availableCells = []; // 存储可用格子的坐标
  // 初始化可用格子的坐标数组
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      availableCells.push([i, j]);
    }
  }  
  // 随机生成成对的图案,并填充到grid中
  for (let pair = 1; pair <= numPairs; pair++) {
    // 随机选择一个可用格子
    const cellIndex = Math.floor(Math.random() * availableCells.length);
    const [row, col] = availableCells[cellIndex];
    // 随机生成一个图案
    const value = Math.floor(Math.random() * 4+ 1);
    igrid[row][col] = value;
    availableCells.splice(cellIndex, 1);
    // 随机选择一个可用格子,将相同图案填充到第二个位置
    const secondCellIndex = Math.floor(Math.random() * availableCells.length);
    const [secondRow, secondCol] = availableCells[secondCellIndex];
    igrid[secondRow][secondCol] = value;
    // 从可用格子数组中移除已经填充的格子
    availableCells.splice(secondCellIndex, 1);
  }
  grid.value = igrid
};

上面注释已经很清楚啦,如果大家有更好的方法欢迎在评论区交流 !

点击动作

上一个步骤中,我们已经获取到了每个格子的值。现在我们写一下选中逻辑,由于每次点击都需要和上一次结果进行对比,就需要将上一个点击位置存起来

现在我们要实现一下功能

const lastSelectedCell = ref<number[]>([])
const selectCell = (rowIndex: number, colIndex: number) => {
  // 上次选中了
  if(lastSelectedCell.value?.length) {
    if(canConnect(lastSelectedCell.value,[rowIndex,colIndex])) {
      console.log('可以连接~~');
      grid.value[lastSelectedCell.value[0]][lastSelectedCell.value[1]] = 0
      grid.value[rowIndex][colIndex] = 0
    }
    lastSelectedCell.value = []
  } else {
    lastSelectedCell.value = [rowIndex,colIndex]
  }
}
/**
 * 格子是否被选中
 */
const isCellSelected = (rowIndex: number, colIndex: number): boolean => {
  return lastSelectedCell.value[0] === rowIndex && lastSelectedCell.value[1] === colIndex;
};

isCellSelected是我们选中要配对的那个方格,为了是对它添加动画方便。

点击时有两种情况:

  • 正在选第一个,我们需要记录次方格,为了下一次连接配对
  • 选了第一个了,此时点击选中第二个,判断是否配对成功 (配对成功的逻辑下一步介绍)

连接判定

连接判断使用bfs来做

这里需要注意,判断逻辑需要写在for循环里面,因为需要配对的那个方格值一定不为0,也就是说目标方格只要有值就到达不了

/**
 * 使用 BFS 检查路径是否通
 */
const directions = [
  [-1, 0], // 上
  [1, 0],  // 下
  [0, -1], // 左
  [0, 1],  // 右
];
const isPathConnected = (startCell: number[], endCell: number[]): boolean => {
  const visited = new Set<string>();
  const queue: number[][] = [startCell];
  while (queue.length) {
    const [row, col] = queue.shift()!;
    // 检查四个方向的相邻格子
    for (const [dx, dy] of directions) {
      const newRow = row + dx;
      const newCol = col + dy;
      const key = `${newRow}-${newCol}`;
      if(endCell[0] === newRow && endCell[1] === newCol) return true
      if (
        newRow >= 0 &&
        newRow < grid.value.length &&
        newCol >= 0 &&
        newCol < grid.value[0].length &&
        !visited.has(key) &&
        grid.value[newRow][newCol] === 0
      ) {
        queue.push([newRow, newCol]);
        visited.add(key);
      }
    }
  }
  return false; // 没找到路径
};

此时我们就可以点击配对了,最简单的版本也就完成了,贴一下完整代码:

<template>
  <div class="game-board">
    <!-- 棋盘网格 -->
    <div v-for="(row,rowIndex) in grid" :key="rowIndex" class="row">
      <div 
        v-for="(cell,colIndex) in row" 
        class="cell"
        :class="{isSelected : isCellSelected(rowIndex,colIndex)}"
        :key="colIndex"
        @click="selectCell(rowIndex,colIndex)"
      >
        {{grid[rowIndex][colIndex]}}
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const grid = ref<number[][]>([])
const lastSelectedCell = ref<number[]>([])
const selectCell = (rowIndex: number, colIndex: number) => {
  // 上次选中了
  if(lastSelectedCell.value?.length) {
    if(canConnect(lastSelectedCell.value,[rowIndex,colIndex])) {
      console.log('可以连接~~');
      grid.value[lastSelectedCell.value[0]][lastSelectedCell.value[1]] = 0
      grid.value[rowIndex][colIndex] = 0
    }
    lastSelectedCell.value = []
  } else {
    lastSelectedCell.value = [rowIndex,colIndex]
  }
}
/**
 * 格子是否被选中
 */
 const isCellSelected = (rowIndex: number, colIndex: number): boolean => {
  return lastSelectedCell.value[0] === rowIndex && lastSelectedCell.value[1] === colIndex;
};
/**
 * 检查两个格子是否可以连接
 */
 const canConnect = (cell1: number[],cell2: number[]): boolean => {
  // 点击同一个格子
  if(cell1[0] === cell2[0] && cell1[1] === cell2[1]) {
    return false
  }
  // 是否值相同
  if(grid.value[cell1[0]][cell1[1]] !== grid.value[cell2[0]][cell2[1]]) {
    return false
  }
  // 路径是否通
  return isPathConnected(cell1,cell2)
}
/**
 * 使用 BFS 检查路径是否通
 */
 const directions = [
  [-1, 0], // 上
  [1, 0],  // 下
  [0, -1], // 左
  [0, 1],  // 右
];
const isPathConnected = (startCell: number[], endCell: number[]): boolean => {
  const visited = new Set<string>();
  const queue: number[][] = [startCell];
  while (queue.length) {
    const [row, col] = queue.shift()!;
    // 检查四个方向的相邻格子
    for (const [dx, dy] of directions) {
      const newRow = row + dx;
      const newCol = col + dy;
      const key = `${newRow}-${newCol}`;
      if(endCell[0] === newRow && endCell[1] === newCol) return true
      if (
        newRow >= 0 &&
        newRow < grid.value.length &&
        newCol >= 0 &&
        newCol < grid.value[0].length &&
        !visited.has(key) &&
        grid.value[newRow][newCol] === 0
      ) {
        queue.push([newRow, newCol]);
        visited.add(key);
      }
    }
  }
  return false; // 没找到路径
};
/**
 * 随机生成grid
 */
const randomizeGrid = () => {
  const rows = 10
  const cols = 10
  for(let i = 0 ; i < rows ; i ++) {
    const row = []
    for(let j = 0 ; j < cols ; j ++) {
      row.push(Math.floor(Math.random() * 3 + 1))
    }
    grid.value.push(row)
  }
}
onMounted(() => {
  randomizeGrid()
})
</script>
<style scoped lang="less">
.game-board {
  // width: 80vw;
  // height: 80vh;
  background-color: palegoldenrod;
  background-size: contain;
  .row {
    display: flex;
    .cell {
      width: 60px;
      height: 60px;
      margin: 5px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: all .3s;
      background-color: aquamarine;
    }
  }
}
</style>

现在效果:

引入月饼图片

现在简单功能算是完成了,但是太简单了。我们得做一个看起来高大上的练练看。

此时,直接请班里妹子画几个好看的月饼图片:

真的太好看了,项目直接升个档次。

拿ps切出来:

然后就可以根据当前格子渲染出对应的小月饼啦!

动态加载图片需要用到vite自己的方法

加了点击的动画,选中的图片放大并且添加了光圈。大家自己看一看具体实现吧,也是很简单。

小结

最后贴一下项目地址

项目地址:https://github.com/Bbbtt04/mooncake-match

主要有两个难点:

  • bfs判断连接
  • 随机生成成对的值
  • 最难的一点:找一个会手绘的妹子

大家也可以试一试自己实现一下,做出来成就感满满!

以上就是基于vue编写一个月饼连连看游戏的详细内容,更多关于vue月饼连连看的资料请关注脚本之家其它相关文章!

相关文章

  • vue路由插件之vue-route

    vue路由插件之vue-route

    这篇文章主要介绍了vue路由插件之vue-route的相关知识,本文通过实例代码给大家介绍了vue router的使用,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-06-06
  • vue-router:嵌套路由的使用方法

    vue-router:嵌套路由的使用方法

    本篇文章主要介绍了vue-router:嵌套路由的使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • Vue.js组件间通信方式总结【推荐】

    Vue.js组件间通信方式总结【推荐】

    组件之间通信分为三种:父-子;子-父;跨级组件通信。下面,就组件间如何通信做一些总结。需要的朋友可以参考下
    2018-11-11
  • 基于vue-cli 路由 实现类似tab切换效果(vue 2.0)

    基于vue-cli 路由 实现类似tab切换效果(vue 2.0)

    这篇文章主要介绍了基于vue-cli 路由 实现类似tab切换效果(vue 2.0),非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-05-05
  • Vue封装远程下拉框组件的实现示例

    Vue封装远程下拉框组件的实现示例

    本文主要介绍了Vue封装远程下拉框组件的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • vue中template的三种写法示例

    vue中template的三种写法示例

    这篇文章主要介绍了vue中template的三种写法示例,帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
    2020-10-10
  • 傻瓜式vuex语法糖kiss-vuex整理

    傻瓜式vuex语法糖kiss-vuex整理

    kiss-vuex 是一个非常简单的语法糖类库,这篇文章主要介绍了傻瓜式vuex语法糖kiss-vuex整理,非常具有实用价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-12-12
  • Element-ui中的Cascader级联选择器基础用法

    Element-ui中的Cascader级联选择器基础用法

    这篇文章主要为大家介绍了Element-ui中的Cascader级联选择器基础用法,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • vue如何实现pc和移动端布局详细代码

    vue如何实现pc和移动端布局详细代码

    这篇文章主要给大家介绍了关于vue如何实现pc和移动端布局的相关资料, Vue响应式布局适配是一种根据设备特性自动调整布局的方法,文中通过代码以及图文介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • vue自定义正在加载动画的例子

    vue自定义正在加载动画的例子

    今天小编就为大家分享一篇vue自定义正在加载动画的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11

最新评论