ccforward / cc

Code & Blog
1.59k stars 193 forks source link

48.帮学弟做题--WebSocket和Vue的五子棋 #51

Open ccforward opened 7 years ago

ccforward commented 7 years ago

socket.io 和 Vue.js 的五子棋

群里有个同学要做个五子棋的面试题,于是拿出了以前写的代码,但是有bug,所以重做了一遍。

代码只是做了简单实现,但是去面试足够了,顺便写篇文章对思路和代码详细解释下。

Demo & 源码

单机Demo

单机Demo源码 可联机Demo源码

单机版详解

1、棋盘布局

棋盘(正方形)是由一个个的方块构成,棋子可落在横竖线的交点处,棋盘基本都是这种样子

那就用一个空的 <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> 

棋盘样式代码

2、坐标初始化

对于后面每一步下棋落子,都需要知道具体棋子的位置,所以要把棋盘所有位置都添加坐标

因为是二维棋盘,所以只需要用长和宽两个嵌套循环初始化每个位置的坐标信息,并存储在 pieces 对象中,而且可以初始化中间棋子的为黑棋子。

每个坐标对象的key为 (x,y) 坐标值, value 为当前坐标的棋子信息 (w:白棋 b:黑棋)

3.1、落子

这一步很简单,因为每一个坐标初始化了数据,所以加上监听 tick 方法即可

<i :class="pieces[_cover(x)+_cover(y)]" @click="tick(x,y)"></i>

3.2、悔棋

这算是额外的功能,加个状态 last 用来存储每一步的坐标, 想悔棋,直接把上一步坐标的数据置空即可。

4、判断输赢

这是五子棋的核心,相对也最麻烦,每次落子(坐标 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多点。。。

codedart2018 commented 7 years ago

牛逼

Diamondsiron commented 7 years ago

厉害了我的哥

juglans commented 7 years ago

厉害了office哥,不过还有优化空间,棋子加上渐变、棋盘四周搞上跑马灯、出棋发出拔剑的声音基本就差不多了。

DoYouZz commented 7 years ago

厉害了我的天

zjhr commented 7 years ago

吊炸天!!

DuckDeck commented 7 years ago

一个字,吊

splinfengwang commented 7 years ago

好玩好玩

caiyongmin commented 7 years ago

相当6,咳咳

Arrray commented 7 years ago

厉害厉害,学习了!

luzemin commented 7 years ago

献上膝盖

cangku commented 7 years ago

你好,仔细学习了一下你的源码,感觉你的思路好清晰。我自己实现的时候,我发觉对于斜线的分析还是有一些吃力,这种思维方面的训练有好的推荐吗?基础不是很好,这是我的弱项。还有一个小疑问就是这种判断的算法其实不是最优的对吗?比如说,我现在把棋盘扩大,其实我只是需要判断以落子的位置以最大可能为5的范围来判断就行,是吗?

ccforward commented 7 years ago

@cangku 训练的话 建议你去做做 leetcode 上面的题目 绝大多数都可用js实现 顺带着去看看算法和数据结构,算法的思维还是个长时间慢慢的积累的东西

关于落子的判断范围,其实可以做到5次最少判断的,就是需要考虑更多的情况,尤其是在边界上的判断,这就需要更详细的算法了。因为实际情况下棋盘也不是很大,以现在的算法对性能影响微乎其微,所以也没做优化了。

cangku commented 7 years ago

@ccforward 好的,谢谢

narco001 commented 7 years ago

厉害了我的哥,献上膝盖的同时,推荐其它vue的实例,http://www.17shulihua.com/archives/category/frontend-demo/vue-demo

layne92 commented 7 years ago

66

yujihu commented 7 years ago

PS:你的判断输赢逻辑有bug吧,[2][4],[3][3],[4][2],[5][1],[6][0]不会判断为赢

ghost commented 7 years ago

厉害,值得学习。

ryu2gaku commented 7 years ago

厉害了我的哥

hejingscu commented 7 years ago

demo挂了

ccforward commented 7 years ago

@hejingscu

demo地址是 http://ccforward.github.io/game/chess/chess.html

我这没问题 能给个截图看看嘛

unknwon commented 7 years ago

厉害了我的哥!

shacai commented 7 years ago

可联机demo 4个文件拷到本地文件夹 然后npm install下载之后 再node socket.js 发现跑不起来啊 题主能说说怎么在本地跑吗 漏了什么 http://localhost:8888/ 打不开无法运行啊

ccforward commented 7 years ago

@shacai

不要访问 http://localhost:8888/

应该访问 chess-connect.html

shacai commented 7 years ago

@ccforward 但是访问 chess-connect.html 打开这个页面是一片空白啊... 111

ccforward commented 7 years ago

@shacai

你看看源码 里面两个外部js文件的引用方式没有加 schema

你改成自己本地的服务器访问或者给两个外部的js 加上 http:

shacai commented 7 years ago

@ccforward 真的是 谢谢大神指点 原来是没有加 http: 难怪空白

maicong commented 7 years ago

大神很厉害啊!不过有 BUG: qq20170324-171846

Anshiii commented 7 years ago

厉害,思路好清晰...自己的五子棋拖了很久也没把联机功能加上,不过这里的五子棋是用canvas画的,当时还用了vue,然后用canvas画的话..基本也展现不出vue的优点了...

plh97 commented 6 years ago

既然vue提供了mvvm的思路, 那么维护 一个vue数组就好,数据模型同步到view上面,

const map = [
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];