线上demo: https://a044161.github.io/fir/
这次开发中采用的是自己还未完成的一个脚手架,主要是之前为了给自己的side project来弄的一个,但是还没完善,这次就匆忙的使用上了,在使用的过程中也要遇到一些问题,比如:
- 无法直接饮用通过访问目录的方式直接引入index.js,而是要
import x from 'x/index'
; - 还有一个就是对象的
...
运算符竟然无法使用
其它的问题暂时没有发现。
运行方式
npm install
npm run dev
运行成功后,访问localhost:3000
即可。
功能点
- 悔棋
- 重新开始
目录结构
js > fir
用于存放这次的主要代码
|____core // 用于存放核心代码(目前只有计算五子连珠,之前考虑还要用于存放机器人部分)
| |____calculate.js // 用于计算是否五子连珠
|____fir.js // 入口文件
|____modules
| |____dom.js // 操作dom
| |____templates // html模板
| | |____col.js // 列模板
| | |____frame.js // 主结构模板
| | |____index.js // 引用模板的主函数
| | |____point.js // 棋子模板
| | |____row.js // 行模板
| |____utils.js // 用于存放工具类的方法
css
用于存放样式文件,时间仓促,只用了一些基本的样式
思路分析
对思路进行简要的一些分析
页面结构生成
虽然说五子棋盘的规格为 15*15
但是在设计的时候,考虑到可以自行设置棋盘的大小,所以在这部分,都通过使用模板进行生成。
伪模板渲染
一开始的想法是可以加入模板引擎,可以赋值,控制类名,数据绑定等,能够腾出来的时间不多,所以这部分就放弃了,改用es6的模板语法来弄。
创建一个用于存放模板的文件夹templates
,里面有个 index.js
文件是用来引入模板,以及通过传递进来的key进行获取模板,渲染模板。
import col from "./col"; // 各模板文件
import row from "./row";
import point from "./point";
import frame from "./frame";
const TPL_OBJ = merge(col, row, point, frame); // ...运算符无法使用,就写了一个用于合并对象的方法
export default (type, data) => {
if ( // 通过type来查找模板,data是用于传入模板内的参数
typeof TPL_OBJ[type] === "function" ||
typeof TPL_OBJ[type] === "string"
) {
return TPL_OBJ[type](data);
}
};
以point.js
模板文件为例
const point = data => {
const style = data.style ? data.style : {}; // 自定义属性
return `<div class="fir__point__block"
data-point="[${data.x},${data.y}]"
style="width: ${style.width}; height: ${style.height}; left: ${style.left}; top: ${style.top};"></div>`
};
export default {
point
};
开发人员自己定义相关的数据,来传入到模板类,可以是其它html字符串,或者样式,但是不支持绑定事件,之后再通过函数调用模板
tpl("point", {
x: current_row, // 横坐标
y: current_col, // 纵坐标
style: {
// 大小以及偏移值
width: `${PointSize}%`,
height: `${PointSize}%`,
top: `${current_top}%`,
left: `${current_left}%`
}
});
返回值为html字符串,还需要通过innerHTML
方式写入到页面中。
这大概就是这次模板渲染的方式,一些细节的代码在fir.js
中的createRow() createCol() createPoint()
中。
下棋思路
其实一开始纠结该如何放置棋子的问题思考了好久,最终还是向页面中的每个交点都放置一个dom元素做了妥协,没有想到其他的可以减少dom元素的方式,想通过样式来解决,但是无法解决点击位置的问题。 通过定义了两个数组来辅助下棋
firBoard = []; // 计算五子连珠的数组
squence = []; // 记录下棋的顺序
生成棋子
先生成棋子,每个棋子的dom中用了data
的属性,增加了一个data-point
用于标记棋子的坐标,在纠结是用从0开始计算还是1开始计算时,选择了1,因为棋盘中的计算方式是从1开始。这时在放置棋子的外围容器上,我注册了一个点击事件
on(firPointWrapper, "click", this.handleChessClick.bind(this));
同时将下棋的方法又剥离出来,可以用于之后机器人下棋时进行直接调用,handleChessClick
其实包含了下棋的函数。
当点击时,先去获取坐标所对应在计算五子连珠数组中的索引值,然后再对棋子添加样式,这是再去调用计算方法 calculate.count
去计算是否有五子连珠,并往下棋队列中添加棋子信息。
到这里为止就会生成棋子了,一个是数据,一个是页面,其实页面上就是增加一个类名而已。
计算方法
分为三种计算方式:横、竖、对角线。其实横、竖比较类型,我也提取了公用的方法出来,对角线的比较麻烦。横、竖各需要按照两个方向来计算,对角线则要四个方向,但是两个两个是一对。
我还写了一个方法,用来获取坐标所对应计算五子连珠数组的索引值getPointIndex
,当下了一个棋子后,则在数组相应的位置赋上值,之前索引没有值的话,就会被自动填充为undefined
,比较麻烦的就是要考虑遇到其中断开的,其实就是遇到undefined
时的计算,我所做的处理就是,如果遇到了undefined
这个方向上之后我就不去计算了。
核心就是,先找到各个方向上,下个点的索引值,以及边界问题,获取两个端点的坐标。通过坐标和数组索引的方式就能很快的进行计算了,因为每个坐标都对应了一个索引值,我只要找了下一个点的索引值,我就能得到值,将值进行相加,大于4或者小于-4则说明五子连珠了。具体的实现在calculate.js
中。
悔棋和重新开始
这两个点比较简单。
悔棋是依托之前定义的下棋序列的数组,只要获取最后一个数组元素就好,里面可以取到对应的坐标和值,通过坐标获取索引,然后再计算五子连珠的数组中置为undefined
即可,再通过元素选择器,里面已经有坐标信息了,所以[data-point="[${pre_step.point}]"]
就能获取到了,再移除样式就好。
重新开始就是将各个数据还原就好
其它
在项目中我写了两个减少工作量的js,一个是操作dom元素的dom.js
,还有一个是包含工具的utils.js
。
dom.js
里面包含了:
-
获取dom元素
-
增加/删除类名
-
增加/移除事件监听器
-
获取dataset
utils.js
里面包含了:
-
判断是否为对象
-
判断是否为数组
-
判断是否为undefined
-
判断是否为null
-
合并对象
这次也是想到说已插件的形式来开发,可以根据挂载的元素,来生成棋盘,这样可以在很多地方调用,所以在页面中是这样调用的
new FIR({
id: 'FIR' // 元素id
size: 14 // 棋盘大小
})
难点
-
棋盘布局上,这次都采用了百分比的计算方式,因为能够适应多种场景比如外容器的宽高,棋子的数量,以及没想到办法来解决dom元素数量的方式。
-
模板使用上一直没有达到最理想的状态,通过数据绑定的方式来渲染模板,最终只能选用了开发时间最短的一种方式来编写模板,因为这样才能根据自己的需要来生成相对应的模板,比如不同棋子数量时,棋子的元素数量也不同,网格的数量也不同。
-
计算五子连珠,这应该是最麻烦的部分,因为可能性太多,要考虑的也比较多,先是进行纸上的模拟找规律,之后再考虑计算方式上有没有办法优化,在计算方式上面的优化是想到,3个方式的计算,我要尽可能的减少循环数组的次数,于是我都把横、竖计算的
for
调整为了一个,次数为5次,每次循环都计算一个方向上两个不同维度的值,只有对角线的比较麻烦,因为要计算边界的情况,所以多了一个for
循环。 -
采用索引和坐标的关系来保证数据的利用率,其实在其他框架中,比较好的一个想法就是用数据驱动模型,这次在开发过程中也一直想要往这方向上靠拢,保证依托数据来完成相对应的操作。就像通过坐标来查找数组索引,通过坐标来控制样式等。
小遗憾
没有写一个watch来监听数据的变化,再反应到样式中。以及样式、交互没有优化。
结束语
每天晚上花了一些时间来弄,上班的时候也有在思考一些思路,都尽可能的从简洁,代价低的方向上去思考,整个完成时间大概是3-4个晚上,样式和交互没有时间做优化了,这次的重心还是放在了开发逻辑功能上。