找回密码
 立即注册

QQ登录

只需一步,快速开始

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

1. 系统结构

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

  2. 逻辑流程

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

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

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

  3. 客户端界面设计

  1 . 登录界面
103308xrptpgkhzydpq0hg.png

  2 . 发牌界面
103319ahp8scf2f7thhi8z.gif

  4. 数据结构

  4.1 牌型

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

  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 出牌对象
  1. var MODAL;
  2. $(init);
  3. function init() {
  4.     new modal();
  5.     //绑定页面上的出牌按钮,根据当前不同的状态运行不同的函数
  6.     $("body").on("click","#sendCards",statusMachine);
  7. }
  8. function statusMachine() {}
  9. var modal = function () {
  10.     var ptrThis;
  11.     var modalBox = {
  12.         //出牌对象的数据
  13.         default:{
  14.             //cards存储服务器发送过来的扑克数组
  15.             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,
  16.             38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53],
  17.             //当前游戏的状态,有DISCARD(发牌),WATING(等待),GAMEOVER(游戏结束)三个状态
  18.             status:"",
  19.             //myIndex为玩家所处于的座位的座位号
  20.             myIndex:0,
  21.             //leftIndex为位于进行游戏的玩家左边的玩家的座位号
  22.             leftIndex:0,
  23.             rightIndex:0,
  24.             //turn与座位号对应,turn表示由对应的座位号玩家进行操作(例如发牌,放弃)
  25.             turn:0,
  26.             //若有两位玩家放弃出牌,则第三位玩家必须出牌,用于标志新的出牌回合的开始
  27.             disCardTrue:false,
  28.             //记录前一位玩家所处的牌,用于实现压牌逻辑
  29.             formercardsType:{}
  30.         },
  31.         //$goal为待插入扑克的jquery对象,cardArray为扑克数组,isDelay为true则延迟插入(隔0.3s插入一张牌)
  32.         placeCards:function ($goal,cardArray,isDelay) {},
  33.         //sort函数所用到的比较函数,a,b都为扑克的index,将扑克按照value从大到小降序排列,value相同则按照花色排序
  34.         comp:function (a,b) {},
  35.         //变换当前扑克牌的状态,未选取->选取,选取->未选取
  36.         toggleCard:function ($this) {},
  37.         //将服务器发送的无序数组按照一定规则进行排序
  38.         cardsSort:function (cards) {},
  39.         //将已被选中并发送的扑克牌从手牌中删除
  40.         removeCards:function () {},
  41.         //判断从服务器发送扑克牌数组是由谁发出的,调用placeCards函数插入扑克
  42.         //turn设置为下一位玩家,根据turn设置status
  43.         //如果扑克牌已被出完,则根据最后一位出牌人来判断当前玩家是胜利还是失败
  44.         justifyWhich:function (obj) {},
  45.         //收到来自服务器转发的某一位玩家发送的投降信息
  46.         someOneTouXiang:function (seats) {},
  47.         //清空玩家发送的扑克
  48.         clearCards:function () {},
  49.         //绘制左右两位玩家的界面,objLeft为左边的玩家的信息,objRight同上
  50.         drawothers:function (objLeft,objRight) {},
  51.         //绘制玩家的界面,包含手牌,obj为相关信息
  52.         drawuser:function (obj) {},
  53.         //向目标jquery对象插入图片,$this为目标jquery对象,obj为相关信息(例如图片路径)
  54.         insertImg:function ($this,obj) {},
  55.         //移除目标jquery对象的图片,$this为目标jquery对象
  56.         removeImg:function ($this) {},
  57.         //开始游戏,seats为服务器发送过来的座位对应着的用户的信息,turn指定座位下标为turn的用户先出牌(turn由服务器的随机数产生)
  58.         //存储服务器发送过来的扑克牌数组,调用cardsSort,drawothers,drawuser,placeCards,initPlay
  59.         startGame:function (seats,turn) {},
  60.         //出牌前的逻辑判断,判断牌是否能压过上家或者是否符合逻辑
  61.         preSend:function () {},
  62.         //在status为WATING时点击出牌调用的函数
  63.         notYourTurn:function () {},
  64.         //压牌逻辑的实现,temp存储着牌型,牌的值和牌的数量
  65.         compWhichLarger:function (temp) {},
  66.         //绑定座位点击坐下事件
  67.         init:function () {},
  68.         //游戏结束正常调用end函数,isWin为true则该玩家胜利
  69.         end:function (isWin) {},
  70.         //重开一局,array为来自服务器的扑克牌数组,turn为先出牌的人
  71.         reStart:function (array,turn) {},
  72.         //切换准备按钮的状态,准备->取消,取消->准备
  73.         readyGame:function () {},
  74.         //游戏结束
  75.         gameover:function (isWin) {},
  76.         //放弃出牌
  77.         giveUp:function () {},
  78.         //放弃出牌得到服务器回应
  79.         giveUpReply:function (giupCount) {},
  80.         //绑定一系列点击事件
  81.         initPlay:function () {}
  82.     }
  83.     MODAL = modalBox;
  84.     return modalBox.init();
  85. }
复制代码
5.2 出牌流程
  1. //出牌按钮绑定状态机,根据当前状态运行对应的函数,只有在处于DISCARD状态才能正常发牌
  2. $("body").on("click","#sendCards",statusMachine);
  3. function statusMachine() {
  4.     switch(MODAL.default.status){
  5.         case "DISCARD":
  6.             //运行至preSend函数
  7.             MODAL.preSend();
  8.             break;
  9.         case "WAITNG":
  10.             MODAL.notYourTurn();
  11.             break;
  12.         case "GAMEOVER":
  13.             MODAL.readyGame();
  14.         default:
  15.             break;
  16.     }
  17. }
  18. var modalBox = {
  19.         preSend:function () {
  20.             var array  = new Array();
  21.             //将被选择(用select来标识)的扑克牌的下标取出,插入数组array中
  22.             $(".cardsLine .card").each(function () {
  23.                 if($(this).hasClass("select")){
  24.                     array.push($(this).attr("index"));
  25.                 }
  26.             });
  27.             //compCards函数参数为排过序的array,因为用户手牌已经按照一定顺序排过序,所以按照一个方向取出来的牌也是具有一定是有序列的
  28.             var temp = compCards(array);
  29.             //console.log(compCards(array));
  30.             //console.log(temp);
  31.             //disCardTrue为true标识之前已经有两个人放弃出牌,所以不需要考虑压牌,只需要牌型符合一定规则即可出牌
  32.             if(MODAL.default.disCardTrue){
  33.                 if(temp.type!="ERR"){
  34.                     socketFun.sendCards(array);
  35.                 }else{
  36.                     alert("无法出牌");
  37.                 }
  38.             }else{
  39.                 //temp为储存array牌型以及大小等数据的对象,compWhichLarger函数则是将temp与上一位玩家发的牌进行比较,如果大于则flag为true
  40.                 var flag = ptrThis.compWhichLarger(temp);
  41.                 if(flag){
  42.                     //将array发送至服务器,如果服务器将接受成功的消息发回,则调用 justifyWhich函数
  43.                     socketFun.sendCards(array);
  44.                 }else{
  45.                     alert("无法出牌");
  46.                 }
  47.             }
  48.             //ptrThis.sendCards();
  49.         },


  50.         justifyWhich:function (obj) {//ojb为服务器发送的消息,包含发牌人,发的牌的信息
  51.             if(obj.posterIndex!=MODAL.default.myIndex){
  52.                  //如果是别人出的牌,则储存该牌型
  53.                 MODAL.default.formercardsType=compCards(obj.array);
  54.             }
  55.             MODAL.default.disCardTrue = false;
  56.             var $goal;//$goal为待渲染的部位


  57.             switch(obj.posterIndex){
  58.                 case MODAL.default.myIndex:
  59.                     ptrThis.removeCards();
  60.                     $goal = $(".showCardLine");
  61.                     break;
  62.                 case MODAL.default.leftIndex:
  63.                     $goal = $(".leftPlayer").children(".otherCards");
  64.                     break;
  65.                 case MODAL.default.rightIndex:
  66.                     $goal = $(".rightPlayer").children(".otherCards");
  67.                     break;
  68.                 default:
  69.                     break;
  70.             }

  71.             ptrThis.placeCards($goal,obj.array,false);
  72.             //进入下一回合,轮次加一
  73.             MODAL.default.turn = (MODAL.default.turn+1)%3;
  74.             console.log("Now turn is"+MODAL.default.turn);
  75.             //设置下一回合该玩家是出牌还是等待
  76.             if(MODAL.default.turn==MODAL.default.myIndex){
  77.                 MODAL.default.status = "DISCARD";
  78.             }else{
  79.                 MODAL.default.status = "WAITNG"
  80.             }
  81.             //如果某一位玩家出完牌,则游戏结束
  82.             if(obj.sendOut){
  83.                 if(obj.posterIndex==MODAL.default.myIndex){
  84.                     ptrThis.end(true);
  85.                 }else{
  86.                     ptrThis.end(false);
  87.                 }

  88.             }
  89.         }
  90. }
复制代码
5.3 客户端SocketIO消息模型
  1. var socket = io.connect('http://localhost:3000');
  2. var X = window.scriptData;                          //截取服务器发送过来的数据
  3.     //收到服务器发送的不同的消息类型,调用对应的出牌模型中的函数
  4.     socket.on("connect",function () {
  5.         socket.emit("addUser",X._id);                   //添加用户
  6.     })
  7.     socket.on("playerSit",function (obj) {
  8.         MODAL.insertImg($(".seat").eq(obj.index).children(),obj);
  9.     })
  10.     socket.on("leave",function (index) {
  11.         MODAL.removeImg($(".seat").eq(index).children());
  12.     })
  13.     socket.on("seatsInfo",function (obj) {
  14.         console.log("seatsInfo"+obj);
  15.         for(var key in obj){
  16.             console.log(key);
  17.             MODAL.insertImg($(".seat").eq(obj[key].index).children(),obj[key]);
  18.         }
  19.     })
  20.     socket.on("gameStart",function (obj,turn) {//服务器通知玩家游戏开始
  21.         MODAL.startGame(obj,turn);
  22.     })
  23.     socket.on("postCards",function (obj) {//服务器返回出牌人以及出牌信息
  24.         MODAL.justifyWhich(obj);
  25.     })
  26.     socket.on("reStart",function (array,turn) {//服务器返回重新开始游戏的信息
  27.         MODAL.reStart(array,turn);
  28.     })
  29.     socket.on("giveup",function (giupCount) {//服务器返回放弃信息
  30.         MODAL.giveUpReply(giupCount);
  31.     })
  32.     socket.on("renshu",function (seats) {
  33.         MODAL.someOneTouXiang(seats);
  34.     })
  35. var socketFun = {
  36.     //出牌对象通过socketFun调用相关函数与服务器通信
  37.     sit:function ($this) {
  38.         var obj = {
  39.             id:X._id,
  40.             index:$this.parent().index()
  41.         }
  42.         socket.emit("sitSeat",obj);
  43.     },
  44.     sendCards:function (array) {
  45.         var sendOut;
  46.         if(($(".cardsLine .cards").children().length-array.length)==0){
  47.             sendOut = true;
  48.         }else{
  49.             sendOut = false;
  50.         }
  51.         var obj = {
  52.             array:array,
  53.             posterIndex:MODAL.default.myIndex,
  54.             sendOut:sendOut
  55.         }
  56.         socket.emit("postCards",obj);
  57.     },
  58.     readyMsg:function (obj) {//告知服务器该玩家准备
  59.         socket.emit("readyMsg",obj);
  60.     },
  61.     giveUp:function () {//告知服务器放弃出牌
  62.         socket.emit("giveup");
  63.     },
  64.     touxiang:function (index) {//告知服务器该玩家投降
  65.         socket.emit("touxiang",index)
  66.     }

  67. }
复制代码
5.4 压牌逻辑根据牌型数组判断牌型的逻辑使用状态机实现,其状态迁移图如下:
103025dnjb5lbgyyo06nyk.png

  1. function compCards(array) {
  2.     if(array.length==2&&data[array[0]].value==16&&data[array[1]].value==15){//天王炸
  3.           var         cardsType={
  4.                             count:array.length,
  5.                             type:"KINGBOMB",
  6.                             value:data[array[0]].value
  7.                         };
  8.            return cardsType;
  9.     }
  10.     //ptr指向array的下标
  11.     var ptr;
  12.     //end标志状态机是否结束
  13.     var end = false;
  14.     //data存储着每一张扑克的value,避免多次运算value
  15.     var box = {
  16.         cardsType:{
  17.             count:array.length,
  18.             type:"ONE",
  19.             value:data[array[0]].value
  20.         },
  21.         setType:function (type) {
  22.             this.cardsType.type = type;
  23.         },
  24.         statusOne:function () {
  25.             if(this.cardsType.count==1){
  26.                 end = true;
  27.                 return ;
  28.             }
  29.             if(data[array[0]].value==data[array[1]].value){          //如果第一个和第二个数字相同
  30.                 this.setType("TWO");
  31.                 return ;
  32.             }
  33.             if(data[array[0]].value==data[array[1]].value+1){
  34.                 this.setType("STRAIGHT");
  35.             }else{
  36.                 this.setType("ERR");
  37.             }
  38.             return ;
  39.         },
  40.         statusTwo:function () {
  41.             if(this.cardsType.count==2){
  42.                 end = true;
  43.                 return ;
  44.             }
  45.             if(data[array[1]].value==data[array[2]].value){
  46.                 this.setType("THREE");
  47.                 return ;
  48.             }
  49.             if(data[array[1]].value==data[array[2]].value+1){
  50.                 this.setType("TWO-ONE");
  51.             }else{
  52.                 this.setType("ERR");
  53.             }

  54.         },
  55.         statusThree:function () {
  56.             if(this.cardsType.count==3){
  57.                 end = true;
  58.                 return ;
  59.             }
  60.             if(data[array[2]].value==data[array[3]].value){
  61.                 this.setType("BOMB");
  62.                 return ;
  63.             }
  64.             if(data[array[2]].value==data[array[3]].value+1){
  65.                 this.setType("THREE-ONE");
  66.             }else{
  67.                 this.setType("ERR");
  68.             }
  69.             return ;
  70.         },
  71.         statusStraight:function () {
  72.             if(this.cardsType.count< 5){
  73.                 this.setType("ERR");
  74.                 end = true;
  75.                 return ;
  76.             }
  77.             if(ptr< this.cardsType.count-1){
  78.                 if(data[array[ptr]].value!=data[array[ptr+1]].value+1){
  79.                     this.setType("ERR");
  80.                     end = true;
  81.                     return ;
  82.                 }
  83.             }else{
  84.                 end = true;
  85.                 return ;
  86.             }
  87.         },
  88.         statusTwoOne:function () {
  89.             if(ptr==this.cardsType.count-1){                //TwoOne处于中间状态,结束则出错
  90.                 this.setType("ERR");
  91.                 return ;
  92.             }
  93.             if(data[array[ptr]].value==data[array[ptr+1]].value){
  94.                 this.setType("TWO-TWO");
  95.             }else{
  96.                 this.setType("ERR");
  97.             }
  98.             return ;
  99.         },
  100.         statusTwoTwo:function () {
  101.             if(ptr==this.cardsType.count-1){
  102.                 end = true;
  103.                 return ;
  104.             }
  105.             if(data[array[ptr]].value==data[array[ptr]].value+1){
  106.                 this.setType("TWO-ONE");
  107.             }else{
  108.                 this.setType("ERR");
  109.             }
  110.             return ;
  111.         },
  112.         statusThreeOne:function () {
  113.             if(ptr==this.cardsType.count-1){
  114.                 this.setType("ERR");
  115.                 return ;
  116.             }
  117.             if(data[array[ptr]].value==data[array[ptr+1]].value){
  118.                 this.setType("THREE-TWO");
  119.             }else{
  120.                 this.setType("ERR");
  121.             }
  122.             return ;
  123.         },
  124.         statusThreeTwo:function () {
  125.             if(ptr==this.cardsType.count-1){
  126.                 this.setType("ERR");
  127.                 return ;
  128.             }
  129.             if(data[array[ptr]].value==data[array[ptr+1]].value){
  130.                 this.setType("THREE-THREE");
  131.             }else{
  132.                 this.setType("ERR");
  133.             }
  134.             return ;
  135.         },
  136.         statusThreeThree:function () {
  137.             if(ptr==this.cardsType.count-1){
  138.                 end = true;
  139.                 return ;
  140.             }
  141.             if(data[array[ptr]].value==data[array[ptr+1]].value+1){
  142.                 this.setType("THREE-ONE");
  143.             }else{
  144.                 this.setType("ERR");
  145.             }
  146.             return ;
  147.         },
  148.         statusBomb:function () {
  149.             if(ptr==this.cardsType.count-1){
  150.                 end = true;
  151.                 return ;
  152.             }
  153.             if(data[array[ptr]].value!=data[array[ptr+1]].value){
  154.                 this.setType("ERR");
  155.             }
  156.         },
  157.         ERR:function () {
  158.             end = true;
  159.             return ;
  160.         }
  161.     };
  162.     for(ptr = 0;ptr< box.cardsType.count;++ptr){
  163.         console.log("END:"+end);
  164.         console.log(box.cardsType);
  165.         if(end){

  166.             break;
  167.         }

  168.         switch(box.cardsType.type){
  169.             //ONE表示单张牌,这个ONE状态结束有效
  170.             case "ONE":
  171.                 box.statusOne();
  172.                 break;
  173.             //TWO表示一对,结束有效
  174.             case "TWO":
  175.                 box.statusTwo();
  176.                 break;
  177.             //THREE表示三张一样的牌,结束有效
  178.             case "THREE":
  179.                 box.statusThree();
  180.                 break;
  181.             //STRAIGHT表示顺子,根据array长度判断是否有效
  182.             case "STRAIGHT":
  183.                 box.statusStraight();
  184.                 break;
  185.             //TWO-ONE表示形如xx(x+1)(x+1)(x+2)的牌型,结束无效,返回类型ERR
  186.             case "TWO-ONE":
  187.                 box.statusTwoOne();
  188.                 break;
  189.             case "TWO-TWO":
  190.             //TWO-TWO表示形如xx(x+1)(x+1)(x+2)(x+2)的牌型,结束有效
  191.                 box.statusTwoTwo();
  192.                 break;
  193.             //THREE-ONE表示形如xxx(x+1)(x+1)(x+1)(x+2)的牌型,结束无效,返回类型ERR
  194.             case "THREE-ONE":
  195.                 box.statusThreeOne();
  196.                 break;
  197.             //THREE-TWO表示形如xxx(x+1)(x+1)(x+1)(x+2)(x+2)的牌型,结束无效,返回类型ERR
  198.             case "THREE-TWO":
  199.                 box.statusThreeTwo();
  200.                 break;
  201.             //THREE-THREE表示形如xxx(x+1)(x+1)(x+1)(x+2)(x+2)(x+2)的牌型,结束有效
  202.             case "THREE-THREE":
  203.                 box.statusThreeThree();
  204.                 break;
  205.             //BOMB表示炸弹,返回有效
  206.             case "BOMB":
  207.                 box.statusBomb();
  208.                 break;
  209.             //ERR表示牌型不合逻辑,无效
  210.             case "ERR":
  211.                 box.ERR();
  212.                 break;
  213.         }
  214.     }
  215.     return box.cardsType;

  216. }
复制代码


本教程由无限星辰工作室CRX349独家整理和提供,转载请注明地址,谢谢。本文地址:https://xmspace.net/thread-527-1-1.html
无限星辰工作室  好集导航 Discuz全集下载  星辰站长网  集热爱361  一品文学  手机小游戏合集   海外空间网 星辰api  星辰支付二维码管理平台 LOT智能硬件聚合平台 阿里云服务器 腾讯云服务器
服务Discuz!建站|DiscuzQ配置|二开|小程序|APP|搬家|挂马清理|防护|Win/Linux环境搭建|优化|运维|
服务理念:专业 诚信 友好QQ842062626 服务项目 Q群315524225

发表于 2017-6-20 02:12:06 | 显示全部楼层 |阅读模式

回复 | 使用道具 举报

该帖共收到 0 条回复!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

美图秀

    • 自建远程桌面服务器 rustdesk
    • Discuz!x3.4 账号保镖 自动冻结账号无效解
    • PVE换主板后 网络丢失解决方法
    • Kvm 虚拟机迁移到PVE里面
    • Discuz!x3,4 阿里云DCDN配置获取客户端ip
拖动客服框
Online Service
点击这里给我发消息
点击这里联系我们
微信扫一扫
在线客服
快速回复 返回顶部 返回列表