先看效果
游戏效果
为什么要使用react?
首先要说明一下,用原生js,vue都是可以的,逻辑思路是一样的,用react的原因主要是我认为react的状态管理比较方便,也比较直观。
原生的当然也可以,但是代码会很长,逻辑也不清晰。
大致思路
至于为什么要用写这个游戏呢?在我刚学习js的时候就很有天马行空的想法,想开发一款游戏装杯,奈何技术受限,当学的差不多的时候(也就是最近),突发奇想旧事重提,想到的这个游戏。
思路完全是自己琢磨的,反复测试后发现行得通。
- 首先是定义一个数组,[1, 1, 2, 2, 3, 3, 4, 4,]
- 打乱数组的顺序
- 将数组遍历成一个个的UI元素,对应的值就是数组中的每个值。
- 点击两次为一个循环,两次点击的值一样就将数组中的对应值替换重新渲染(react会自动渲染)
- 判断数组中的每个元素是否都被替
思路很简单,一开始我也不知道是否行得通,测试之后很nice。
样式与交互
先将样式准备好,再专注于业务(不变真理)
每张卡片的html结构
<div onClick={props.fn} className="card">
<div
className={active === index || nextActive === index ? "card__content active" : "card__content"}
>
<div className="card__front">
<h1>?</h1>
</div>
<div className="card__back">
{content === "无" ? <i style={{ fontSize: "30px" }} className='iconfont icon-wancheng1' /> : <h1>{content}</h1>}
</div>
</div>
</div>
每张卡片css样式
.card {
margin: auto;
width: 20vw;
height: 150px;
perspective: 1000px;
background-color: transparent;
padding-bottom: 10px;
box-sizing: border-box;
cursor: pointer;
}
.card__content {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: all 1s;
transform-style: preserve-3d;
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.15);
}
.active {/* 被点击时的样式 */
transform: rotateY(180deg);
}
.card__front,
.card__back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.card__front {
background-color: red;
font-size: 50px;
}
.card__back {
background-color: indigo;
transform: rotateY(180deg);
font-size: 36px;
}
打乱数组函数
const shuff = (arr) => {
for (let i = 0; i < arr.length; i++) {
const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]
}
return arr
}
export default shuff
定义状态
我使用的是react中的函数式组件,下面是一些状态
const Random = [1, 1, 2, 2, 3, 3, 4, 4,]
const [arr, setArr] = useState(shuff(Random))
const [frist, setFrist] = useState(0)//每次循环第一次点击的牌号
const [crrIndex, setCrrIndex] = useState("")//每次循环第一次点击的牌的索引
const [active, setActive] = useState("")//每次循环第一张牌点击处于活动状态
const [nextActive, setNextActive] = useState("")//每次循环第二张牌点击处于活动状态
const [canRun, setCanRun] = useState(true)//节流阀
//计步器
const [step, setStep] = useState(99)
一些状态并不是我一开始就想到的,也是后面完善项目时添加的。
核心逻辑函数
每次点击卡片都会触发该函数
function clearBefor(item, index) {
//判断是否步数之内
if (step < 1) return /* alert("挑战失败!!!") */;
if (!canRun) return;
setCanRun(false)
if (item === "无") return setCanRun(true);//已经被消除的牌,点击后返回
setActive(index)//当前被掀开的牌
if (frist === 0) {
setCanRun(true)
//点击第一张
setNextActive("")
} else {
//点击第二张
setNextActive(crrIndex)
}
new Promise((resolse, reject) => {
setFrist(item)//记录每个循环第一次点击的牌号
setCrrIndex(index)//记录当前点的是哪张牌
resolse()
}).then(() => {
if (crrIndex !== index)//要确保两次点的不是同一张牌
if (frist !== item && frist !== 0) {
setStep(step => step -= 1)
//点击第二张
//每个循环点击的牌号不同时的输出,确保点两次才运算一次
console.log("F");
setFrist(0)//点击两次一循环,结束此次循环
setTimeout(() => {
//匹配失败后经过1秒回到翻拍前的状态
setActive("")
setNextActive("")
//打开节流阀
setCanRun(true)
}, 600);
} else if (frist === item && frist !== 0) {
//步数减一
setStep(step => step -= 1)
//打开节流阀
setCanRun(true)
//点击第二张
//成功匹配
console.log("T");
setActive("")
setNextActive("")
setFrist(0)//点击两次一循环,结束此次循环
let newArr = arr.map(arr_item => {
if (item === arr_item) return "无";
return arr_item
})
setArr([...newArr])
console.log(newArr);
//成功条件
if (newArr.every(element => {
return element === "无"
})) {
setTimeout(() => {
alert("恭喜你,过关了!!!")
}, 1000);
}
}
if (crrIndex === index) setCanRun(true);
})
}
大家不要被这个逻辑函数吓退了,最开始是很简短的,没办法,为了解决一些bug就加入了亿点点细节,就比如:两次点击之后要通过判断来确定是都翻转回去还是不呢?以及防乱点(节流的思想)
每一步都有注释,大致思路还是很清晰的,建议自己敲敲看。
组件的return结果
<div>
<div className="card-box">
{arr.map((item, index) => {
if (item === "无") {
return (<Card
key={index}
index={index}
content={item}
active={index}
/>)
} else {
return (
<Card
key={index}
index={index}
content={item}
fn={() => { clearBefor(item, index) }}
active={active}
nextActive={nextActive}
/>
)
}
})}
</div>
<h3>剩余步数:{step}</h3>
</div>
这里面也做了一个判断,因为在卡片上写了一个“无”真的是太low了。
目录结构
核心组件我并没有分离出来,而是写在了App.js中,因为想着只是练习嘛,主要是因为懒。
以上是全部,很有意思的
该游戏项目还有很大的灵活性,就比如:
- 可以通过改变数组内容控制难度
- 可以限定通关的条件:步数或倒计时
- 每次翻牌后如果两次点击结果不同,牌翻回去的时长
快去试试吧!
版权声明:本文为m0_63615227原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。