目錄:
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. 判斷牌組類型
簡單撲克游戲規則.gif
關牌規則
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;
}