基于状态机模型的斗地主游戏(NodeJs&SocketIO)
1. 系统结构
系统考虑使用Nodejs和SocketIo实现服务器端逻辑,前端使用HTML5。
2. 逻辑流程
1 . 主要逻辑包括用户进入游戏、等待对家进入游戏、游戏过程、结束统计这4个过程。
2 . 游戏过程的逻辑具体如下
3 . 服务器-客户端通讯逻辑如下
3. 客户端界面设计
1 . 登录界面
2 . 发牌界面
4. 数据结构
4.1 牌型
为了便于计算,使用一维数组定义每张扑克的index,根据图中顺序,按从左到右以及从上到下递增(即左上角的红桃A为0,右上角的红桃K为12,方块A为13,以此类推)
>
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 压牌逻辑
根据牌型数组判断牌型的逻辑使用状态机实现,其状态迁移图如下:
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项目
相关推荐
嵌入式资讯精选 2020-10-15
benjonc 2020-07-04
setse 2020-07-04
Chaunceywu 2020-06-27
tuniumobile 2020-06-26
llxxyy0 2020-06-21
tobecrazy 2020-06-16
llxxyy0 2020-06-05
benjonc 2020-05-30
luyaoda0 2020-05-26
benjonc 2020-05-05
luyaoda0 2020-02-17
llxxyy0 2020-01-07
Chaunceywu 2019-12-18
tuniumobile 2019-12-18
luyaoda0 2019-12-08
Neumann 2019-12-05
Neumann 2019-12-04