/fir

五子棋

Primary LanguageJavaScript

线上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个晚上,样式和交互没有时间做优化了,这次的重心还是放在了开发逻辑功能上。