群里有个同学要做个五子棋的面试题,于是拿出了以前写的代码,但是有bug,所以重做了一遍。
代码只是做了简单实现,但是去面试足够了,顺便写篇文章对思路和代码详细解释下。
单机Demo
单机Demo源码
可联机Demo源码棋盘(正方形)是由一个个的方块构成,棋子可落在横竖线的交点处,棋盘基本都是这种样子
那就用一个空的 <i></i>
标签作为一个方块,伪元素 before
after
作为横竖中心线形成一个十字
然后一行的 <i>
标签排列下去,在需要换行处加一个 <br>
标签, 伪代码如下,
// 长宽为 11 的正方形棋盘 <template v-for="x in 11"> <template v-for="y in 11"> <i></i> <br v-if="x == 11"> </template> </template>
棋盘样式代码
对于后面每一步下棋落子,都需要知道具体棋子的位置,所以要把棋盘所有位置都添加坐标
因为是二维棋盘,所以只需要用长和宽两个嵌套循环初始化每个位置的坐标信息,并存储在 pieces 对象中,而且可以初始化中间棋子的为黑棋子。
每个坐标对象的key为 (x,y) 坐标值, value 为当前坐标的棋子信息 (w:白棋 b:黑棋)
这一步很简单,因为每一个坐标初始化了数据,所以加上监听 tick
方法即可
<i :class="pieces[_cover(x)+_cover(y)]" @click="tick(x,y)"></i>
这算是额外的功能,加个状态 last
用来存储每一步的坐标, 想悔棋,直接把上一步坐标的数据置空即可。
这是五子棋的核心,相对也最麻烦,每次落子(坐标 x,y ), 都需要从 ** 横向、竖向、/向、/向 ** 四个方向的其他棋子来判断结果。
这两个方向很简单,从落子坐标的最左处和最上处挨个判断是否和当前落子的颜色是否相同,连续5个即为获胜。
先计算 / 方向上从当前落子点往左上方后退x(最大5)个位置后的坐标(_x,_y)
然后从 (_x,_y) 开始往右下角方向用 for 循环逐一的比较每个坐标,循环长度为该方向最多棋子数(其实这里判断五次即可,用最多棋子数会有多余的超出棋盘的坐标进入循环中,小细节,暂时不修改)
/ 方向 相当于 / 方向的镜像
同样计算出从当前落子点往左下方后退x (最大5) 个位置后的坐标, 然后往右上角方向逐一的比较,据该方向上是否有连续5个相同的棋子判断结果,这里涉及到镜像坐标的计算,算法有点绕,具体的判断可以参考代码
每个联机的用户都可以做主机,也都可以输入其他主机的 socket.id 来配对
这里 WebSocket 用了 socket.io 来做通信。
const players = {} // sid和socket对象映射关系 id:socket const relations = {} // 每个玩家和对手的对应关系 id1:id2 id2:id1
players 对象存储每个玩家的 sid和socket 对象映射关系
relations 对象存储每个玩家和对手的对应关系
这里只用了两个事件 link 和 tick
客户端监听 连接事件 和 落子事件(tick-back)
客户端发送 link事件 来连接主机
发送 tick事件 来同步落子和坐标信息 (为了方便没有添加websocket通信失败的处理)
整个客户端的代码都比较简单,直接贴出来了
const socket = io.connect('http://127.0.0.1:8888') socket.on('connect', () => { this.socket = socket }) socket.on('linked', () => { alert('有主机连接成功,等待对方下棋') }) socket.on('linkOK', () => { alert('连接主机成功,开始下棋') // 连接其他主机 成为白棋子 可以落子 this.player = 0 this.canPlay = true }) // 对手落子后数据返回 socket.on('tick-back', d => { const data = JSON.parse(d) this.pieces = data.pieces; this.canPlay = true if(data.gameOver){ alert('game over') } })
这是给群里的学弟提供面试题的demo,主要看思路,难免很多细节没处理,bug多点。。。