用 React 写一个数字华容道 你需要知道的秘密
新的一年,总要学一个新东西来迎接新的未来吧,所以选择了一直未碰的那个据说是全宇宙最牛逼的前端框架-React,在上下班的地铁上看了两天官方教程,so what。光看不练假把式,于是就想着做个什么,偶然看到一个妹妹发了一条关于玩数字华容道,根本停不下来的朋友圈,这游戏我在今年的最强大脑看过,但是就看两小天才在滑呀滑呀滑,感觉还不错,程序猿就该多玩益智类,少玩什么跳一跳。
于是去商店下了个,玩着还行,就是广告太多,而且只能玩五阶以下的,看起不难,一个想法涌于脑上,何不拿这练练手做个 Demo,毕竟我们属于智慧家族的。闲话扯完,进入正题。本文包含但不仅包括以下内容:
- Demo 开发环境
- 数字华容道里的秘密
- 讲一讲里面算法的实现
- React 的使用感受及易错点
Demo 开发环境
- React:16.2.0
- react-router-dom:4.2.2
- webpack:3.8.1
- JS:ES6
- CSS:Scss
项目目录 结构(很适合 React 入门 如果感觉不错,请留下你的 star):如下图所示
数字华容道里的秘密
玩法说明
数字华容道里在外国被称为 puzzle,译为数字推盘,最经典的就是 puzzle15 的高价悬赏 13 15 14 局面的解。怎么玩,一图胜千言,简单来说就是讲左图的乱选状态,滑成右图所示的顺序状态。个人感觉和小时候玩的推箱子有点类似。
你不知道的秘密
其实写完 Demo 的随机序列页面生成和操作交互就用了一个周天和一个周一晚上,但到现在线上这个样子,又多用了三四个晚上(白天上班,晚上学习,这就是前端的日常),为什么?因为当时基础版部署到线上,让女朋友试一把,结果玩个三阶的,都两分钟了还在折腾(我最快是 18 秒还原),怎么这么笨,于是抢过来,捣腾,再捣腾,怎么回事,感觉无解啊,于是去百度了一下,在知乎里看到了这个问题,还真的有无解的情况, 问题地址 。看图,后面还要讲(敲黑板)
噢,原来是酱紫。光随机生成一个乱序数列是不够的,还得保证这个数列的逆序数为偶数,嗦嘎。于是在随机序列的生成中又多加一个过程,判断序列逆序数奇偶性,并调整。早上地铁上自己又不断玩玩测试,三阶 ok,四阶 ok,五阶 ok,然后又一遍,一遍。原以为这样就大功告成了,但是出现了这样的画面。有图。谁刚说的四阶 ok,于是又测试了多次,发现三阶,五阶确实 ok,但四阶确实有 Bug。Why,后面自己每开一局,截一张图,无解的,标记下来,下面就是几张立功的图片。
下午年会,领导上面讲,自己下面睡。睡得天昏地暗,时过境迁,居然还在叨叨叨,自己就拿起酒店提供的纸和笔找这些数字间的秘密。首先,这几组数字都是偶逆序列,前一晚写的调整奇逆序列为偶逆序列的代码是没有问题的,那问题出在哪了。抓脑抠鼻,抖脚摇头,那一组图片来回翻阅,灵光一闪,水哥附身,原来是这样:空格项都出现在第三行,哦,不,应该是奇数行。为什么呢?
又去百度,又看到了上面知乎和豆瓣的正经说瞎话的大神(此生最讨此类人,害死个仙人),看到这,我开始怀疑,这句话的正确性。奇数阶,格子上下左右移动确实不会改变数列的逆序奇偶性。但偶数阶,格子的上下移动是会改变序列的奇偶性的,简单总结一下:
奇数阶(3x3,5x5):上移或下移一个数字,其调换的位置是偶数,所以不改变数列逆序数的奇偶性,所以奇数阶,生成的初始随机数列的逆序数必须为偶数;
偶数阶(4x4,6x6):上移或下移一个数字,其调换的位置是奇数,所以会改变数列逆序数的奇偶性,上下交换一次改变一次奇偶性,交换两次就回到初始状态。所以可以大致这样理解,偶数的平方仍然为偶数,其有数字的滑块个数为奇数个,所以有一个数字必然会和空滑块产生位置交换,如果空滑块位于奇数行(空滑块是不参于数字序列的逆序数计算的),就会产生 2n-1 次交换,其会改变数列逆序数的奇偶性;而位于偶数行,就会产生 2n 次交换,不会改变数列逆序数的奇偶性,所以用一个公式总结就是:(数列初始状态是否为偶数) === (空行是否为偶数),简单来讲就是求这两个数的异或。最后的代码流程的实现
讲一讲里面算法的实现
生成一个乱序不重复的 1~n 数组数列
方法有很多,但我知道的两种,这里分享一下,有知道其他的,请留言做个评论,让大家一起进步。
顺序数组随机性调换思路基本就是,先生成一个顺序的 1n 的顺序数组,然后再通过一个 1n 的循环来打乱这个数组,其时间复杂度是 O(n)。代码如下。
export const disorganize = (length) => { const arr = []; let temp; for (var i = 1; i < length; i++) { arr.push(i); } for (i = 0; i < length; i++) { let random = Math.round(Math.random() * (length - 2)); temp = arr[random]; arr[random] = arr[i]; arr[i] = temp; } return arr; };
随机数生成乱序数组生成一个随机数,并判断其是否在目标数组中已存在,当数组个数为 n 时,目的达到。其时间复杂度我不知,代码如下:
export const randomArr = (length) => { const arr = []; let temp; while(arr.length<(length-1)){ let random = 1+Math.round(Math.random() * (length - 2)); if(random<length && arr.indexOf(random)===-1){ arr.push(random); } } return arr; };
虽然代码看起比上面的简单,但其时间复杂度最由是 O(f(n),最差情况未知,所以,方法一更推荐,如果说的有什么毛病,还请及时指出。
数列逆序性的奇偶性判断
最先想到的就是冒泡排序,因为冒泡排序过程就是判断两个数是否逆序,是,就交换,不是,继续下一组判断。所以,我们直接将交换的次数,记为数列逆序数个数,就达到了想要的效果。当然这个题用其他排序方法也能达到目的,理解了其排序的原理,就很容易计算数列的逆序性,我这里是直接用的以前冒泡排序的算法。
const bubbleOrder=(arr)=>{ let i ,j ,count=0; const swap=(tar,lastIndex,newIndex)=>{ let temp = tar[lastIndex]; tar[lastIndex] =tar[newIndex]; tar[newIndex] = temp; count++; } for(i=0;i<arr.length;i++){ for(j=arr.length-1;j>i;j--){ (arr[j]<arr[j-1])&&swap(arr,j-1,j); } } return count; }
JS 写一个秒表
秒表是啥,start-pause-stop-reset,中间的步骤不是必须的,但前后两步必须。当然方法有很多,但都离不开 setTimeout 或则 setInterval 两个方法,requestAnimation 应该也可以。这里提供一个自己写的,当然思路来源于网上,只是用自己的思路表达出来。基于 setInterval 和 Date 对象。源码如下:
const timer=(offsetTime)=>{ //offsetTime 为 0 时,表示从 0 开始计,不为 0,表示是暂停后继续计时 const formatter=(t)=>{ const res =t>9 ? t : '0'+t return res; } let startTime = new Date().getTime(),tPass=0,tOffset=offsetTime||0; this.interId = setInterval(()=> { //this.interId 是组件下面建的一个保持定时器值的,用于暂停和停止 let tNew = new Date().getTime(),ms,sec,min,timeStr; tPass = tOffset +tNew - startTime; ms = Math.floor(tPass/10 % 100); sec = Math.floor((tPass / 1000) % 60); min = Math.floor((tPass / 1000 / 60) % 60); timeStr = formatter(min)+':'+formatter(sec)+':'+formatter(ms); this.tick(timeStr,tPass); },100) } tick(timeStr,tPass){ //这是游戏页面一个 react 组件中的一个用于更新显示 dom 的触发器 this.setState({ timePass:tPass, time:timeStr }) }
React 的使用感受及易错点(大神留步)
用 Vue 与用 React 的区别,抱头痛哭,Vue 半年没上项目了,忘得差不多了,个人观点(非喜勿喷)。
- 直接感受就是,Vue 确实比 React 容易上手,其模板,js,css 的组件式的开发方式,更接近我们以前工作中常用的 template + requireJs 的开发模式。
- React 更强调 js 的编程和项目的整体架构,state 放在哪一级,哪一级通过 props 来控制,当然 route 4.0 的组件化设计更强调这一点;
- React 在国内还是不像 Vue 那么大众,比方说,出了问题,很多只有在 stackOverflow 有相关答案;
- 不过,不论是 Vue 还是 React,熟悉 ES6 和面向对象的编程,两者上手都是很快的(装个逼,别打我)。
在整个学习过程中,将很多教程中敲黑板指出来的坑,又结结实实踩了一遍,现在可以说影响深刻。自己整理了一下,做个小分享,愿和我一样刚入门的,遇见下面的错误,不会那么迷茫
不要在
Cannot read property 'setState' of undefined'
解释:这个问题,主要是组件对象的构造 constructor 中,未在 constructor 绑定事件处理函数的 this 指向。这个在教程中是有明确说明的,解决办法就是 constructor()中添加:this.resetClick = this.resetClick.bind(this);
Cannot read property 'size' of undefined'
解释:这个问题,主要是组件对象的构造 constructor 中,未传入 props 对象,导致整个组件对象无 props 属性;其实除了理解继承,理解 React 组件的 生命周期 也很重要。
you are adding a new property in the Synthetic event Object
解释:可以简单理解为 SyntheticEvent 是 react 为浏览器兼容写的一个 dom 事件代理,除了她原有的那些属性,你不能私自为其添加属性。
iphone,元素滑动,页面会跟随滑动
这个真的是一个让人头疼的东西,太影响交互体验了。如果做过 h5 都知道加滑动阻止.而 react 有他自己的一套事件机制封装,所以在跟元素直接添加 touchmove 的 preventDefault 是不行的。得用最原始的事件写法写一个阻止滑动的触摸事件,如下所示。
componentDidMount() {
document.addEventListener('touchmove', (e) => {
e.preventDefault();
}, { passive: false });
}
一个疑问
一开始我的游戏盒子是用的 flex 布局,但一考虑,盒子里面的方块要滑动效果,我要做滑动的缓动效果,于是又改用了绝对定位布局,每个方块计算其定位点。但事实证明,我当时确实太菜,我用了 state 来管理每个方块在盒子的位置,但我调整 state 时,React 的 virtualDom 会自动计算,并更新 dom 节点,那我保持整个项目,怎么才能自己做出缓动效果呢?纯 CSS 不行,请各位大神给点建议。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论