羊了个羊

215 阅读4分钟

国庆假后,摸鱼1天,vue手撸了一个简易版的羊了个羊。

github地址:github.com/yujinhongMM…

在线体验地址(手机模式下打开):yujinhongmm.github.io/yangleyang/

注:使用到的所有的图片都可以到阿里巴巴矢量图标库去下载哦。

第一关的地图绘制

  1. 首先把我们的视口宽分成8份,每份占比12.5vw。
  2. 装卡片的容器设计为87.5vw * 87.5vw,每个卡片占比12.5vw。
  3. 将容器设计为相对定位,卡片设置为绝对定位。
  4. 设计地图数组的数据格式。每个卡片应该具备绝对定位需要的z-index,top,left,以及卡片的点击状态click,卡片类型type,id等。这里以最简单的第1关为例。
export default [
    // 第一层
    {
        id: 1,
        type: 0,
        top: 12.5,
        left: 12.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 2,
        type: 0,
        top: 12.5,
        left: 37.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 3,
        type: 1,
        top: 12.5,
        left: 62.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 4,
        type: 0,
        top: 37.5,
        left:  12.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 5,
        type: 0,
        top: 37.5,
        left: 37.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 6,
        type: 1,
        top: 37.5,
        left: 62.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 7,
        type: 1,
        top: 62.5,
        left: 12.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 8,
        type: 0,
        top: 62.5,
        left: 37.5,
        click: false,
        zIndex: 1,
    },
    {
        id: 9,
        type: 2,
        top: 62.5,
        left: 62.5,
        click: false,
        zIndex: 1,
    },
    // 第2层
    {
        id: 10,
        type: 2,
        top: 12.5 + 5,
        left: 12.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 11,
        type: 2,
        top: 12.5 + 5,
        left: 37.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 12,
        type: 2,
        top: 12.5 + 5,
        left: 62.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 13,
        type: 1,
        top: 37.5 + 5,
        left:  12.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 14,
        type: 0,
        top: 37.5 + 5,
        left: 37.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 15,
        type: 2,
        top: 37.5 + 5,
        left: 62.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 16,
        type: 1,
        top: 62.5 + 5,
        left: 12.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 17,
        type: 2,
        top: 62.5 + 5,
        left: 37.5,
        click: false,
        zIndex: 2,
    },
    {
        id: 18,
        type: 1,
        top: 62.5 + 5,
        left: 62.5,
        click: false,
        zIndex: 2,
    }
]
  1. 有了地图以后我们就可以开始绘制,用for循环遍历生成卡牌,同时设置卡片的top,left,z-index。用click来判断当前的卡片的样式是亮色还是灰色。clickCard是我们点击卡片的方法,待会儿会在拾取的部分讲到。
<div class="container">
  <div 
    v-for="lattice in map"
    :key="lattice.id"
    :class="lattice.click ? 'lattice' : 'lattice gray' " 
    :style="`top: ${lattice.top}vw; left: ${lattice.left}vw; z-index: ${lattice.zIndex}`" 
    @click="() => clickCard(lattice)">
    <img :src="images[lattice.type]" class="lattice-img" />
  </div>
</div>

判断卡片能否拾取

在羊了个羊中,卡片为灰色的时候是不可以点击拾取的,亮色的时候可以拾取。我们可以通过先把所有卡片的click状态置为true,再通过2个for循环遍历去比较低z-index卡牌j是否被高z-index卡牌i档住,如果是的话将j的click置为false。

判断B是否在A下面的条件

  1. 不考虑边界
    • j的左上角在i的覆盖范围内 image.png
    • j的右上角在i的覆盖范围内 image.png
    • j的左下角在i的覆盖范围内 image.png
    • j的右下角在i的覆盖范围内 image.png
  2. 考虑边界

image.png image.png image.png image.png

代码如下:

// 计算当前map可拾取的状态
export const changeMapClick = (originMap) => {
    const map = [...originMap];
    const length = map.length;
    // 全部变成true
    for (let i = 0; i < length; i++) {
        map[i].click = true;
    }
    // 修改不可点击的状态
    // 全部变成true
    for (let i = 0; i < length; i++) {
        const left = map[i].left;
        const top = map[i].top;
        for (let j = 0; j < length; j++) {
            if (map[i].zIndex > map[j].zIndex) {
                // 判断j在不在i的范围内
                const jTop = map[j].top, jLeft = map[j].left;
                // 判断左上角
                const LT = jTop > top && jTop < top + WIDTH && jLeft > left && jLeft < left + WIDTH;
                // 判断右上角
                const RT = jTop > top && jTop < top + WIDTH && jLeft + WIDTH > left && jLeft + WIDTH < left + WIDTH;
                // 判断左下角
                const LB = jTop + WIDTH > top && jTop + WIDTH < top + WIDTH && jLeft > left && jLeft < left + WIDTH;
                // 判断右下角
                const RB = jTop + WIDTH > top && jTop + WIDTH < top + WIDTH && jLeft + WIDTH > left && jLeft + WIDTH < left + WIDTH;
                // 边界重合情况
                const boundary = 
                    (top === jTop && jLeft >= left && jLeft < left + WIDTH) ||
                    (top === jTop && jLeft <= left && jLeft + WIDTH > left) ||
                    (left === jLeft && jTop >= top && jTop < top + WIDTH) ||
                    (left === jLeft && jTop <= top && jTop + WIDTH > top)
                if (LT || RT || LB || RB || boundary) {
                    map[j].click = false;
                    continue;
                }
            }
        }
    }
    return map;
}

卡片click设置时机

  1. 初始化的时候
  2. 每次卡片被点击的时候

卡片收集框

我们卡片收集框中的卡片通过数组list来维护。通过for循环遍历到我们的视图上就行。

<div class="drawer">
  <div v-for="lattice in list" :key="lattice.id" class="lattice drawer-lattice">
    <img :src="images[lattice.type]" class="lattice-img" />
  </div>
</div>

卡片点击时需要做的事情

  1. 判断该卡片是否能够点击,能点击进行步骤2。
  2. 对countSelected(当前已选择的全部卡片数量)进行累加1。
  3. 把选择的卡牌从地图map上删除,并重新计算地图map的所有click状态。
  4. 判断当前被选择的卡片是否在list中有相同类型的卡片type。
  5. 如果有的话放在同类型卡片后面,没有的话放在list最后。
  6. 判断当前list中是否存在被消除的卡片。
  7. 判断当前list的长度是否等于7,是的话挑战失败。
  8. 判断当前countSelected(当前已选择的全部卡片数量)是否等于卡片的总数量,如果是的话挑战成功。
const clickCard = (lattice) => {
  if (lattice.click && gameState.value === GAME_STATE.PENDING) {
    countSelected++;
    map.value = changeMapClick(map.value.filter(item => item.id !== lattice.id));
    // 需要放进被选择框的列表
    const index = list.value.findIndex(item => item.type === lattice.type)
    if (index === -1) {
      list.value.push(lattice);
    } else {
      list.value.splice(index, 0, lattice);
    }
    
    setTimeout(() => {
      // 判断能不能消除
      for (let i = 0; i < list.value.length - 2; i++) {
        // 可以消除
        if (list.value[i + 2].type === list.value[i].type) {
          list.value.splice(i, 3);
        }
      }
      // 判断length是否为7
      if (list.value.length === 7) {
        gameState.value = GAME_STATE.FAIL;
      }
      if (countSelected === count.value) {
        gameState.value = GAME_STATE.SUCCESS;
      }
    }, 300)
  }
}

现在这个游戏已经基本实现了羊了个羊的第一关了。

实现地图动态生成

地图的动态生成,我做得比较简单,主要就是产生随机数。我主要是绘制了一个7 * 7(1层),6 * 6(2层),5 * 5(3层)的一个随机生成卡片固定的地图。再去判断卡片中不是3的倍数的卡片类型,随机生成该卡片类型的层数z-index(因为我这里的id是累加的,所以不会存在相同的情况,这里z-index用id生成),top,left再随机生成,最后得到动态生成的地图。

export const generateMap = () => {
    const mapList = [];
    const map = new Map([[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0]])
    let id = 0;
    for (let i = 0; i < 7; i++) {
        for (let j = 0; j < 7; j++) {
            const type = Math.floor(Math.random()*6);
            map.set(type, map.get(type) + 1)
            id++;
            const obj = {
                id,
                type,
                top: WIDTH * i,
                left: WIDTH * j,
                click: false,
                zIndex: 1,
            }
            mapList.push(obj);
        }
    }
    for (let i = 0; i < 6; i++) {
        for (let j = 0; j < 6; j++) {
            const type = Math.floor(Math.random()*6);
            map.set(type, map.get(type) + 1)
            id++;
            const obj = {
                id,
                type,
                top: WIDTH * i + WIDTH / 2,
                left: WIDTH * j + WIDTH / 2,
                click: false,
                zIndex: 2,
            }
            mapList.push(obj);
        }
    }
    for (let i = 0; i < 5; i++) {
        for (let j = 0; j < 5; j++) {
            const type = Math.floor(Math.random()*6);
            map.set(type, map.get(type) + 1)
            id++;
            const obj = {
                id,
                type,
                top: WIDTH * i + WIDTH,
                left: WIDTH * j + WIDTH,
                click: false,
                zIndex: 3,
            }
            mapList.push(obj);
        }
    }
    // 遍历map
    for (let [key, value] of map) {
        const remainder = 3 - value % 3;
        if (remainder !== 3) {
            for (let j = 0; j < remainder; j++) {
                id++;
                const random_1 = Math.floor(Math.random() * 75);
                const random_2 = Math.floor(Math.random() * 75);
                const obj = {
                    id,
                    type: key,
                    top: random_1 % WIDTH < 3 ? random_1 + 4 : random_1,
                    left: random_2 % WIDTH < 3 ? random_2 + 4 : random_2,
                    click: false,
                    zIndex: id,
                }
                mapList.push(obj);
            }
        }
    }
    
    return mapList;
}

游戏展示图

image.png

image.png

游戏的主要内容就是这些,具体的代码,有兴趣的话可以去github地址:github.com/yujinhongMM…