乐趣区

关于前端:教你做小游戏-用86行代码写一个联机五子棋WebSocket后端

我是 HullQin,公众号 线下团聚游戏 的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者 HullQin 受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入 Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。

背景

上篇文章《用 177 行代码写个体验超好的五子棋》,咱们一起用 177 行代码实现了一个本地对战的五子棋游戏。

当初,如果咱们要做一个联机五子棋,怎么办呢?

需要剖析

首先,咱们须要一个后端服务。2 个不同的玩家,一起连贯这个后端服务,把要下的棋通知后端,后端再转发给另一个玩家即可。当然,如果有观战的,也要把当后期局转发给观战者。

此外,为了让 2 个玩家联机,还须要有「房间号」的概念,只有同一个房间的人才能联机对战。不同房间的人互不影响,容许同时有多个房间的人同时玩游戏。

流程

整个通信流程是这样的:

  1. 玩家 A 申请进入房间 1。玩家 A 会执黑棋。
  2. 玩家 B 申请进入房间 1。玩家 B 会执白棋。此时人已满,其他人进入将观战。
  3. 玩家 C 申请进入房间 1。玩家 C 是观战者。
  4. 玩家 A 申请下棋,通知坐标给服务器。
  5. 服务器告诉玩家 B、玩家 C,通知大家 A 下棋的坐标。
  6. 玩家 B 申请下棋,通知坐标给服务器。
  7. 服务器告诉玩家 A、玩家 C,通知大家 B 下棋的坐标。

之后循环 4 - 7 步骤。

为了简化后端逻辑,把逻辑判断都放在前端。例如在前端判断是否游戏完结(五联珠),如果游戏完结,前端不容许再发任何申请。

技术选型

协定与计划

因为波及到服务器被动给用户发送数据,所以有几种可选计划:

  • Http 轮询:若在期待对方下棋,则前端每隔 1s 就发送一条申请,看看对方是否下棋。
  • Http 长轮询:若在期待对方下棋,则前端每隔 1s 就发送一条申请,看看对方是否下棋。然而后盾不会立刻返回后果,要等到接口超过某个工夫才返回后果。
  • WebSocket:建设好浏览器、服务器的连贯,可随时被动向浏览器推送数据。

这里咱们抉择 WebSocket,因为这种场景下 Http 协定的确有很大的资源节约。而 WebSocket 尽管实现起来有点难度,然而节约了资源。

具体实现计划

只有某个编程语言 / 框架能够反对 WebSocket 就能够。

因为我以前常常用 Django,用过Channels,对它的底层依赖daphne 有所理解,所以我间接抉择了 daphne。它是 ASGI 规范的一种实现。

daphne 是一个十分轻量的抉择,不像 Django+Channels 这套框架提供了很重的解决方案。daphne 只提供了根底的 ASGI 实现,没有其它冗余的性能。就好比:我开发五子棋前端时,应用了 SVG + Dom API,没有用 React 框架一样。

开发

基础知识

daphne要求咱们以这样的格局定义一个服务:

# server.py
async def application(scope, receive, send):
    # 解决 websocket 协定
    if scope['type'] == 'websocket':
        # 先接管第一个包,必须是建设连贯的包(connect),否则拒绝服务
        event = await receive()
        if event['type'] != 'websocket.connect':
            return
        # 校验通过,发送 accept,表明建设 ws 连贯胜利
        await send({'type': 'websocket.accept'})
        # 尔后单方能够相互随时发消息。开启个有限循环
        while True:
            # 接管一个包
            event = await receive()
            # 如果是断开连接的申请,就完结循环
            if event['type'] == 'websocket.disconnect':
                break
            # 这种形式能够读取包的文本内容
            data = event['text']
            # 这种形式能够发送一个包给浏览器,这里是把浏览器发来的包一成不变传回去
            await send({'type': 'websocket.send', 'text': data})

运行办法:

pip install daphne
daphne -b 0.0.0.0 -p 8001 server:application

业务开发

咱们须要定义一个房间汇合,称之为house

house = {}

编写玩家首次连贯(进入房间)的逻辑:

import json
async def application(scope, receive, send):
    if scope['type'] == 'websocket':
        event = await receive()
        if event['type'] != 'websocket.connect':
            return
        await send({'type': 'websocket.accept'})
        # 建设连贯后,要求前端发送一个 EnterRoom 事件,以 json 格局提供用户 id 和房间号 room
        event = await receive()
        data = json.loads(event['text'])
        if data['type'] != 'EnterRoom' or not data['id'] or not data['room']:
            # 若前端发送的第一个事件不是这个,就报错,断开连接
            await send({'type': 'websocket.close', 'code': 403})
            return
        room_id = data['room']
        user_id = data['id']
        # 看看房间号是否在 house 内,不在则创立一个 room
        if room_id not in house:
            house[room_id] = {
                'black': None,
                'white': None,
                'pieces': [],
                'sends': [],
                'users': [],}
        room = house[room_id]
        old = False  # 看玩家是不是老玩家(断线重连进来的)if room['black'] == user_id or room['white'] == user_id:
            old = True
            if user_id in room['users']:
                old_send = room['sends'][room['users'].index(user_id)]
                room['sends'].remove(old_send)
                room['users'].remove(user_id)
                await old_send({'type': 'websocket.close', 'code': 4000})
        else:  # 阐明玩家是第一次进,给他拿黑棋或白棋
            if room['black'] is None:
                room['black'] = user_id
            elif room['white'] is None:
                room['white'] = user_id
        # 如果玩家没拿到黑棋也没拿到白旗,就是观战者
        visiting = room['black'] != user_id and room['white'] != user_id
        # 把玩家的 send 函数存到 room 里,不便其余玩家下棋时调用,从而播送下棋事件
        room['sends'].append(send)
        # 把玩家 ID 存进去
        room['users'].append(user_id)

玩家进入房间后,咱们须要给他告诉一下这个房间的根本信息,例如是否曾经开始了?以后场上的期局是怎么的?

        await send({'type': 'websocket.send', 'text': json.dumps({
            'type': 'InitializeRoomState',
            'pieces': room['pieces'],  # 场上棋子状况
            'visiting': visiting,  # 你是否是观战者
            'black': room['black'] == user_id if not visiting else bool(len(room['pieces']) % 2),  # 如果你在下棋:黑棋是你吗?如果你是观战者:黑棋是谁?'ready': bool(room['black'] and room['white']),  # 房间是否筹备好开局了?只有有 2 集体同时在,就能够开了
        })})
        # 因为有人进入了房间,所以须要播送一下这个音讯。if not old and (room['black'] == user_id or room['white'] == user_id):
            for _send in room['sends']:
                if _send == send:
                    continue
                await _send({'type': 'websocket.send', 'text': json.dumps({
                    'type': 'AddPlayer',
                    'ready': bool(room['black'] and room['white']),
                })})
        while True:
            event = await receive()
            # 有人断线了,解决一下。若房间空了,还要删掉房间,以防内存占用有限增大
            if event['type'] == 'websocket.disconnect':
                if send in room['sends']:
                    room['sends'].remove(send)
                    room['users'].remove(user_id)
                    if len(room['pieces']) == 0 and len(room['sends']) == 0:
                        del house[room_id]
                break
            # 有人发送了事件,接管一下
            data = json.loads(event['text'])
            # 如果是下棋事件,就改一下 room 的 pieces 数据,并播送给大家
            if data['type'] == 'DropPiece':
                room['pieces'].append((data['x'], data['y']))
                for _send in room['sends']:
                    if _send == send:  # 不须要给本人告诉,所以跳过本人
                        continue
                    await _send({'type': 'websocket.send', 'text': json.dumps({
                        'type': 'DropPiece',
                        'x': data['x'],
                        'y': data['y'],
                    })})

当然,写好这些后,还须要测试,最好间接写好前端一起联调。咱们下篇文章把前端的 WebSocket 逻辑补充一下。

残缺源码

蕴含了前后端源码(总共不到 400 行): https://github.com/HullQin/gobang

是一个十分值得学习的对于 WebSocket 的 demo。

写在最初

我是 HullQin,公众号 线下团聚游戏 的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者 HullQin 受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入 Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。

退出移动版