共计 8102 个字符,预计需要花费 21 分钟才能阅读完成。
这篇文章是 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://wiki.swoole.com/
- https://github.com/TianLiangZhou/surf
- https://pixijs.io/examples/
- https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
正文完