Swoole和WebSocket之斗地主中篇准备发牌抢地主

2,470次阅读

共计 8102 个字符,预计需要花费 21 分钟才能阅读完成。

这篇文章是 Swoole 和 WebSocket 之斗地主上篇进房间 的续集,在这里我们主要讲解准备 ->发牌 ->抢地主整个流程。

Swoole 和 WebSocket 之斗地主中篇准备发牌抢地主

开始

环境和上次保持一致即可.

功能实现

我们这期需要实现的有 准备协议 发牌推送 抢地主 这几个协议.

准备协议

当一个用户准备,需要通知其它的用户更改显示状态,当最后一个人准备的时候我们就发牌,我要通知用户抢地主.

  • 功能分析

当用户准备的时候,我要通知其它用户更新准备用户的状态, 若是最后一个准备我们需要洗牌推送发牌协议.

Note: 在这里我们抢地主不作复杂的流程,我们要是抢了就是成功为地主,没有抢就将消息发给下一个人抢。

  • 服务端

修改协议文件,实现准备协议的代码

  1. 修改协议路由protocol.php

$app->addProtocol(
    'player.ready', // 准备
    LandownerController::class . ':ready'
);

$app->addProtocol(
    'player.grab', // 抢地主
    LandownerController::class . ':grabLandowner'
);
  1. 实现代码

...

/**
 * @param $body
 * @return array
 */
public function ready($body)
{if (!$this->redis->sIsMember(RedisConstant::FULL_CONNECT_FD, $this->frame->fd)) {
        return ["flag" => 500,
            "requestId" => $body->requestId,
        ];
    }

    $ready = 0;
    if (isset($body->ready)) {
        echo "ready";
        $ready = (int) $body->ready;
    }
    $this->redis->hSet(self::READY_KEY, $this->frame->fd, $ready);

    $count = $this->redis->sCard(RedisConstant::FULL_CONNECT_FD);
    if ($count>= 2) {$players = $this->redis->sMembers(RedisConstant::FULL_CONNECT_FD);
        $otherPlayer = [];
        foreach ($players as $player) {if ($player == $this->frame->fd) {continue;}
            $otherPlayer[] = $player;}
        $this->task(['from' => $otherPlayer,
            'content' => json_encode(["listen" => "readyStatus", "content" => ['playerId' => $this->frame->fd,
                'ready'  => $ready,
            ]])
        ], PushTaskHandle::class); // 通知其它用户更新状态

        if ($count>= 3) {
            $status = 1;
            foreach ($players as $player) {$status = $status & (int)$this->redis->hGet(self::READY_KEY, $player);
            }
            if ($status) { // 全部准备
                $playerInfo = $this->poker($players);
                $playerPoker = $playerInfo['poker'];
                unset($playerInfo['poker']);
                foreach ($players as $player) {$playerInfo['poker'] = $playerPoker[$player];
                    $this->task(['from' => $player,
                        'content' => json_encode(["listen" => "assignPoker", "content" => $playerInfo])
                    ], PushTaskHandle::class); // 通知发牌
                }
            }
        }
    }
    return ["flag" => 0,
        "requestId" => $body->requestId,
    ];
}

/**
 * 洗牌
 * @param array $player
 * @return array
 */
private function poker(array $player)
{$playId = mt_rand(100, 1000000); // 局号

    $poker = range(1, 54);

    shuffle($poker);
    shuffle($poker);
    shuffle($poker);

    $landowner = [];

    for ($i = 1; $i <= 3; $i++) {$key = array_rand($poker, 1);
        $landowner[] = $poker[$key];
        unset($poker[$key]);
    }

    $playerPoker = [];
    foreach (array_chunk($poker, 3) as $value) {$playerPoker[$player[0]][] = $value[0];
        if (isset($value[1])) {$playerPoker[$player[1]][] = $value[1];
        }
        if (isset($value[2])) {$playerPoker[$player[2]][] = $value[2];
        }
    }

    $this->redis->hSet(sprintf(self::PLAYER_INFO, $playId), 'poker', json_encode($playerPoker)); // 写入用户的牌
    $this->redis->hSet(sprintf(self::PLAYER_INFO, $playId), 'luck_poker', json_encode($landowner)); // 写入当前局的 3 张底牌

    return ['playId' => $playId,
        'poker'  => $playerPoker,
        'landowner' => $landowner,
    ];
}

/**
 * 抢地主
 *
 */
public function grabLandowner($body)
{if (!$this->redis->sIsMember(RedisConstant::FULL_CONNECT_FD, $this->frame->fd)) {
        return ["flag" => 500,
            "requestId" => $body->requestId,
        ];
    }

    $playId = $body->playId; // 当前局

    if ($body->grab) {$poker = json_decode($this->redis->hGet(sprintf(self::PLAYER_INFO, $playId), 'poker'), true);

        $landowner = json_decode($this->redis->hGet(sprintf(self::PLAYER_INFO, $playId), 'luck_poker'), true);

        $poker[$this->frame->fd] = array_merge($poker[$this->frame->fd], $landowner);

        return ["flag" => 0,
            "requestId" => $body->requestId,
        ];
    }

    $players = $this->redis->sMembers(RedisConstant::FULL_CONNECT_FD);

    $currentPos = 0;
    foreach ($players as $key => $player) {if ($player == $this->frame->fd) {$currentPos = $key;}
    }
    if ($currentPos == count($players) - 1) {$currentPos = 0;} else {$currentPos += 1;}
    $this->container->get('server')->getServer()->push($players[$currentPos], json_encode(["listen" => "grabLandowner",
        "content" => "grabLandowner",
    ]));
    return ["flag" => 500,
        "requestId" => $body->requestId,
    ];
}

  • 客户端

我们只需要实现对应的 readyStatusassignPoker就行了.

实现的客户业务代码



Landowner.prototype = {start: function () {document.body.appendChild(this.app.view);
        this.listen(); // 初始化监听},
    initRender: function (landowner) {
        /**
         * @var landowner Landowner
         */
        if (this.socket.isConnected()) {this.socket.send('room.player', {}, function(body) {if (body.count> 3) {return ;}
                landowner.playerCount = body.count;
                for (var i = 0; i < body.player.length; i++) {landowner.readyWorker(i + 2, !!body.player[i].ready, body.player[i].playerId);
                }
                this.send("enter.room", {}, function (body) {if (body.flag === 0) {
                        landowner.player = body.player;
                        landowner.readyWorker(1, false, body.player);
                    }
                });
            });
        }
    },
    listen: function() {
        var _this = this;
        this.socket.listen("readyStatus", function(content) { // 直接在回调函数里面修改文字
            /**
             * @var button Graphics
             */
            var button = _this.playerReadyButton[content.playerId];
            var text = button.getChildByName('text');
            text.text = content.ready ? "准备中" : "准备";
            text.text = content.ready ? "准备中" : "准备";
            if (content.ready) {text.x = 60 - 48;}
        });
        this.socket.listen("assignPoker", function (content) {
            _this.playId = content.playId;
            _this.renderBottomCard(content.landowner);
            _this.assignPoker(content.poker);
            _this.userPoker = content.poker;
            _this.landownerPoker = content.landowner;
            for (var key in _this.playerReadyButton) {_this.playerReadyButton[key].destroy();}
            _this.assignOtherPoker(content.poker.length);
        });
        this.socket.listen("grabLandowner", function () {_this.grabWorker(); // 抢地主
        });
    },
    renderBottomCard: function (poker) {var container = new PIXI.Container();
        container.y = 100;
        container.x = window.innerWidth / 2 - 254;
        this.app.stage.addChild(container);
        for (var key in poker) {var bottomPoker = PIXI.Sprite.fromImage('poker/' + poker[key] + '.jpg');
            bottomPoker.x = key * 125;
            container.addChild(bottomPoker);
        }
    },
    assignPoker: function (poker) {console.log(poker);
        var createPoker = function () {
            var i = 1;
            return function (index) {var poker = PIXI.Sprite.fromImage('poker/' + index + '.jpg');
                poker.interactive = true;

                // Shows hand cursor
                poker.buttonMode = true;
                poker.x = i * 20;
                var clickCounter = 1;
                poker.on('pointerdown', function () { // 监听牌的点击
                    if (clickCounter % 2) {poker.y = -20} else {poker.y = 0;}
                    clickCounter++;
                });
                i++;
                return poker;
            }
        };
        poker.sort(function (a, b) { // 将牌排好序
            var o = a % 13, t = b % 13;
            if (o>= 0 && o <= 2) {o = 13 + o;}
            if (t>= 0 && t <= 2) {t = 13 + t;}
            if (a === 53) o = 16;
            if (a === 54) o = 17;
            if (b === 53) t = 16;
            if (b === 54) t = 17;

            if (o> t) {return -1;}
            if (o < t) {return 1;}
            return 0;
        });
        var container = new PIXI.Container();
        container.y = window.innerHeight / 2 + 100;
        container.x = window.innerWidth / 2 - 254;
        this.app.stage.addChild(container);
        var createFactory = createPoker();
        for (var key in poker) {container.addChild(createFactory(poker[key]));
        }
    },
    assignOtherPoker: function(count) { // 渲染其它人的牌样式
        var containerRight = new PIXI.Container(),
            containerLeft = new PIXI.Container();
        containerRight.x = window.innerWidth - 200 - 105;
        containerRight.y = 150;
        containerLeft.x = 200;
        containerLeft.y = 150;
        this.app.stage.addChild(containerLeft);
        this.app.stage.addChild(containerRight);
        for (var i = 0; i < count; i++) {var pokerSource1 = PIXI.Sprite.fromImage('poker/background.jpg');
            var pokerSource2 = PIXI.Sprite.fromImage('poker/background.jpg');
            pokerSource1.y = i * 20;
            pokerSource2.y = i * 20;
            containerLeft.addChild(pokerSource1);
            containerRight.addChild(pokerSource2);
        }
    },
    grabWorker: function () { // 渲染抢地主
        var button = new PIXI.Graphics()
            .beginFill(0x2fb44a)
            .drawRoundedRect(0, 0, 120, 60, 10)
            .endFill();
        var noGrabButton = new PIXI.Graphics()
            .beginFill(0x2fb44a)
            .drawRoundedRect(0, 0, 120, 60, 10)
            .endFill();

        var grab = new PIXI.Text("抢地主", new PIXI.TextStyle({
            fontFamily: "Arial",
            fontSize: 32,
            fill: "white",
        }));
        var noGrab = new PIXI.Text("不抢", new PIXI.TextStyle({
            fontFamily: "Arial",
            fontSize: 32,
            fill: "white",
        }));
        grab.x = 60 - 48;
        grab.y = 30 - 16;
        grab.name = "grab";
        noGrab.x = 60 - 32;
        noGrab.y = 30 - 16;
        noGrab.name = "noGrab";
        button.interactive = true;
        button.buttonMode = true;
        noGrabButton.interactive = true;
        noGrabButton.buttonMode = true;
        button.addChild(grab);
        noGrabButton.addChild(noGrab);
        var playerGrab = function (grab) {return function() {
                _this.socket.send('player.grab', {'grab': grab, 'playId': _this.playId}, function (body) {if (body.flag === 0) { // 抢成功
                        _this.userPoker.concat(_this.landownerPoker); // 合并牌
                        _this.app.stage.getChildByName('poker').destroy(); // 删除牌
                        _this.assignPoker(_this.userPoker); // 重新整理牌
                    }
                    button.destroy();
                    noGrabButton.destroy();});
            }
        };
        button.on('pointertap', playerGrab(1));
        noGrabButton.on('pointertap', playerGrab(0));
        var x = y = 0;
        y = window.innerHeight / 2;
        x = window.innerWidth / 2;
        x = x - 200;
        y = y + 10;
        button.x = x;
        button.y = y;
        noGrabButton.x = x + 200;
        noGrabButton.y = y;
        this.app.stage.addChild(button);
        this.app.stage.addChild(noGrabButton);
    },
}

以上服务端和客户端代码就完成了用户准备、发牌、抢地主功能。下一期我们讲下准备出牌的实现.

Note: 在代码中我们以实现功能为主,实际有好多情况没有去考虑以及一些 BUG。比如说关闭页面需要断开用户通知给其它用户等等。

源码地址

https://github.com/TianLiangZhou/loocode-example/tree/master/landowner

效果地址

https://example.loocode.com/landowner/index.html

推荐阅读

  1. https://wiki.swoole.com/
  2. https://github.com/TianLiangZhou/surf
  3. https://pixijs.io/examples/
  4. https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API

正文完
 
Blood.Cold
版权声明:本站原创文章,由 Blood.Cold 2019-06-04发表,共计8102字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。