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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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

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

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

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

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

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

树莓派摄像头_Arducam 8MP重磅来袭,为树莓派4B构建完全同步的双摄像头方案~

大家好&#xff0c;我是小月月。双目立体景深一直是很热门的项目&#xff0c;最近已经有不少小伙伴学会在树莓派上插两个USB摄像头了&#xff0c;这个方案可以识别静态场景中各个物体的距离远近。但是两个USB摄像头捕获到的两组图像序列的时间并不是严格同步的&#xff0c;对运…

稀疏自编码器_自编码(AutoEncoder)模型及几种扩展之二

4. 稀疏自编码假设我们只有一个没有类别标签的训练样本集合{x(1),x(2)...},一个自编码神经网络就是一种非监督学习算法&#xff0c;它使用BP算法&#xff0c;并将目标值设为&#xff1a; y(i)x(i)。我们的目标是希望得到hW,b(X)≈x。用aj(2)(x)表示输入向量x对隐藏层单元j的激活…

linux x86 关机 过程,linux在x86上的中断处理过程(详细)

Linux在x86上的中断处理过程一&#xff1a;引言在Intel的文档中,把中断分为两种。一种是异常,也叫同步同断。一种称之为中断,也叫异常中断。同步中断指的是由CPU控制单元产生,之所以称之为同步,是因为只有一条指令执行完毕后才会发出中断。例如除法运算中,除数为零的时候,就会产…

linux 显卡转码,ffmpeg用GPU转码

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;有些视频编码太屌&#xff0c;播放起来对CPU消耗巨大&#xff0c;而在电视上或者电视盒子上看视频&#xff0c;编码就需要合适&#xff0c;楼主用的FireTV Stick&a…

c语言课程设计作业五子棋,C语言课程设计报告五子棋游戏课程设计

C语言课程设计报告五子棋游戏课程设计 C语言课程设计报告_五子棋游戏-课程设计C语言课程设计报告_五子棋游戏|c语言程序代码编程小程序设计|c语言课程设计报告课程案例**********************************************************//* 本程序在Turbo C或Borland C下编译通过 */…

redis内存淘汰和持久化_redis 持久化

一、RDB持久化&#xff08;一&#xff09;、RDB介绍可以在指定的时间间隔内生成数据集的 时间点快照&#xff08;point-in-time snapshot&#xff09;&#xff0c;新快照会覆盖老快照&#xff08;二&#xff09;、优点压缩格式&#xff0c;恢复速度快&#xff0c;适合于用做备份…

最新变态传奇android,新开变态传奇单职业

新开变态传奇单职业这款传奇手游中拥有这极为丰富的游戏玩法&#xff0c;玩家既可以进入各种副本中&#xff0c;挑战那些强大的副本boss&#xff0c;也可以和自己的兄弟好友们一起组队&#xff0c;前往各种跨服战场&#xff0c;和全服的高手玩家们一起进行最激烈的战斗。游戏中…

android手机微信收藏功能实现,Android模仿微信收藏文件的标签处理功能

最近需要用到微信的标签功能(如下图所示)。该功能可以添加已有标签&#xff0c;也可以自定义标签。也可以删除已编辑菜单。研究了一番。发现还是挺有意思的&#xff0c;模拟实现相关功能。该功能使用类似FlowLayout的功能。Flowlayout为一个开源软件(https://github.com/ApmeM/…

android 电话号码标记,强化电话标记功能 360手机卫士3.0安卓版体验

作为“房产商”&#xff0c;上海仁恒置地集团营销总监姚伟示通常每天也要接到20个左右房产或其他推销电话。“现在已经形成了一个机械的对应方式&#xff0c;对于此类推销电话&#xff0c;很多人包括我自己已经麻木了。”针对这种情况&#xff0c;360手机卫士发布了3.0.0正式版…

android 左滑按钮,android开发类似微信列表向左滑动按钮操作

话不多说&#xff0c;直接上代码&#xff0c;有详细的注释的。layout布局中&#xff1a;主要是跟大家说一下listview怎么写&#xff1a;android:id"id/pull_refresh_viewId"android:layout_width"match_parent"android:layout_height"match_parent&qu…

向量表示 运动抛物线_流动的美丽函数——抛物线浅谈

事先说明&#xff1a;笔者初三&#xff0c;如在叙述中有不严谨的地方&#xff0c;还请诸位指出&#xff0c;自当感激不尽。&#xff08;本文默认受众对象为初高中生&#xff0c;因此抛物线一律采取了yax的形式&#xff0c;高中的同学们可以应用旋转矩阵把它变到y2px的形式QAQ笔…

android 获取已安装 错误代码,android获取手机已经安装的app信息

Android获取手机已安装APP(系统/非系统)效果图主体代码private ListView mlistview;private ListpackageInfoList;private ListapplicationInfoList;private MyAdapter myAdapter;private PackageManager pm;Overrideprotected void onCreate(Bundle savedInstanceState) {supe…

android与ios ui切图关系,APP-IOS与Android界面设计与切图

做一全套的APP设计&#xff0c;流程是&#xff1a;1、界面设计&#xff1a;设计IOS界面&#xff1b;设计Android界面。2、切图&#xff1a;切IOS的2倍图和3倍图&#xff1b;切Android的hdpi,xhdpi,xxhdpi这三个尺寸的图。3、标注&#xff1a;以px为单位标注IOS界面的尺寸&#…

android app应用后台休眠,安卓手机锁屏后程序自动关闭,怎么设置手机app允许锁屏后台运行...

原标题&#xff1a;安卓手机锁屏后程序自动关闭&#xff0c;怎么设置手机app允许锁屏后台运行安卓手机锁屏后&#xff0c;很多程序就会自动关闭&#xff0c;实际上&#xff0c;这是安卓手机的一种保护机制。为了使系统能够流畅稳定的运行以及更加省电&#xff0c;它都会在手机锁…