双人扑克-总结文档.md

在线双人扑克文档说明

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<outCards.length;i++){
            copy[i] = outCards[i];
        }
        //对每一张牌遍历,进入cardType安检  实质上是默认获取第一张牌,然后从第二张牌开始逐张检验
        copy[0] = valuePre(copy[0]);
        CARDTYPE = "ONE";
        STARTVALUE = copy[0];
        CARDCOUNT = 1;
        for (var i = 0; i < copy.length - 1; i++) {
            copy[i + 1] = valuePre(copy[i + 1]);
            // alert("又读到一张" + copy[i+1]);
            cardTypeMachine(copy[i],copy[i+1]);
        }
        //对最后的牌组判定进行筛选,把未完态也转化为ERR状态
        //  CARDTYPE :  PAIRPLUS / TRIPLEPLUS  / TRIPLEDOUBEL   半完成态
        if(CARDTYPE == "PAIRPLUS" || CARDTYPE == "TRIPLEPLUS" || CARDTYPE == "TRIPLEDOUBEL"){
            CARDTYPE = "ERR";
        }
        if(CARDTYPE == "STRAIGHT" && CARDCOUNT < 5){
            CARDTYPE = "ERR";
        }
        var outObj = {
            "cardType":CARDTYPE,
            "startValue":STARTVALUE,
            "cardCount": CARDCOUNT
        };
        console.dir(outObj);
        console.dir(rival);
        //当出牌符合规则,并且牌面比对方大,就可以压牌
        // 判断我方出牌是否能够压住对方来牌
        var comp = cardCompare(outObj,rival);   //outObj >> 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<outCards.length;i++){
            copy[i] = outCards[i];
        }
        //对每一张牌遍历,进入cardType安检  实质上是默认获取第一张牌,然后从第二张牌开始逐张检验
        copy[0] = valuePre(copy[0]);
        CARDTYPE = "ONE";
        STARTVALUE = copy[0];
        CARDCOUNT = 1;
        for (var i = 0; i < copy.length - 1; i++) {
            copy[i + 1] = valuePre(copy[i + 1]);
            // alert("又读到一张" + copy[i+1]);
            cardTypeMachine(copy[i],copy[i+1]);
        }
        //对最后的牌组判定进行筛选,把未完态也转化为ERR状态
        //  CARDTYPE :  PAIRPLUS / TRIPLEPLUS  / TRIPLEDOUBEL   半完成态
        if(CARDTYPE == "PAIRPLUS" || CARDTYPE == "TRIPLEPLUS" || CARDTYPE == "TRIPLEDOUBEL"){
            CARDTYPE = "ERR";
        }
        if(CARDTYPE == "STRAIGHT" && CARDCOUNT < 5){
            CARDTYPE = "ERR";
        }
        var outObj = {
            "cardType":CARDTYPE,
            "startValue":STARTVALUE,
            "cardCount": CARDCOUNT
        };

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;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容

  • 两人斗地主 一、体系结构图 通讯模型 大功能模块切换关系 二、逻辑流程图 登录login.PNG 主页面开始界面....
    wuyumumu阅读 505评论 0 0
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,974评论 6 13
  • 目录: 1. 体系结构 2. 服务器-客户端通讯图 客户端处理 用户点击登录按钮进入排队,并发给服务器随机 id,...
    淡就加点盐阅读 1,071评论 1 3
  • 体系结构图 逻辑流程图 逻辑流程图详细 通讯图 图片参考简书作者kamidox的文章使用 Sublime + Pl...
    ofelia_why阅读 957评论 0 3
  • 你只是我曾经悄悄地喜欢过的人而已 文/小花 长大以后,我明白了其实有些喜欢,不一定有美好的结局。就像我喜欢你,但不...
    库鲁小樱阅读 166评论 0 0