php socket开发斗地主,基于状态机模型的斗地主游戏(NodeJsSocketIO)

1. 系统结构

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

1460000007643089

2. 逻辑流程

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

1460000007643090

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

1460000007643091

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

1460000007643092

3. 客户端界面设计

1 . 登录界面

1460000007643093

2 . 发牌界面

1460000007643120?w=1074&h=791

4. 数据结构

4.1 牌型

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

1460000007643121>

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 压牌逻辑

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

1460000007643094

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项目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/534798.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python几何拼贴画_图形几何图形拼贴画

四边形教学内容:教材第34页?——36页教学目标:1.直观感知四边形,能区分和辨认四边形,知道四边形的特征.进一步认识长方形和正方形,知道它们的角都是直角.2.通过画一画、找一找、拼一拼等活动,培养学生[此文转于斐斐课件园?FFKJ.Net]的观察比较和概括抽象的能力,发展空间想象能…

wgs84转经纬度_wgs84经纬度转为cgcs2000平面坐标

航拍测绘中,pos数据多数是wgs84&#xff0c;而GCP坐标是2000或者80坐标。有如下2情况需要转换坐标&#xff0c;1.不同坐标系刺点时&#xff0c;位置相差较远&#xff0c;影响工作效率。2.该软件不支持wgs84转国家2000&#xff0c;比如photoscan。所以需要把pos数据转为跟像控点…

php system 255,GitHub - dwg255/OA-SYS: OA办公系统开源项目

OA-SYSOA办公系统开源项目这是一套主要用于演示、学习为目的OA办公自动化系统。该系统具备基本的自动化办公功能。在使用前请确保您已阅读本系统协议部分内容。主要功能列表用户和用户组、权限管理个人网盘和分享文件生产计划任务和业绩考评通讯录和内部短消息公告系统个人工作…

wpcomments post.php,WordPress移动wp-comments-post文件防垃圾评论

将wp-comments-post文件移动到主题目录中达到阻止垃圾评论。需要修改程序文件&#xff1a;一、首先打开Wordpress程序根目录的wp-comments-pos.php文件&#xff0c;用&#xff1a;require( dirname(__FILE__) . /../../../wp-load.php );替换第16行的&#xff1a;require( dirn…

人工蜂群算法python_改进的人工蜂群算法解决聚类问题(在Python中的分步实现)...

在之前的文章中&#xff0c;我介绍了如何通过实施名为Artificial Bee Colony(ABC)的群集智能(SI)算法来解决现实世界中的优化问题。现在是时候让我们掌握一些真实的数据并解释我们如何使用我们的ABC算法的Python实现来执行群集任务。但在此之前&#xff0c;让我们深入了解一下聚…

python涉及到的逻辑_涉及逻辑运算的python表达式的运算顺序

以下代码并不报错&#xff0c;而我理解在”point 1″处如果先运算小括号里面的表达式&#xff0c;而y并不存在&#xff0c;不应该报错吗&#xff1f;难道发现x 10的短路运算优先于小括号里面的表达式运算&#xff1f;将这一行中x 10改为x 100后报错了&#xff0c;这个理解没…

oracle sql last_value,図でイメージするOracle DatabaseのSQL全集 第3回 分析関数

指定したソートキーでの、最初の行の値を求めるのが、First_Value関数。指定したソートキーでの、最後の行の値を求めるのが、Last_Value関数。指定したソートキーでの、(Row_Numberな順位が)n番目の行の値を求めるのが、nth_Value関数となります。Oracle11gR2でnth_Value関数が…

python读取字典元素笔记_Python 学习笔记 - 字典

Python 字典字典是一种无序的、可修改的、可索引的容器。字典使用花括号声明&#xff0c;具有键和值。mydic {name:孙悟空,origin:猴子}print(mydic)结果&#xff1a;{name: 孙悟空, origin: 猴子}访问字典mydic {name:孙悟空,origin:猴子}print(mydic[name])结果&#xff1a…

oracle =1,oracle中的 where 1=1 和where 1 !=1

11 永真&#xff0c; 1<>1 永假。1<>1 的用处&#xff1a; 用于只取结构不取数据的场合例如&#xff1a;create table table_ly_temp tablespace ly_temp asselect * from table_ly where 1<>112建成一个与table_ly 结构相同的表table_ly_temp &#xff0c;但…

企业信息化投入中咨询服务_全过程工程咨询服务核心价值是什么

建筑行业服务现状传统建设工程的目标、计划、控制都以参与单位个体为主要对象&#xff0c;项目管理的阶段性和局部性割裂了项目的内在联系&#xff0c;导致项目管理存在明显的管理弊端&#xff0c;这种模式已经与国际主流的建设管理模式脱轨。“专而不全”、“多小散”企业的参…

sp_addlinkedserver oracle,sp_addlinkedserver使用方法

Exec sp_droplinkedsrvlogin DBVIP,Null --删除映射(录与链接服务器上远程登录之间的映射)Exec sp_dropserver DBVIP --删除远程服务器链接EXEC sp_addlinkedserverserverDBVIP,--被访问的服务器别名srvproduct,providerSQLOLEDB,datasrc"…

oracle的undo表空间不足,undo表空间不足,ORA-30036 unable to extend segment by ...

一、一次更新或者删除大量数据&#xff0c;这些数据需要保存在undo表空间中(直到提交或回滚后这些undo表空间中的数据才允许被覆盖)&#xff0c;如果undo表空间不足&#xff0c;就会报ORA-30036错误。下面是两种解决办法&#xff1a;1、增加undo表空间大小&#xff0c;或将undo…

交流信号叠加直流偏置_放大器偏置电流Ib需要完整的直流回路

多数工程师对于放大器偏置电流参数并不陌生&#xff0c;它是导致放大器电路产生直流噪声的又一重要影响因素。因为偏置电流经过输入端电阻网络会形成一个失调电压源&#xff0c;再通过电路的噪声增益影响输出直流噪声。所以工程师会注重电阻网络的匹配&#xff0c;降低偏置电流…

teamviewer类似软件_TeamViewer系统后台被黑客组织APT攻破,请注意防范

TeamViewer事件10月11日&#xff0c;深圳市网络与信息安全信息通报中心发布了一份编号为“2019029”的名为《关于TeamViewer客户端被远程控制的紧急通报》文件&#xff0c;文件指出目前知名远程办公工具TeamViewer已经被境外黑客组织APT41攻破&#xff0c;提醒企业组织做好防护…

linux上dig命令,Linux dig命令(示例代码)

dig(Domain Information Groper)&#xff0c;和nslookup作用有些类似&#xff0c;都是DNS查询工具1、dig命令格式dig dnsserver name querytype如果你设置的dnsserver是一个域名&#xff0c;首先通过默认的上连DNS服务器去查询对应的IP地址&#xff0c;然后再以设置的dnsserver…

python实现表格线性回归_Python实现线性回归

一、线性回归原理二、python实现线性回归1.基本矩阵运算pratice1.py:# Author:WYCimport numpy as npfrom numpy.linalg import invfrom numpy import dotfrom numpy import matprint(-------------给定矩阵A,B----------)A np.mat([1,1])print (A:\n,A)B mat([[1,2],[2,3]])…

linux后台启动脚本nohup,linux下后台执行shell脚本nohup

【GSM】GTM900C的应用——短信虽说GSM已经很老旧,但其低廉的价格,非常适合一些需要小数据上网传输和短信等功能的应用场合. 不知道GSM能否像51单片机一样,在低端应用中长久不衰.GTM900C发送短信,支持两种模式,TXT和PD ...iOS UITabBarController的使用UITabBarController 和 UI…

华为4g模块 linux驱动程序,定制Android之4G-LTE模块驱动

定制Android之4G-LTE模块驱动一&#xff0e; 简介本文讲述在Android内核中&#xff0c;添加中国移动4G-LTE制式华为MU909模块驱动&#xff0c;实现通过4G上网业务&#xff0c;电话业务&#xff0c;短信业务。CPU&#xff1a;Samsung 6410Kernel&#xff1a;linux 2.6.36…

软件测试用例_软件测试用例设计实战场景法

不点蓝字&#xff0c;我们哪来故事&#xff1f; 目录场景法扩展例子场景法介绍影子场景法用例设计举例场景法设计用例步骤和表示场景法举例 总结 场景法的注意点场景法影子本来想直接跳过场景法的&#xff0c;今天群友提出问题&#xff1a;1、面试官问&#xff1a;场景…

linux下printf语句执行时间,linux下printf中\n刷新缓冲区的疑问(已解决)

#include #include int main(void){ printf("hello world");close(STDOUT_FILENO); return 0;}//什么都不输出12345678910111234567891011#include #include int main(void){ printf("hello world\n");close(STDOUT_FILENO); …