美文网首页前端社团让前端飞
基于状态机模型的斗地主游戏(NodeJs&SocketI

基于状态机模型的斗地主游戏(NodeJs&SocketI

作者: 折柳画马 | 来源:发表于2016-11-27 14:45 被阅读153次

    NodeJs 实现斗地主游戏

    1. 系统结构

    系统考虑使用Nodejs和SocketIo实现服务器端逻辑,前端使用HTML5。

    2. 逻辑流程

    1 . 主要逻辑包括用户进入游戏、等待对家进入游戏、游戏过程、结束统计这4个过程。

    2 . 游戏过程的逻辑具体如下

    3 . 服务器-客户端通讯逻辑如下

    3. 客户端界面设计

    1 . 登录界面

    2 . 发牌界面

    <img src="http:https://img.haomeiwen.com/i3120669/eb47262f595a9382.gif?imageMogr2/auto-orient/strip" width="500">

    4. 数据结构

    4.1 牌型

    为了便于计算,使用一维数组定义每张扑克的index,根据图中顺序,按从左到右以及从上到下递增(即左上角的红桃A为0,右上角的红桃K为12,方块A为13,以此类推)

    <img src="http:https://img.haomeiwen.com/i3120669/d4a8e8c067b0f6ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="500">

    4.2 出牌规则

    • 牌的大小顺序:大王,小王,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。

    4.3 比较大小

    根据牌型用整数定义扑克的数值大小

    • 从3到K对应的value为2到12
    • A对应13
    • 2对应14
    • 大小王对应16与15

    5. 系统模块设计

    5.1 出牌对象

    var MODAL;
    $(init);
    function init() {
        new modal();
        //绑定页面上的出牌按钮,根据当前不同的状态运行不同的函数
        $("body").on("click","#sendCards",statusMachine);
    }
    function statusMachine() {}
    var modal = function () {
        var ptrThis;
        var modalBox = {
            //出牌对象的数据
            default:{
                //cards存储服务器发送过来的扑克数组
                cards:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,
                38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53],
                //当前游戏的状态,有DISCARD(发牌),WATING(等待),GAMEOVER(游戏结束)三个状态
                status:"",
                //myIndex为玩家所处于的座位的座位号
                myIndex:0,
                //leftIndex为位于进行游戏的玩家左边的玩家的座位号
                leftIndex:0,
                rightIndex:0,
                //turn与座位号对应,turn表示由对应的座位号玩家进行操作(例如发牌,放弃)
                turn:0,
                //若有两位玩家放弃出牌,则第三位玩家必须出牌,用于标志新的出牌回合的开始
                disCardTrue:false,
                //记录前一位玩家所处的牌,用于实现压牌逻辑
                formercardsType:{}
            },
            //$goal为待插入扑克的jquery对象,cardArray为扑克数组,isDelay为true则延迟插入(隔0.3s插入一张牌)
            placeCards:function ($goal,cardArray,isDelay) {},
            //sort函数所用到的比较函数,a,b都为扑克的index,将扑克按照value从大到小降序排列,value相同则按照花色排序
            comp:function (a,b) {},
            //变换当前扑克牌的状态,未选取->选取,选取->未选取
            toggleCard:function ($this) {},
            //将服务器发送的无序数组按照一定规则进行排序
            cardsSort:function (cards) {},
            //将已被选中并发送的扑克牌从手牌中删除
            removeCards:function () {},
            //判断从服务器发送扑克牌数组是由谁发出的,调用placeCards函数插入扑克
            //turn设置为下一位玩家,根据turn设置status
            //如果扑克牌已被出完,则根据最后一位出牌人来判断当前玩家是胜利还是失败
            justifyWhich:function (obj) {},
            //收到来自服务器转发的某一位玩家发送的投降信息
            someOneTouXiang:function (seats) {},
            //清空玩家发送的扑克
            clearCards:function () {},
            //绘制左右两位玩家的界面,objLeft为左边的玩家的信息,objRight同上
            drawothers:function (objLeft,objRight) {},
            //绘制玩家的界面,包含手牌,obj为相关信息
            drawuser:function (obj) {},
            //向目标jquery对象插入图片,$this为目标jquery对象,obj为相关信息(例如图片路径)
            insertImg:function ($this,obj) {},
            //移除目标jquery对象的图片,$this为目标jquery对象
            removeImg:function ($this) {},
            //开始游戏,seats为服务器发送过来的座位对应着的用户的信息,turn指定座位下标为turn的用户先出牌(turn由服务器的随机数产生)
            //存储服务器发送过来的扑克牌数组,调用cardsSort,drawothers,drawuser,placeCards,initPlay
            startGame:function (seats,turn) {},
            //出牌前的逻辑判断,判断牌是否能压过上家或者是否符合逻辑
            preSend:function () {},
            //在status为WATING时点击出牌调用的函数
            notYourTurn:function () {},
            //压牌逻辑的实现,temp存储着牌型,牌的值和牌的数量
            compWhichLarger:function (temp) {},
            //绑定座位点击坐下事件
            init:function () {},
            //游戏结束正常调用end函数,isWin为true则该玩家胜利
            end:function (isWin) {},
            //重开一局,array为来自服务器的扑克牌数组,turn为先出牌的人
            reStart:function (array,turn) {},
            //切换准备按钮的状态,准备->取消,取消->准备
            readyGame:function () {},
            //游戏结束
            gameover:function (isWin) {},
            //放弃出牌
            giveUp:function () {},
            //放弃出牌得到服务器回应
            giveUpReply:function (giupCount) {},
            //绑定一系列点击事件
            initPlay:function () {}
        }
        MODAL = modalBox;
        return modalBox.init();
    }
    

    5.2 出牌流程

    //出牌按钮绑定状态机,根据当前状态运行对应的函数,只有在处于DISCARD状态才能正常发牌
    $("body").on("click","#sendCards",statusMachine);
    function statusMachine() {
        switch(MODAL.default.status){
            case "DISCARD":
                //运行至preSend函数
                MODAL.preSend();
                break;
            case "WAITNG":
                MODAL.notYourTurn();
                break;
            case "GAMEOVER":
                MODAL.readyGame();
            default:
                break;
        }
    }
    var modalBox = {
            preSend:function () {
                var array  = new Array();
                //将被选择(用select来标识)的扑克牌的下标取出,插入数组array中
                $(".cardsLine .card").each(function () {
                    if($(this).hasClass("select")){
                        array.push($(this).attr("index"));
                    }
                });
                //compCards函数参数为排过序的array,因为用户手牌已经按照一定顺序排过序,所以按照一个方向取出来的牌也是具有一定是有序列的
                var temp = compCards(array);
                //console.log(compCards(array));
                //console.log(temp);
                //disCardTrue为true标识之前已经有两个人放弃出牌,所以不需要考虑压牌,只需要牌型符合一定规则即可出牌
                if(MODAL.default.disCardTrue){
                    if(temp.type!="ERR"){
                        socketFun.sendCards(array);
                    }else{
                        alert("无法出牌");
                    }
                }else{
                    //temp为储存array牌型以及大小等数据的对象,compWhichLarger函数则是将temp与上一位玩家发的牌进行比较,如果大于则flag为true
                    var flag = ptrThis.compWhichLarger(temp);
                    if(flag){
                        //将array发送至服务器,如果服务器将接受成功的消息发回,则调用 justifyWhich函数
                        socketFun.sendCards(array);
                    }else{
                        alert("无法出牌");
                    }
                }
                //ptrThis.sendCards();
            },
    
    
            justifyWhich:function (obj) {//ojb为服务器发送的消息,包含发牌人,发的牌的信息
                if(obj.posterIndex!=MODAL.default.myIndex){
                     //如果是别人出的牌,则储存该牌型
                    MODAL.default.formercardsType=compCards(obj.array);
                }
                MODAL.default.disCardTrue = false;
                var $goal;//$goal为待渲染的部位
    
    
                switch(obj.posterIndex){
                    case MODAL.default.myIndex:
                        ptrThis.removeCards();
                        $goal = $(".showCardLine");
                        break;
                    case MODAL.default.leftIndex:
                        $goal = $(".leftPlayer").children(".otherCards");
                        break;
                    case MODAL.default.rightIndex:
                        $goal = $(".rightPlayer").children(".otherCards");
                        break;
                    default:
                        break;
                }
    
                ptrThis.placeCards($goal,obj.array,false);
                //进入下一回合,轮次加一
                MODAL.default.turn = (MODAL.default.turn+1)%3;
                console.log("Now turn is"+MODAL.default.turn);
                //设置下一回合该玩家是出牌还是等待
                if(MODAL.default.turn==MODAL.default.myIndex){
                    MODAL.default.status = "DISCARD";
                }else{
                    MODAL.default.status = "WAITNG"
                }
                //如果某一位玩家出完牌,则游戏结束
                if(obj.sendOut){
                    if(obj.posterIndex==MODAL.default.myIndex){
                        ptrThis.end(true);
                    }else{
                        ptrThis.end(false);
                    }
    
                }
            }
    }
    

    5.3 客户端SocketIO消息模型

    var socket = io.connect('http://localhost:3000');
    var X = window.scriptData;                          //截取服务器发送过来的数据
        //收到服务器发送的不同的消息类型,调用对应的出牌模型中的函数
        socket.on("connect",function () {
            socket.emit("addUser",X._id);                   //添加用户
        })
        socket.on("playerSit",function (obj) {
            MODAL.insertImg($(".seat").eq(obj.index).children(),obj);
        })
        socket.on("leave",function (index) {
            MODAL.removeImg($(".seat").eq(index).children());
        })
        socket.on("seatsInfo",function (obj) {
            console.log("seatsInfo"+obj);
            for(var key in obj){
                console.log(key);
                MODAL.insertImg($(".seat").eq(obj[key].index).children(),obj[key]);
            }
        })
        socket.on("gameStart",function (obj,turn) {//服务器通知玩家游戏开始
            MODAL.startGame(obj,turn);
        })
        socket.on("postCards",function (obj) {//服务器返回出牌人以及出牌信息
            MODAL.justifyWhich(obj);
        })
        socket.on("reStart",function (array,turn) {//服务器返回重新开始游戏的信息
            MODAL.reStart(array,turn);
        })
        socket.on("giveup",function (giupCount) {//服务器返回放弃信息
            MODAL.giveUpReply(giupCount);
        })
        socket.on("renshu",function (seats) {
            MODAL.someOneTouXiang(seats);
        })
    var socketFun = {
        //出牌对象通过socketFun调用相关函数与服务器通信
        sit:function ($this) {
            var obj = {
                id:X._id,
                index:$this.parent().index()
            }
            socket.emit("sitSeat",obj);
        },
        sendCards:function (array) {
            var sendOut;
            if(($(".cardsLine .cards").children().length-array.length)==0){
                sendOut = true;
            }else{
                sendOut = false;
            }
            var obj = {
                array:array,
                posterIndex:MODAL.default.myIndex,
                sendOut:sendOut
            }
            socket.emit("postCards",obj);
        },
        readyMsg:function (obj) {//告知服务器该玩家准备
            socket.emit("readyMsg",obj);
        },
        giveUp:function () {//告知服务器放弃出牌
            socket.emit("giveup");
        },
        touxiang:function (index) {//告知服务器该玩家投降
            socket.emit("touxiang",index)
        }
    
    }
    

    5.4 压牌逻辑
    根据牌型数组判断牌型的逻辑使用状态机实现,其状态迁移图如下:

    008.png
    function compCards(array) {
        if(array.length==2&&data[array[0]].value==16&&data[array[1]].value==15){//天王炸
              var         cardsType={
                                count:array.length,
                                type:"KINGBOMB",
                                value:data[array[0]].value
                            };
               return cardsType;
        }
        //ptr指向array的下标
        var ptr;
        //end标志状态机是否结束
        var end = false;
        //data存储着每一张扑克的value,避免多次运算value
        var box = {
            cardsType:{
                count:array.length,
                type:"ONE",
                value:data[array[0]].value
            },
            setType:function (type) {
                this.cardsType.type = type;
            },
            statusOne:function () {
                if(this.cardsType.count==1){
                    end = true;
                    return ;
                }
                if(data[array[0]].value==data[array[1]].value){          //如果第一个和第二个数字相同
                    this.setType("TWO");
                    return ;
                }
                if(data[array[0]].value==data[array[1]].value+1){
                    this.setType("STRAIGHT");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusTwo:function () {
                if(this.cardsType.count==2){
                    end = true;
                    return ;
                }
                if(data[array[1]].value==data[array[2]].value){
                    this.setType("THREE");
                    return ;
                }
                if(data[array[1]].value==data[array[2]].value+1){
                    this.setType("TWO-ONE");
                }else{
                    this.setType("ERR");
                }
    
            },
            statusThree:function () {
                if(this.cardsType.count==3){
                    end = true;
                    return ;
                }
                if(data[array[2]].value==data[array[3]].value){
                    this.setType("BOMB");
                    return ;
                }
                if(data[array[2]].value==data[array[3]].value+1){
                    this.setType("THREE-ONE");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusStraight:function () {
                if(this.cardsType.count< 5){
                    this.setType("ERR");
                    end = true;
                    return ;
                }
                if(ptr< this.cardsType.count-1){
                    if(data[array[ptr]].value!=data[array[ptr+1]].value+1){
                        this.setType("ERR");
                        end = true;
                        return ;
                    }
                }else{
                    end = true;
                    return ;
                }
            },
            statusTwoOne:function () {
                if(ptr==this.cardsType.count-1){                //TwoOne处于中间状态,结束则出错
                    this.setType("ERR");
                    return ;
                }
                if(data[array[ptr]].value==data[array[ptr+1]].value){
                    this.setType("TWO-TWO");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusTwoTwo:function () {
                if(ptr==this.cardsType.count-1){
                    end = true;
                    return ;
                }
                if(data[array[ptr]].value==data[array[ptr]].value+1){
                    this.setType("TWO-ONE");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusThreeOne:function () {
                if(ptr==this.cardsType.count-1){
                    this.setType("ERR");
                    return ;
                }
                if(data[array[ptr]].value==data[array[ptr+1]].value){
                    this.setType("THREE-TWO");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusThreeTwo:function () {
                if(ptr==this.cardsType.count-1){
                    this.setType("ERR");
                    return ;
                }
                if(data[array[ptr]].value==data[array[ptr+1]].value){
                    this.setType("THREE-THREE");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusThreeThree:function () {
                if(ptr==this.cardsType.count-1){
                    end = true;
                    return ;
                }
                if(data[array[ptr]].value==data[array[ptr+1]].value+1){
                    this.setType("THREE-ONE");
                }else{
                    this.setType("ERR");
                }
                return ;
            },
            statusBomb:function () {
                if(ptr==this.cardsType.count-1){
                    end = true;
                    return ;
                }
                if(data[array[ptr]].value!=data[array[ptr+1]].value){
                    this.setType("ERR");
                }
            },
            ERR:function () {
                end = true;
                return ;
            }
        };
        for(ptr = 0;ptr< box.cardsType.count;++ptr){
            console.log("END:"+end);
            console.log(box.cardsType);
            if(end){
    
                break;
            }
    
            switch(box.cardsType.type){
                //ONE表示单张牌,这个ONE状态结束有效
                case "ONE":
                    box.statusOne();
                    break;
                //TWO表示一对,结束有效
                case "TWO":
                    box.statusTwo();
                    break;
                //THREE表示三张一样的牌,结束有效
                case "THREE":
                    box.statusThree();
                    break;
                //STRAIGHT表示顺子,根据array长度判断是否有效
                case "STRAIGHT":
                    box.statusStraight();
                    break;
                //TWO-ONE表示形如xx(x+1)(x+1)(x+2)的牌型,结束无效,返回类型ERR
                case "TWO-ONE":
                    box.statusTwoOne();
                    break;
                case "TWO-TWO":
                //TWO-TWO表示形如xx(x+1)(x+1)(x+2)(x+2)的牌型,结束有效
                    box.statusTwoTwo();
                    break;
                //THREE-ONE表示形如xxx(x+1)(x+1)(x+1)(x+2)的牌型,结束无效,返回类型ERR
                case "THREE-ONE":
                    box.statusThreeOne();
                    break;
                //THREE-TWO表示形如xxx(x+1)(x+1)(x+1)(x+2)(x+2)的牌型,结束无效,返回类型ERR
                case "THREE-TWO":
                    box.statusThreeTwo();
                    break;
                //THREE-THREE表示形如xxx(x+1)(x+1)(x+1)(x+2)(x+2)(x+2)的牌型,结束有效
                case "THREE-THREE":
                    box.statusThreeThree();
                    break;
                //BOMB表示炸弹,返回有效
                case "BOMB":
                    box.statusBomb();
                    break;
                //ERR表示牌型不合逻辑,无效
                case "ERR":
                    box.ERR();
                    break;
            }
        }
        return box.cardsType;
    
    }
    

    详细代码见GITHUB的pokepoke项目

    相关文章

      网友评论

        本文标题:基于状态机模型的斗地主游戏(NodeJs&SocketI

        本文链接:https://www.haomeiwen.com/subject/gwfnpttx.html