Swoole和WebSocket之斗地主中篇准备发牌抢地主
这篇文章是Swoole和WebSocket之斗地主上篇进房间的续集,在这里我们主要讲解准备->发牌->抢地主整个流程。
开始
环境和上次保持一致即可.
功能实现
我们这期需要实现的有准备协议
,发牌推送
,抢地主
这几个协议.
准备协议
当一个用户准备,需要通知其它的用户更改显示状态, 当最后一个人准备的时候我们就发牌,我要通知用户抢地主.
- 功能分析
当用户准备的时候,我要通知其它用户更新准备用户的状态, 若是最后一个准备我们需要洗牌推送发牌协议.
Note:在这里我们抢地主不作复杂的流程,我们要是抢了就是成功为地主,没有抢就将消息发给下一个人抢。
- 服务端
修改协议文件,实现准备协议的代码
- 修改协议路由
protocol.php
$app->addProtocol(
'player.ready', //准备
LandownerController::class . ':ready'
);
$app->addProtocol(
'player.grab', //抢地主
LandownerController::class . ':grabLandowner'
);
- 实现代码
...
/**
* @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,
];
}
- 客户端
我们只需要实现对应的readyStatus
和assignPoker
就行了.
实现的客户业务代码
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