记忆翻牌游戏——react算法学习

先看效果
 

游戏效果

为什么要使用react?

首先要说明一下,用原生js,vue都是可以的,逻辑思路是一样的,用react的原因主要是我认为react的状态管理比较方便,也比较直观。

原生的当然也可以,但是代码会很长,逻辑也不清晰。

大致思路

至于为什么要用写这个游戏呢?在我刚学习js的时候就很有天马行空的想法,想开发一款游戏装杯,奈何技术受限,当学的差不多的时候(也就是最近),突发奇想旧事重提,想到的这个游戏。

思路完全是自己琢磨的,反复测试后发现行得通。

  1. 首先是定义一个数组,[1, 1, 2, 2, 3, 3, 4, 4,]
  2. 打乱数组的顺序
  3. 将数组遍历成一个个的UI元素,对应的值就是数组中的每个值。
  4. 点击两次为一个循环,两次点击的值一样就将数组中的对应值替换重新渲染(react会自动渲染)
  5. 判断数组中的每个元素是否都被替

思路很简单,一开始我也不知道是否行得通,测试之后很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中,因为想着只是练习嘛,主要是因为懒。

 以上是全部,很有意思的

该游戏项目还有很大的灵活性,就比如:

  1. 可以通过改变数组内容控制难度
  2. 可以限定通关的条件:步数或倒计时
  3. 每次翻牌后如果两次点击结果不同,牌翻回去的时长

快去试试吧!


版权声明:本文为m0_63615227原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。