我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛
前言
这个游戏想必大家都没有见过吧,我这里给它取个名字叫翻牌子, 也比较贴切,就是点击一张牌子,然后将牌子周围的其它牌子翻转过来,包括上下左右的牌子,当然特殊的四个角落或者边缘的牌子,点击的时候呢,周围的翻转牌子数量就不一定是四个了。
思路
我们来说以下具体的实现思路,假设这些牌子的周围有一个大盒子,这里我给的是540 * 540
个px
,然后默认size
是5 * 5
的,这里的size
是5
,你也可以自己修改。
最外层的大盒子开启相对定位,然后里面需要渲染size * size
个元素,也就是5 * 5 = 25
个元素,这些个元素需要在这个盒子中定位放置。
<div
v-for="(item, index) in list"
:key="index"
class="item"
:style="setPostion(index)"
></div>
init() {
this.list = new Array(size * size).fill(0).map(e => ({ v: false }))
},
复制代码
因此我们有一个专门的定位函数setPostion
,用来根据具体的每个元素的索引号,来定位它们的位置。其中~~
双非运算符表示取整,以下可能看不太懂。我直接举个栗子就好明白了,假设元素的索引值为6
,那么7 / 5
取整为1
,7 % 5
取余为2
,因此就是(1, 2)
。
setPostion(index) {
const top = ~~(index / size)
const left = index % size
return {
left: `${left * 110}px`,
top: `${top * 110}px`,
}
},
复制代码
所以6
的元素的定位就是,110 * 2 = 220px
,110 * 1 = 110px
,故left
为220
,top
为110px
。
定位好了之后就是翻牌子的问题了,刚才也说了,理想情况是点击的元素和周围的四个元素都翻牌子。
但是注意特殊的地方,四个角,四个边。其中result
为需要翻转的索引,然后无非就是另外四个索引,分别为index - 1
,index + 1
,index + 5
和index - 5
。以下四个if
判断详细讲一下,index % size === size - 1
是右边的一个边,右边的边没有index + 1
的索引值,因此只有当index % size !== size - 1
的情况时,result
中才能push
这个索引值,也就是index + 1
,然后另外三个边也是同理的。
handleClick(index) {
const result = [index]
if (index % size !== size - 1) {
result.push(index + 1)
}
if (index % size !== 0) {
result.push(index - 1)
}
if (~~(index / size) !== 0) {
result.push(index - 5)
}
if (~~(index / size) !== size - 1) {
result.push(index + 5)
}
this.list.forEach((el, index) => {
if (result.includes(index)) {
el.v = !el.v
}
})
},
复制代码
拿到result
后,我们就去遍历25
个元素,当其中有一个元素的索引值在result
中,我们就将它的v
值去翻,实际上就是翻转。
为了翻转好看一点,我们给v
值为true
的元素bg
类名,且角度为Y
轴的180
度,默认元素是0
度,然后加上transition: all 0.6s;
的过渡效果。由于是all
,因此背景色也会存在过渡效果。
最后,怎么判断完全翻转呢,很简单,当list
列表中的每个元素的v
值都是true
的时候,表示已经翻转完全了,游戏就通过了,这里我们用every
是非常合理的。注意为什么需要setTimeout
延时呢,主要是想等到过渡动画完全消失时再提示游戏通过,当然你也可以利用transitionend
事件来判断成功与否。
if (this.list.every(el => el.v)) {
setTimeout(() => {
alert('成功')
this.init()
}, 600)
}
复制代码
最后贴一下完整代码。
<template>
<div id="app">
<div
v-for="(item, index) in list"
:key="index"
class="item"
:style="setPostion(index)"
:class="{ bg: item.v }"
@click="handleClick(index)"
></div>
</div>
</template>
<script>
const size = 5
export default {
name: 'App',
data() {
return {
list: [],
}
},
mounted() {
this.init()
},
methods: {
init() {
this.list = new Array(size * size).fill(0).map(e => ({ v: false }))
},
handleClick(index) {
const result = [index]
if (index % size !== size - 1) {
result.push(index + 1)
}
if (index % size !== 0) {
result.push(index - 1)
}
if (~~(index / size) !== 0) {
result.push(index - 5)
}
if (~~(index / size) !== size - 1) {
result.push(index + 5)
}
this.list.forEach((el, index) => {
if (result.includes(index)) {
el.v = !el.v
}
})
if (this.list.every(el => el.v)) {
setTimeout(() => {
alert('成功')
this.init()
}, 600)
}
},
setPostion(index) {
const top = ~~(index / size)
const left = index % size
return {
left: `${left * 110}px`,
top: `${top * 110}px`,
}
},
},
}
</script>
<style lang="scss" scoped>
#app {
width: 540px;
height: 540px;
margin: 0 auto;
border-radius: 5px;
margin-top: 50px;
position: relative;
}
.item {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #00ccff;
box-sizing: border-box;
position: absolute;
transform: rotateY(0deg);
transition: all 0.6s;
&.bg {
background-color: pink;
transform: rotateY(180deg);
}
}
</style>
复制代码
再放上一张完整通关图,实际这个游戏是有难度的。