目录:
1. 体系结构
2. 服务器-客户端通讯图
客户端处理
用户点击登录按钮进入排队,并发给服务器随机 id,并进入登录状态
function doInit() {
socket.emit("add user", id);
}
初始化socket
function socketInit() {
// //当前只有一人登录,等待其他人加入,等待状态
socket.on("waiting", function () {
pkObj.data.status = WAIT;
});
//进入打牌界面
socket.on("start poker", function (data) {
if(拿到黑桃♠3) { //转换成出牌状态
pkObj.data.status = DISCARD;
} else { //等待
pkObj.data.status = WAIT;
}
});
//第三人登录显示拥挤
socket.on("crowded", function () {
alert("棋牌室拥挤,请稍后再试。。。");
});
//对方退出全部初始化,转换成重新开始状态
socket.on("exit", function () {
pkObj.data.status = RESTART;
});
//接受对方的牌,转换成出牌状态
socket.on("receive discard", function (data) { });
//对方放弃出牌,转换成出牌状态
socket.on("receive abandon", function () {
pkObj.data.status = DISCARD;
});
//比赛结果,装换成游戏结束状态
socket.on("receive result", function (data) {
pkObj.data.status = GAMEOVER;
});
}
//游戏结束,刷新页面
function doRestart() {
location.reload();
}
//登录状态,向服务器发送id
function doInit() {
socket.emit("add user", id);
}
//出牌状态
function doDiscard(that) {}
//初始化扑克牌
function poker(list, n) {}
//展示对方的牌
function oppositePoker(n) {}
//根据余数排序
function sortNumber(a, b) {}
//改变特殊牌的值
function changeNum(n, m) {}
//根据序号指定精灵图的位置
function getPos(index) {}
//检查和确定出牌的类型
function checkPokerType(pokerList) {}
//将选择出的牌和对方出的牌进行比较
function compare(list1, list2) {}
//返回每张牌的面值
function getPokerFace(n) {}
服务器端处理
//从一个给定的数组arr中,随机返回num个不重复项
function getArrItems(arr, num) {}
//数组相减获得剩下的牌数组
function getArrSubtract(arr, sub) {}
//返回两组互不相同的27张牌数组
function getPoker() {}
//poker
var io = require('socket.io').listen(server);
var userCount = 0;
var user = {};
var first; //第一位用户id
var second; //第二位用户id
io.on('connection',function(socket) {
//用户登录
socket.on('add user', function(id) {
if (userCount == 1) {
//只有一人发送等待
} else if (userCount == 2) {
//两个人就发送开始打牌命令
var data1 = { }; //包含每个人的牌组和是否还有黑桃三
var data2 = { };
//向每个人发送牌组
} else if (userCount > 2) {
//第三者不可以登录
}
});
//用户退出
socket.on('disconnect', function() {
//其中一方退出则全部回到起始页面
});
//用户出牌
socket.on('discard', function(data) {
//向另外一个人发送牌组信息
});
//用户放弃
socket.on("abandon", function(data) {
//轮到另外一个人出牌
});
//用户投降
socket.on("surrender", function(data) {
//发送游戏结果
});
});
3. 客户端状态转换
说明:
- 1.用户进入登录界面,点击登录按钮,若当前已有一个人在等待,则直接进入游戏,否则一直等待,直到第二个人登录;
- 2.用户在游戏中随时可退出,刷新页面即视为退出,此时双方都回到登录界面,需要重新登录;
- 3.依据哪方拿到 黑桃♠3,哪方就先出牌;
- 4.假设A先出牌,A出牌给B,B页面区域1显示A发过来的牌,换B出牌,出现有出牌、放弃和认输三个选项。点击出牌同样发送选中牌的数据,放弃则是不刷新桌上牌列,将出牌权给A。若A认输则直接结束游戏,B获胜,重新开始。
页面布局
2.1 PokerObj 对象定义
//pkObj
var pkObj = {
data: {
type: ERROR, //出牌类型
discardList: 0, //出牌数组
remain: 27, //我方牌的剩余量
from: 0, //我方id
to: 0, //对方id
before: false, //先出牌或对方放弃
status: INIT //状态:INIT初始化, DISCARD出牌,GAMEOVER游戏结束
},
init: function () {
//初始化socket
socketInit();
//加载登录界面
$(wrapper).append(htmlLogin);
}
};
2.2 定义各种状态
var INIT = "INIT"; //开始
var WAIT = "WAIT"; //等待2
var DISCARD = "DISCARD" ; //出牌
var GAMEOVER = "GAMEOVER"; //结束
var RESTART = "RESTART"; // 重新开始
2.3 根据状态绑定各种点击事件
function init() {
//绑定点击事件
$("body").on("click", clickEvent);
//初始化socket和登录界面
pkObj.init();
}
//点击事件
function clickEvent(e) {
var target = e.target;
var that = $(target);
switch (pkObj.data.status) {
case INIT: //未登录状态
doInit();
break;
case DISCARD: //出牌状态
doDiscard(that);
break;
case GAMEOVER: //游戏结束状态
doRestart();
break;
default:
break;
}
}
相关函数
//登录状态
function doInit() {
//获得唯一的id
window.id = new Date().getTime()+""+Math.floor(Math.random()*899+100);
pkObj.data.from = id;
socket.emit("add user", id);
}
//出牌状态
function doDiscard(that) {
var element = that.attr("id");
var temp = element.replace(/[p]([0-9]+)/g, "p");
switch (temp) {
//p表示点击了选择的牌
case "p":
break;
//点击出牌按钮
case "discard":
break;
//点击放弃按钮
case "abandon":
$(".click-up").each(function () {
$(this).removeClass("click-up");
});
socket.emit("abandon", pkObj.data.to);
$(".button-turn").addClass("hide-block");
pkObj.data.status = WAIT;
break;
//点击认输按钮
case "surrender":
var data = {
to: pkObj.data.to,
from: pkObj.data.from
};
socket.emit("surrender", data);
pkObj.data.status = GAMEOVER;
break;
default:
break;
}
}
2.5 出牌状态点击 "#box-below" 的牌,选中后上移显示
if (that.parent().attr("id") == "box-below") {
if (that.hasClass("click-up")) {
that.removeClass("click-up");
} else {
that.addClass("click-up");
}
}
2.6 点击出牌按钮将选中的牌显示在 “#preview-below” 里
利用.prop("outerHTML")
获取选中的牌的整个div代码
$(".click-up").each(function() {
list[i] = $(this).prop("outerHTML"); //获取选中的牌的整个div代码
$(this).removeClass("click-up");
$("#preview-below").append(list[i]);
$(this).remove();
i++;
});
2.7 将出牌的数组,对方id,己方id,出牌后剩余量 放在data对象里一起发给服务器,并将状态设置为WAIT
pkObj.data.remain -= list.length;
var data = {
discard: list,
from: pkObj.data.from,
to: pkObj.data.to,
num: list.length,
remain: pkObj.data.remain
};
pkObj.data.before = false;
$("#abandon").removeAttr("disabled");
socket.emit("discard", data);
$(".button-turn").addClass("hide-block");
pkObj.data.status = WAIT;
2.8 接收对方的牌,利用append()
放入 “#preview-above” 里
//接受对方的牌
socket.on("receive discard", function(data) {
var num = data.num;
var length = $(".opposite").length;
$("#preview-above").html("");
$("#preview-below").html("");
for (var index in data.discard) {
$("#preview-above").append(data.discard[index]);
}
$(".button-turn").removeClass("hide-block");
// $(".opposite:eq(" + i + ")").remove();
$(".box-upper").html("");
oppositePoker(length - num);
pkObj.data.status = DISCARD;
});
3. 判断牌组类型
关牌规则
1.牌数
一副牌,保留了所以牌,共54张;
2.发牌
由系统随机分发2家的牌,每家27张,不重复
3.出牌
- 第一次为黑桃3先出
- 牌的大小顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3。
- 牌形分为:单张、 一对、 三张、姐妹对(两张三张都可以连接,且连接数量无限)、顺子(数量无限制)、炸弹(不能4带1):
- 除了炸弹以外,普通牌形不允许对压,相同牌形只有比它大的才能出。
- 炸弹任何牌形都能出,炸弹的大小为:天王炸,2,A,K,Q,J,10,9,8,7,6,5,4,3。
思路
1. 客户端向服务器发送的是出牌的扑克块状代码数组
//利用$(selector).each 将每一个选中(上移)的扑克牌的html放进数组
var i = 0;
$(".click-up").each(function() {
list[i] = $(this).prop("outerHTML"); //获取选中的牌的整个html代码
$(this).removeClass("click-up");
$("#preview-below").append(list[i]);
$(this).remove();
i++;
});
2. 利用socket.io
发送到对方客户端,如下获取
//利用append动态添加到指定id为“preview-above”的div里
for (var index in data.discard) {
$("#preview-above").append(data.discard[index]);
}
把id为preview-above
元素里的扑克.poker
利用attr("id")
取出id
值并进行正则操作获取值
$("#preview-above").find(".poker").each(function() {
var pokerId = $(this).attr("id"); //atte取出id值
pokerId = pokerId.replace(/[p]([0-9]+)/g, "$1"); //获取选中的牌的编号(删掉id值首字母p)
aboveList[j] = getPokerFace(pokerId);
j++;
});
3. 判断牌的类型
3.1 由于扑克牌的序号是从1到54,但这并不是扑克牌的面值。转换操作:
//返回每张牌的面值
function getPokerFace(n) {
var temp = n % 13;
var result;
if(n == 53) { //大王
result = 17;
} else if(n == 54) { //小王
result = 16;
}
if(temp >= 3 && temp <= 12) { //3到Q
result = temp;
} else if(temp == 0) { //老K
result = 13;
} else if(temp == 1) { //A
result = 14;
} else if(temp == 2) { //2
result = 15;
}
return result;
}
3.2 获取到扑克牌面值,进行确定扑克牌数组的类型操作
//出牌类型:单,对,连对,炸
var ONE = "ONE";
var TWO = "TWO";
var TWO_2 = "TWO_2";
var THREE = "THREE";
var THREE_2 = "THREE_2";
var THREE_3 = "THREE_3";
var FOUR = "FOUR";
var STRAIGHT = "STRAIGHT";
var ERROR = "ERROR";
var KING = "KING";
//牌型状态机
function typeState(type, n, m) {
switch (type) {
//单
case ONE:
if(n == m) {
type = TWO;
} else if(n == m +1 && m == 16) {
type = KING;
} else if(n == m + 1){
type = STRAIGHT;
} else {
type = ERROR;
}
break;
//对
case TWO:
if(n == m) {
type = THREE;
} else if(n == m + 1){
type = TWO_2;
} else {
type = ERROR;
}
break;
case TWO_2:
if(n == m) {
type = TWO;
} else {
type = ERROR;
}
break;
case THREE:
if(n == m) {
type = FOUR;
} else if(n == m + 1){
type = THREE_2;
} else {
type = ERROR;
}
break;
case THREE_2:
if(n == m) {
type = THREE_3;
} else {
type = ERROR;
}
break;
case THREE_3:
if(n == m) {
type = THREE;
} else {
type = ERROR;
}
break;
case STRAIGHT:
if(n == m + 1) {
type = STRAIGHT;
} else {
type = ERROR;
}
break;
default:
break
}
return type;
}
//返回牌的类型和排列中最小牌的面值
function getPokerType(pokerList) {
var type = ONE;
var n, m;
for(var i = 1; i < pokerList.length; i++) {
n = pokerList[i-1];
m = pokerList[i];
type = typeState(type, n, m);
}
if(type == TWO_2 || type == THREE_2 || type == THREE_3 || (type == STRAIGHT && pokerList.length < 5)) {
type = ERROR;
}
var result = {
type: type,
val: m,
length: pokerList.length
};
return result;
}
3.3 获取我自己选中的牌
$("#box-below").find(".click-up").each(function() {
var pokerId = $(this).attr("id");
pokerId = pokerId.replace(/[p]([0-9]+)/g, "$1"); //获取选中的牌的id(删掉首字母p)
belowList[k] = getPokerFace(pokerId);
k++;
});
将两个扑克牌数组的类型进行比较并确定大小
//pkObj.data.before为true表示谁拿到♠3 或者 对方放弃我可以出任意牌,同时我出的牌必须符合规则,即type != ERROR
var myPoker = getPokerType(belowList);
//compare(aboveList, belowList)用于比较相同类型的牌的大小
if(compare(aboveList, belowList) || (pkObj.data.before && myPoker.type != ERROR))
//将选择出的牌和对方出的牌进行比较
function compare(list1, list2) {
var check = false;
var data1 = getPokerType(list1); //对方的牌类型
var data2 = getPokerType(list2); //我的牌类型
//1.类型相同的情况下再比较数组第一个元素大小;
//2.4表示炸,对方不是炸,我出炸则check为true
//3.天王炸
if((data1.type == data2.type && (data1.length == data2.length) && list2[0] > list1[0]) || (data1.type != FOUR && data2.type == FOUR) || (data2.type == KING)) {
check = true;
}
//check为true可出牌,否则不能出牌
return check;
}