使用原生JS写五子棋
06月8日, 2017 JavaScript Tumars 7,313 views次
06月8日, 2017 7,313 views次
前言
听说这是一道腾讯的面试题,可能网上已经有不少答案了,晚上没事看到这道题就自己做了下。逻辑很简单,考虑到是面试题,使用了 ES6 的语法。本文介绍下核心逻辑跟部分代码。
目录
思路
核心逻辑大致如下:
- 棋盘上的点就是
x, y
的坐标值; - 每次落子就给该点赋颜色值;
- 落子后判断该点为中心的 4 条轴,每条轴上的 9 个点,含中心点是否能连续 5 个点颜色相同;
- 是的话就结束游戏并判断为该颜色值的持方胜利,否的话就改变颜色值继续游戏。
保存已下的棋
我们建立个对象保存已下的棋,该对象的结构形为{x6y9: 1, x3y8: 2, x20y1: 2 ...}
,初始为空,每次落子都给它添加属性,属性名表示坐标,属性值表示棋子颜色,1
为白色,2
为黑色,未配置的属性查询返回0
。同时设两个方法用来操作该对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var board = new Proxy({},{ get: function(target, property) { if (property in target) { return target[property]; } else { return 0 } } }) //--------// getProps(x,y) { return 'x'+x+'y'+y } getColor(board, x, y) { return board[this.getProps(x,y)] } |
判断落子后是否胜利
首先获取 4 条轴上的坐标,我们设落子点的坐标为[x, y],那该点左上角的左边为 [x - 1, y - 1],右下角为 [x +1, y +1],所以每个方向都是 x,y 与 1, -1, 0 三个数两两形成的数组加4 次。如我们设落点坐标为[0,0],则左上角的4个位置就是:
1 2 3 4 5 6 7 8 9 |
var var lt = [] for(var i = 1;i<5;i++) { lt.push([0-i, 0-i]) } console.log(lt) // [[-1,-1], [-2, -2], [-3, -3], [-4, -4]] |
可以看出8个方向的相加值分别为[-1,-1], [-1,0], [1, -1], [-1, 0],以及它们的相反值。
1 2 3 |
var roundDirect = [[-1,-1], [-1,0], [1, -1], [-1, 0]] |
接下来写个函数checkRoundDirect
用来判断是否胜利,接收roundDirect
数组作为参数:
1 2 3 4 5 6 |
// 判断 4 个轴中是否有一个成立 checkRoundDirect(x, y, board, roundDirect) { return roundDirect.some(direct => this.checkSingleDirect(x, y, board, direct)) } |
这里把每条轴的判断交给checkSingleDirect
函数判断:
1 2 3 4 5 6 7 8 9 10 |
// 判断单个轴(两个方向)是否成立 checkSingleDirect(x, y, board, direct) { var leftDirect = direct var rightDirect = direct.map(v=>-v) var getNum = this.getDirectSameColorNum.bind(this, x, y, board) return (getNum(leftDirect) + 1 + getNum(rightDirect)) >= 5 } |
判断单轴是否成立的逻辑是以落子为起点,它左侧与右侧的连续相同颜色的点棋子数目相加大于 4,再加上落子本身,就是形成了 5 子。
中心点与一侧的连续相同颜色棋子的数量通过getDirectSameColorNum
函数获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 返回每个方向的颜色值相同的棋子数 getDirectSameColorNum(x, y, board, direct) { var result = 0 var bindGetcolor = this.getColor.bind(this, board) var activeColor = bindGetcolor(x,y) for (var i = 1; i < 5; i++) { var nextColor = bindGetcolor(x + i * direct[0], y + i * direct[1]) if (activeColor == nextColor) { result++ } else {break} } return result } |
这样通过调用checkRoundDirect
函数并传入当前点的x, y
参数以及已知的board, roundDirect
参数,就可以得到落子后是否胜利连成 5 子。
对外 API 与 UI 绑定
每个棋盘实例对外输出以下属性与接口。
属性:
board
对象,每个属性为已下棋子的坐标,属性值为该坐标的颜色值;palyChess(x, y, colorNumber)
方法,是执行下棋的调用方法,参数分别为 x,y 坐标值与要下棋子的颜色值;
接口:
onEnd(color)
与onKeep(color)
,两个回调函数,每次落子后游戏胜利结束及游戏继续的回调事件,color是下次落子的颜色值。
同时构建一个棋盘的 UI 对象并返回一个配置函数用来初始化棋盘实例,这里我就不写 UI 对象的具体实现了,各位有兴趣的可以查看源码,UI 对象的主要目的是绑定棋盘实例、渲染页面、绑定事件。
棋盘 UI 对象主要接收以下参数:
num
,棋盘大小,会输出 num * num 大小的棋盘
棋盘实例与 UI 绑定后共同输出一个函数用来初始化棋盘,按如下方式调用:
1 2 3 4 5 6 7 8 9 10 11 |
// 初始化游戏,(棋盘大小,{结束事件,落子事件}) Game.start(15,{ onEnd(color) { alert(`游戏结束, ${color === 1 ? '白子' : '黑子'}胜利`) }, onKeep(color) { document.getElementById('info').innerHTML = `${color !== 1 ? '白子' : '黑子'}回合` } }) |
结语
这个五子棋很简单,只要有基本的 js 基础跟清晰的思路就能很快做出来。源码中涉及到了作用域、原型、闭包、多态、柯里化等基础知识,以及对对象、函数、数组等方法的运用,同时使用了 ES6 的语法。
欢迎指教。
😛
您好,我是上海一家互联网公司的HR,公司现招聘一位中高级前端工程师,可以私聊下吗?
DEMO运行出错哦!
index.js:11 Uncaught SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
index.html:17 Uncaught ReferenceError: Game is not defined
你这个提示块级作用域不支持在非 strict 模式外使用,可能是浏览器对 es6 语法支持不健全,尝试使用最新版的 chrome 或者 firefox 运行下。