在线双人扑克文档说明
1. 体系结构
2. 逻辑流程图
3. 服务器和客户端的通讯交互
4. 功能设计
数据结构
用户Object:{
status: 用户状态,共有"WAITING"、"DISCARD"、"GAMEOVER"三种case
leftCount:余牌计数器,当余牌数为0,即为获胜方
cards[]: 余牌数组,存放余牌的牌面信息
}
出牌Object:{
cardType: 出牌牌型,共有"NONE"、"ONE"、"PAIR"、"TRIPLE"、"FOUR"、"STRAIGHT"六种完整态牌型,以及"PAIRPLUS"、"TRIPLEPLUS"、"TRIPLEDOUBLE"、"ERR"四种半完成态与终止态
startValue: 当前出牌的牌组中最小的牌面值,单张、顺子、对子、三张、炸弹的起始值
cardCount: 对应牌型计数器。如果是对子,存放有几对;顺子,存放有几张连牌;三张成对,存放有几对
}
牌型判断
压牌原理
分派发牌原理
- cardBase[55]数组:
保存54张正面牌面与1张背面牌面的base64编码; 0为背面,113为红桃AK,1426为方片AK,2739为黑桃AK,4052为草花AK,53大王,54小王。 - cards[54]数组:
存放54张牌的序号;从1~54。 - cards1[27]数组:
存放分出的第一组牌的序号,在渲染时到cardBase数组中找对应牌面的base编码 - cards2[27]数组:
存放分出的另一组牌的序号,在渲染时到cardBase数组中找对应牌面的base编码
服务器:
对cards数组(54张牌)进行随机排序,逐一插入元素到cards1(27张牌)和cards2(27张牌)两个数组中去。
客户端:
接收到来自服务器的cards1与cards2,进行深度拷贝并且完成页面渲染。
系统模块与实现
1. 界面绘制
初始化发牌页面
function doInit() {
if(onlineUserCount == 2){
$(".wait").css("display", "none");
deal(myCards, cards);
doCreateRivalCards(myCards, cards);
status = "DISCARD";
}
}
//渲染我的牌面
function deal(halfCard, cardBase) {
halfCard.sort(compare);
for (var i = 0; i < halfCard.length; i++) {
var myCard = $.format(MYCARD_MODULE, cardBase[halfCard[i]], halfCard[i]);
$('.me-cards').append(myCard);
}
leftCount = halfCard.length;
}
//渲染对方牌面
function doCreateRivalCards(halfCard,cardBase) {
for (var i = 0; i < halfCard.length; i++) {
var rivalCard = $.format(RIVALCARD_MODULE, cardBase[0], 0);
$('.rival-cards').append(rivalCard);
}
}
渲染出牌
//出牌
function doOutCards() {
if(outCards.length != 0) {
outCards.sort(compare);
//复制outCards数组,避免污染outCards原数组
var copy = [];
for(var i = 0;i> rival
// 判断函数结束
if(outObj.cardType != "ERR" && comp == true) {
// 清空前一轮的出牌,渲染新的出牌
$(".out-cards").empty();
for (var i = 0; i < outCards.length; i++) {
$("img[name=" + outCards[i] + "]").remove();
var outcard = $.format(OUTCARD_MODULE, cards[outCards[i]], outCards[i]);
$(".out-cards").append(outcard);
}
//更新我方计数器,查看余牌是否为0,能否获胜
leftCount -= outCards.length;
//把我方此轮的出牌,发送给服务器
socket.emit('nextTurn', _username, outCards, outObj);
console.log(leftCount);
outCards.splice(0, outCards.length);
$("#play-options").css("display", "none");
//判定余牌数之后的操作之后,还要修改status
if (leftCount == 0) {
//给服务器发送我方获胜信息,由服务器完成二次分发
socket.emit("win", _username);
status = "GAMEOVER";
}
}
}
}
2. 通讯机制
通讯命令定义
服务器端:
io.on('connection',function (socket) {
var uname;
//接收到js初始化base64成功信号后,打乱分牌成两堆
socket.on('cardDone',function (_cards) {
if (cards.length == 0){
cards = _cards;
console.log("我收到牌了");
//发牌
cardNumber.sort(function(){ return 0.5 - Math.random() });
for(var i = 0; i < 27; i++){
card1.push(cardNumber.pop());
}
for(var i = 0; i < 27; i++){
card2.push(cardNumber.pop());
}
card1.sort(compare);
card2.sort(compare);
console.dir(card1);
console.dir(card2);
}
})
socket.on('login', function (_username) {
console.dir(pkList);
var username = _username;
pkList.push(username);
clientSockets[username] = socket;
console.log("新用户");
console.dir(pkList);
//如果在线两人,开始pk
if(pkList.length == 2) {
clientSockets[pkList[0]].emit('gameInit', card1 ,cards);
clientSockets[pkList[0]].emit('firstOut');
clientSockets[pkList[1]].emit('gameInit', card2, cards);
}
});
socket.on('nextTurn',function (player , outCards ,outObj) {
//移交出牌权, 给对手渲染我方出的牌,还要把余牌数量相应减少
console.log("这是XX打的牌:");
console.log(player);
if (player == pkList[0]){
console.log('给第二个人传牌');
console.dir(outCards);
clientSockets[pkList[1]].emit('checkRound', outCards, cards ,outObj);
} else {
console.log('给第一个人传牌');
console.dir(outCards);
clientSockets[pkList[0]].emit('checkRound', outCards, cards ,outObj);
}
})
socket.on('win',function (winnerName) {
//在线人数改为0 , 需要按下restart才能+1
clientSockets[pkList[0]].emit('gameOver', winnerName);
clientSockets[pkList[1]].emit('gameOver', winnerName);
})
//非正常下线
socket.on('disconnect',function (username) {
onlineCount -=1;
//删除该用户对应的键值对,即socket包
delete clientSockets[username];
socket.leave(username);
// 移除pk列表对应元素
pkList.splice(pkList.indexOf(username),1);
console.log(username + 'offline....');
})
socket.on('leave',function () {
socket.emit('disconnect');
})
})
客户端:
socket.on('firstOut',function () {
// alert("没错,我先出牌");
//显示function bar
$("#play-options").css("display","block");
})
//接收到对方的出牌,渲染来牌与减少对方张数,并且获得出牌权
socket.on('checkRound', function (outCard, cardBase,obj) {
$("#play-options").css("display","block");
$(".out-cards").empty();
console.log("hello 我收到牌了");
console.dir(outCard);
for (var i = 0; i < outCard.length; i++) {
console.log("出牌了 yizhang");
var outcard = $.format(OUTCARD_MODULE, cardBase[outCard[i]], outCard[i]);
$('.rival-card')[0].remove();
$(".out-cards").append(outcard);
}
// 把对方的来牌obj接受,并且复制到rival对象中去,留到后续比较时候用
for(key in obj){
rival[key] = obj[key];
}
console.dir(rival);
})
//显示某一个方的获胜信息,游戏结束
socket.on('gameOver', function (winnerName) {
$('.wait').css("display","block");
$(".wait-info")[0].innerHTML = "游戏结束," + winnerName + "获胜!";
status = "GAMEOVER";
//还要渲染一个选择框,是否重新开始,或者退出
})
##### 实际触发调用
3. 牌型判断函数
输入:牌1值,牌2值
输出:牌型、出牌起始值、对应牌型计数器
牌型判断状态机,修改CARDTYPE、STARTVALUE、CARDCOUNT等参数
function cardTypeMachine(card1,card2) {
switch(CARDTYPE){
case "ONE":
checkPairOrStraight(card1,card2);
break;
case "PAIR":
checkTripleOrPairplus(card1,card2);
break;
case "TRIPLE":
checkFourOrTripleplus(card1,card2);
break;
case "STRAIGHT":
checkStraight(card1,card2);
break;
case "PAIRPLUS":
checkPair(card1,card2);
break;
case "TRIPLEPLUS":
checkTriple(card1,card2);
break;
case "FOUR":
checkFourMore(card1,card2);
break;
case "TRIPLEDOUBLE":
checkTriple(card1,card2);
break;
}
}
//1 顺子还是对子
function checkPairOrStraight(card1, card2) {
if(card1 == card2){
CARDTYPE = "PAIR";
}else if (card1 + 1 == card2){
CARDTYPE = "STRAIGHT";
CARDCOUNT++;
}else {
CARDTYPE = "ERR";
}
}
//2 三张还是对子加一
function checkTripleOrPairplus(card1, card2) {
if(card1 == card2){
CARDTYPE = "TRIPLE";
}else if (card1 + 1 == card2){
CARDTYPE = "PAIRPLUS";
}else {
CARDTYPE = "ERR";
}
}
//3 四张还是三带一(此处是三张连队的中间态)
function checkFourOrTripleplus(card1, card2) {
if(card1 == card2){
CARDTYPE = "FOUR";
}else if (card1 +1 == card2){
CARDTYPE = "TRIPLEPLUS";
}else {
CARDTYPE = "ERR";
}
}
//4 是否是顺子加一
function checkStraight(card1, card2) {
if(card1 +1 == card2){
CARDTYPE = "STRAIGHT";
}else {
CARDTYPE = "ERR";
}
}
//5 是否是连对
function checkPair(card1, card2) {
if(card1 == card2){
CARDTYPE = "PAIR"
CARDCOUNT++;
}else {
CARDTYPE = "ERR";
}
}
//6 是否是三带二(此处是三张连队的中间态)
function checkTripledouble(card1, card2) {
if(card1 == card2){
CARDTYPE = "TRIPLEDOUBLE";
}else {
CARDTYPE = "ERR";
}
}
//7 判断四张加一
function checkFourMore(card1, card2) {
CARDTYPE = "ERR";
}
//8 判断判断三张连对
function checkTriple(card1, card2) {
if(card1 == card2){
CARDTYPE = "TRIPLE";
CARDCOUNT++;
}else if (card1 +1 == card2){
CARDTYPE = "ERR";
}
}
出牌牌组实际调用牌型判断函数,并且把CARDTYPE、STARTVALUE、CARDCOUNT等参数封装成一个出牌对象
for(var i = 0;i
4. 压牌比较函数
输入:我方压牌对象,对方出牌对象
输出:boolean值,true表示可出牌压住对方,false表示不可出牌
function cardCompare(myObj,rivalObj) {
if (rivalObj.cardType == "FOUR") {
if (myObj.cardType == "FOUR") { //我方是炸弹,对方也是炸弹
return valueCompare(myObj.startValue, rivalObj.startValue);
} else { //我方不是炸弹,对方是炸弹
return false;
}
}else if(rivalObj.cardType == "NONE"){
return true;
}else {
if(myObj.cardType == "FOUR"){ //我方是炸弹,对方不是炸弹
return valueCompare(myObj.startValue,rivalObj.startValue);
}else { //我方不是炸弹,对方也不是炸弹
//牌型相同,张数相同才能比较大小
if(myObj.cardType == rivalObj.cardType && myObj.cardCount == rivalObj.cardCount){
return valueCompare(myObj.startValue,rivalObj.startValue);
}else{
return false;
}
}
}
}
function valueCompare(value1,value2) {
if (value1 > value2){
return true;
}else{
return false;
}
}