登录游戏服务器
1: 客户端在GameNetMgr.js中,通过connectGameServer方法连接到游戏服务器,并发送“login”命令。
var sd = {
token:data.token,
roomid:data.roomid,
time:data.time,
sign:data.sign,
};
2: 服务器在socket_service.js中监听到login事件后,进行相应的处理。
//启动服务
exports.start = function(conf,mgr){
config = conf;
var httpServer = require('http').createServer(app);
io = require('socket.io')(httpServer);
httpServer.listen(config.CLIENT_PORT);
//监听是否有客户端连接进来,如果有客户连接进来就会调用这个函数
//为这个客户创建一个socket通道
io.sockets.on('connection',function(socket){
//监听一个事件,然后data,客户端发送过来的数据
//数据使用的是json协议的编码,来进行数据交换;底层发送json文本;
socket.on('login',function(data){
//转成json格式数据
data = JSON.parse(data);
if(socket.userId != null){
//已经登陆过的就忽略
return;
}
var token = data.token;
var roomId = data.roomid;
var time = data.time;
var sign = data.sign;
console.log(roomId);
console.log(token);
console.log(time);
console.log(sign);
//检查参数合法性
if(token == null || roomId == null || sign == null || time == null){
console.log(1);
socket.emit('login_result',{errcode:1,errmsg:"invalid parameters"});
return;
}
//检查参数是否被篡改
var md5 = crypto.md5(roomId + token + time + config.ROOM_PRI_KEY);
if(md5 != sign){
console.log(2);
//emit发送一个事件
socket.emit('login_result',{errcode:2,errmsg:"login failed. invalid sign!"});
return;
}
//检查token是否有效
if(tokenMgr.isTokenValid(token)==false){
console.log(3);
socket.emit('login_result',{errcode:3,errmsg:"token out of time."});
return;
}
//检查房间合法性
var userId = tokenMgr.getUserID(token);
var roomId = roomMgr.getUserRoom(userId);
userMgr.bind(userId,socket);
socket.userId = userId;
//返回房间信息
var roomInfo = roomMgr.getRoom(roomId);
var seatIndex = roomMgr.getUserSeat(userId);
roomInfo.seats[seatIndex].ip = socket.handshake.address;
var userData = null;
var seats = [];
for(var i = 0; i < roomInfo.seats.length; ++i){
var rs = roomInfo.seats[i];
var online = false;
if(rs.userId > 0){
online = userMgr.isOnline(rs.userId);
}
seats.push({
userid:rs.userId,
ip:rs.ip,
score:rs.score,
name:rs.name,
online:online,
ready:rs.ready,
seatindex:i
});
if(userId == rs.userId){
userData = seats[i];
}
}
//通知前端
var ret = {
errcode:0,
errmsg:"ok",
data:{
roomid:roomInfo.id,
conf:roomInfo.conf,
numofgames:roomInfo.numOfGames,
seats:seats
}
};
socket.emit('login_result',ret);
//通知其它客户端
userMgr.broacastInRoom('new_user_comes_push',userData,userId);
socket.gameMgr = roomInfo.gameMgr;
//玩家上线,强制设置为TRUE
socket.gameMgr.setReady(userId);
socket.emit('login_finished');
if(roomInfo.dr != null){
var dr = roomInfo.dr;
var ramaingTime = (dr.endTime - Date.now()) / 1000;
var data = {
time:ramaingTime,
states:dr.states
}
userMgr.sendMsg(userId,'dissolve_notice_push',data);
}
});


userMgr.bind(userId,socket);
socket.userId = userId;
(2) 获取房间信息,并在用户进入房间时保存其座位号(包括roomid和seatIndex)。
var roomId = roomMgr.getUserRoom(userId);

(3) 为当前座位配置对应的IP地址。
var roomInfo = roomMgr.getRoom(roomId);

(4)遍历roomInfo里面所有的座位,将作为信息的数据全部打包好;
if(userId == rs.userId){
userData = seats[i];
}
(5)login_result事件发送给链接进来的客户端;
//通知前端
var ret = {
errcode:0,
errmsg:"ok",
data:{
roomid:roomInfo.id,
conf:roomInfo.conf,
numofgames:roomInfo.numOfGames,
seats:seats
}
};
socket.emit('login_result',ret);

数据被保存到全局的GameNetMgr中,用于管理和共享用户的游戏状态信息。
(6) 将该用户进入的消息广播给其他用户,使他们能够看到新用户的加入。
//通知其它客户端
userMgr.broacastInRoom('new_user_comes_push',userData,userId);

(7) 通知所有玩家有新玩家上线,如果玩家人数达到4人,则开始新一局游戏。
socket.gameMgr = roomInfo.gameMgr;
//玩家上线,强制设置为TRUE
socket.gameMgr.setReady(userId);


场景客户端
1: 麻将游戏场景挂载了MJGame.js和MahjongMgr.js两个脚本,用于控制游戏逻辑和麻将管理。
注:新框架新添加了Alert.js脚本,是3个。
2: MJGame.js中的onLoad方法添加了其他功能所需的一些组件。
onLoad: function () {
cc.vv.utils.setFitSreenMode();
this.addComponent("NoticeTip");
this.addComponent("GameOver");
this.addComponent("DingQue");
this.addComponent("PengGangs");
this.addComponent("MJRoom");
this.addComponent("TimePointer");
this.addComponent("GameResult");
this.addComponent("Chat");
this.addComponent("Folds");
this.addComponent("ReplayCtrl");
this.addComponent("PopupMgr");
this.addComponent("HuanSanZhang");
this.addComponent("ReConnect");
this.addComponent("Voice");
this.addComponent("UserInfoShow");
this.addComponent("Status");
this.initView();
this.initEventHandlers();
this.gameRoot.active = false;
this.prepareRoot.active = true;
this.initWanfaLabel();
this.onGameBeign();
cc.vv.audioMgr.playBGM("bgFight.mp3");
cc.vv.utils.addEscEvent(this.node);
},
3: initView 方法初始化场景,并隐藏不必要的元素。
initView:function(){
//搜索需要的子节点
var gameChild = this.node.getChildByName("game");
this._mjcount = gameChild.getChildByName('mjcount').getComponent(cc.Label);
this._mjcount.string = "剩余" + cc.vv.gameNetMgr.numOfMJ + "张";
this._gamecount = gameChild.getChildByName('gamecount').getComponent(cc.Label);
this._gamecount.string = "" + cc.vv.gameNetMgr.numOfGames + "/" + cc.vv.gameNetMgr.maxNumOfGames + "局";
var myselfChild = gameChild.getChildByName("myself");
var myholds = myselfChild.getChildByName("holds");
this._chupaidrag = gameChild.getChildByName('chupaidrag');
this._chupaidrag.active = false;
for(var i = 0; i < myholds.children.length; ++i){
var sprite = myholds.children[i].getComponent(cc.Sprite);
this._myMJArr.push(sprite);
sprite.spriteFrame = null;
this.initDragStuffs(sprite.node);
}
//var realwidth = this.node.width;
//myholds.scaleX *= realwidth/1280;
//myholds.scaleY *= realwidth/1280;
var sides = ["myself","right","up","left"];
for(var i = 0; i < sides.length; ++i){
var side = sides[i];
var sideChild = gameChild.getChildByName(side);
this._hupaiTips.push(sideChild.getChildByName("HuPai"));
this._hupaiLists.push(sideChild.getChildByName("hupailist"));
this._playEfxs.push(sideChild.getChildByName("play_efx").getComponent(cc.Animation));
this._chupaiSprite.push(sideChild.getChildByName("ChuPai").children[0].getComponent(cc.Sprite));
var opt = sideChild.getChildByName("opt");
opt.active = false;
var sprite = opt.getChildByName("pai").getComponent(cc.Sprite);
var data = {
node:opt,
sprite:sprite
};
this._opts.push(data);
}
var opts = gameChild.getChildByName("ops");
this._options = opts;
this.hideOptions();
this.hideChupai();
},
4: 监听并处理从网络传输过来的事件
initEventHandlers:function(){
cc.vv.gameNetMgr.dataEventHandler = this.node;
//初始化事件监听器
var self = this;
this.node.on('game_holds',function(data){
self.initMahjongs();
self.checkQueYiMen();
});
this.node.on('game_begin',function(data){
self.onGameBeign();
//第一把开局,要提示
if(cc.vv.gameNetMgr.numOfGames == 1){
self.checkIp();
}
});
this.node.on('check_ip',function(data){
self.checkIp();
});
this.node.on('game_sync',function(data){
self.onGameBeign();
self.checkIp();
});
this.node.on('game_chupai',function(data){
self.hideChupai();
self.checkQueYiMen();
if(data.last != cc.vv.gameNetMgr.seatIndex){
self.initMopai(data.last,null);
}
if(!cc.vv.replayMgr.isReplay() && data.turn != cc.vv.gameNetMgr.seatIndex){
self.initMopai(data.turn,-1);
}
});
this.node.on('game_mopai',function(data){
self.hideChupai();
var pai = data.pai;
var localIndex = cc.vv.gameNetMgr.getLocalIndex(data.seatIndex);
if(localIndex == 0){
var index = 13;
var sprite = self._myMJArr[index];
self.setSpriteFrameByMJID("M_",sprite,pai,index);
sprite.node.mjId = pai;
}
else if(cc.vv.replayMgr.isReplay()){
self.initMopai(data.seatIndex,pai);
}
});
this.node.on('game_action',function(data){
self.showAction(data);
});
this.node.on('hupai',function(data){
//如果不是玩家自己,则将玩家的牌都放倒
var seatIndex = data.seatindex;
var localIndex = cc.vv.gameNetMgr.getLocalIndex(seatIndex);
var hupai = self._hupaiTips[localIndex];
hupai.active = true;
if(localIndex == 0){
self.hideOptions();
}
var seatData = cc.vv.gameNetMgr.seats[seatIndex];
seatData.hued = true;
if(cc.vv.gameNetMgr.conf.type == "xlch"){
hupai.getChildByName("sprHu").active = true;
hupai.getChildByName("sprZimo").active = false;
self.initHupai(localIndex,data.hupai);
if(data.iszimo){
if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
seatData.holds.pop();
self.initMahjongs();
}
else{
self.initOtherMahjongs(seatData);
}
}
}
else{
hupai.getChildByName("sprHu").active = !data.iszimo;
hupai.getChildByName("sprZimo").active = data.iszimo;
if(!(data.iszimo && localIndex==0))
{
//if(cc.vv.replayMgr.isReplay() == false && localIndex != 0){
// self.initEmptySprites(seatIndex);
//}
self.initMopai(seatIndex,data.hupai);
}
}
if(cc.vv.replayMgr.isReplay() == true && cc.vv.gameNetMgr.conf.type != "xlch"){
var opt = self._opts[localIndex];
opt.node.active = true;
opt.sprite.spriteFrame = cc.vv.mahjongmgr.getSpriteFrameByMJID("M_",data.hupai);
}
if(data.iszimo){
self.playEfx(localIndex,"play_zimo");
}
else{
self.playEfx(localIndex,"play_hu");
}
cc.vv.audioMgr.playSFX("nv/hu.mp3");
});
this.node.on('mj_count',function(data){
self._mjcount.string = "剩余" + cc.vv.gameNetMgr.numOfMJ + "张";
});
this.node.on('game_num',function(data){
self._gamecount.string = "" + cc.vv.gameNetMgr.numOfGames + "/" + cc.vv.gameNetMgr.maxNumOfGames + "局";
});
this.node.on('game_over',function(data){
self.gameRoot.active = false;
self.prepareRoot.active = true;
});
this.node.on('game_chupai_notify',function(data){
self.hideChupai();
var seatData = data.seatData;
//如果是自己,则刷新手牌
if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
self.initMahjongs();
}
else{
self.initOtherMahjongs(seatData);
}
self.showChupai();
var audioUrl = cc.vv.mahjongmgr.getAudioURLByMJID(data.pai);
cc.vv.audioMgr.playSFX(audioUrl);
});
this.node.on('guo_notify',function(data){
self.hideChupai();
self.hideOptions();
var seatData = data;
//如果是自己,则刷新手牌
if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
self.initMahjongs();
}
cc.vv.audioMgr.playSFX("give.mp3");
});
this.node.on('guo_result',function(data){
self.hideOptions();
});
this.node.on('game_dingque_finish',function(data){
self.initMahjongs();
});
this.node.on('peng_notify',function(data){
self.hideChupai();
var seatData = data;
if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
self.initMahjongs();
}
else{
self.initOtherMahjongs(seatData);
}
var localIndex = self.getLocalIndex(seatData.seatindex);
self.playEfx(localIndex,"play_peng");
cc.vv.audioMgr.playSFX("nv/peng.mp3");
self.hideOptions();
});
this.node.on('gang_notify',function(data){
self.hideChupai();
var seatData = data.seatData;
var gangtype = data.gangtype;
if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
self.initMahjongs();
}
else{
self.initOtherMahjongs(seatData);
}
var localIndex = self.getLocalIndex(seatData.seatindex);
if(gangtype == "wangang"){
self.playEfx(localIndex,"play_guafeng");
cc.vv.audioMgr.playSFX("guafeng.mp3");
}
else{
self.playEfx(localIndex,"play_xiayu");
cc.vv.audioMgr.playSFX("rain.mp3");
}
});
this.node.on("hangang_notify",function(data){
var localIndex = self.getLocalIndex(data);
self.playEfx(localIndex,"play_gang");
cc.vv.audioMgr.playSFX("nv/gang.mp3");
self.hideOptions();
});
this.node.on('login_result', function () {
self.gameRoot.active = false;
self.prepareRoot.active = true;
console.log('login_result');
});
},
5: MJRoom.js:包含与客户端游戏逻辑相关的代码组件,负责处理房间内的游戏操作和交互。
cc.Class({
extends: cc.Component,
properties: {
lblRoomNo:{
default:null,
type:cc.Label
},
// foo: {
// default: null,
// url: cc.Texture2D, // optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
_seats:[],
_seats2:[],
_timeLabel:null,
_voiceMsgQueue:[],
_lastPlayingSeat:null,
_playingSeat:null,
_lastPlayTime:null,
},
// use this for initialization
onLoad: function () {
if(cc.vv == null){
return;
}
this.initView();
this.initSeats();
this.initEventHandlers();
},
initView:function(){
var prepare = this.node.getChildByName("prepare");
var seats = prepare.getChildByName("seats");
for(var i = 0; i < seats.children.length; ++i){
this._seats.push(seats.children[i].getComponent("Seat"));
}
this.refreshBtns();
this.lblRoomNo = cc.find("Canvas/infobar/Z_room_txt/New Label").getComponent(cc.Label);
this._timeLabel = cc.find("Canvas/infobar/time").getComponent(cc.Label);
this.lblRoomNo.string = cc.vv.gameNetMgr.roomId;
var gameChild = this.node.getChildByName("game");
var sides = ["myself","right","up","left"];
for(var i = 0; i < sides.length; ++i){
var sideNode = gameChild.getChildByName(sides[i]);
var seat = sideNode.getChildByName("seat");
this._seats2.push(seat.getComponent("Seat"));
}
var btnWechat = cc.find("Canvas/prepare/btnWeichat");
if(btnWechat){
cc.vv.utils.addClickEvent(btnWechat,this.node,"MJRoom","onBtnWeichatClicked");
}
var titles = cc.find("Canvas/typeTitle");
for(var i = 0; i < titles.children.length; ++i){
titles.children[i].active = false;
}
if(cc.vv.gameNetMgr.conf){
var type = cc.vv.gameNetMgr.conf.type;
if(type == null || type == ""){
type = "xzdd";
}
titles.getChildByName(type).active = true;
}
},
refreshBtns:function(){
var prepare = this.node.getChildByName("prepare");
var btnExit = prepare.getChildByName("btnExit");
var btnDispress = prepare.getChildByName("btnDissolve");
var btnWeichat = prepare.getChildByName("btnWeichat");
var isIdle = cc.vv.gameNetMgr.numOfGames == 0;
btnExit.active = !cc.vv.gameNetMgr.isOwner() && isIdle;
btnDispress.active = cc.vv.gameNetMgr.isOwner() && isIdle;
btnWeichat.active = isIdle;
},
initEventHandlers:function(){
var self = this;
this.node.on('new_user',function(data){
self.initSingleSeat(data);
});
this.node.on('user_state_changed',function(data){
self.initSingleSeat(data);
});
this.node.on('game_begin',function(data){
self.refreshBtns();
self.initSeats();
});
this.node.on('game_num',function(data){
self.refreshBtns();
});
this.node.on('game_huanpai',function(data){
for(var i in self._seats2){
self._seats2[i].refreshXuanPaiState();
}
});
this.node.on('huanpai_notify',function(data){
var idx = data.seatindex;
var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
self._seats2[localIdx].refreshXuanPaiState();
});
this.node.on('game_huanpai_over',function(data){
for(var i in self._seats2){
self._seats2[i].refreshXuanPaiState();
}
});
this.node.on('voice_msg',function(data){
self._voiceMsgQueue.push(data);
self.playVoice();
});
this.node.on('chat_push',function(data){
var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
self._seats[localIdx].chat(data.content);
self._seats2[localIdx].chat(data.content);
});
this.node.on('quick_chat_push',function(data){
var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
var index = data.content;
var info = cc.vv.chat.getQuickChatInfo(index);
self._seats[localIdx].chat(info.content);
self._seats2[localIdx].chat(info.content);
cc.vv.audioMgr.playSFX(info.sound);
});
this.node.on('emoji_push',function(data){
var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
console.log(data);
self._seats[localIdx].emoji(data.content);
self._seats2[localIdx].emoji(data.content);
});
},
initSeats:function(){
var seats = cc.vv.gameNetMgr.seats;
for(var i = 0; i < seats.length; ++i){
this.initSingleSeat(seats[i]);
}
},
initSingleSeat:function(seat){
var index = cc.vv.gameNetMgr.getLocalIndex(seat.seatindex);
var isOffline = !seat.online;
var isZhuang = seat.seatindex == cc.vv.gameNetMgr.button;
console.log("isOffline:" + isOffline);
this._seats[index].setInfo(seat.name,seat.score);
this._seats[index].setReady(seat.ready);
this._seats[index].setOffline(isOffline);
this._seats[index].setID(seat.userid);
this._seats[index].voiceMsg(false);
this._seats2[index].setInfo(seat.name,seat.score);
this._seats2[index].setZhuang(isZhuang);
this._seats2[index].setOffline(isOffline);
this._seats2[index].setID(seat.userid);
this._seats2[index].voiceMsg(false);
this._seats2[index].refreshXuanPaiState();
},
onBtnSettingsClicked:function(){
cc.vv.popupMgr.showSettings();
},
onBtnBackClicked:function(){
cc.vv.alert.show("返回大厅","返回大厅房间仍会保留,快去邀请大伙来玩吧!",function(){
cc.vv.wc.show('正在返回游戏大厅');
cc.director.loadScene("hall");
},true);
},
onBtnChatClicked:function(){
},
onBtnWeichatClicked:function(){
var title = "<血战到底>";
if(cc.vv.gameNetMgr.conf.type == "xlch"){
var title = "<血流成河>";
}
cc.vv.anysdkMgr.share("天天麻将" + title,"房号:" + cc.vv.gameNetMgr.roomId + " 玩法:" + cc.vv.gameNetMgr.getWanfa());
},
onBtnDissolveClicked:function(){
cc.vv.alert.show("解散房间","解散房间不扣房卡,是否确定解散?",function(){
cc.vv.net.send("dispress");
},true);
},
onBtnExit:function(){
cc.vv.net.send("exit");
},
playVoice:function(){
if(this._playingSeat == null && this._voiceMsgQueue.length){
console.log("playVoice2");
var data = this._voiceMsgQueue.shift();
var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
var localIndex = cc.vv.gameNetMgr.getLocalIndex(idx);
this._playingSeat = localIndex;
this._seats[localIndex].voiceMsg(true);
this._seats2[localIndex].voiceMsg(true);
var msgInfo = JSON.parse(data.content);
var msgfile = "voicemsg.amr";
console.log(msgInfo.msg.length);
cc.vv.voiceMgr.writeVoice(msgfile,msgInfo.msg);
cc.vv.voiceMgr.play(msgfile);
this._lastPlayTime = Date.now() + msgInfo.time;
}
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
var minutes = Math.floor(Date.now()/1000/60);
if(this._lastMinute != minutes){
this._lastMinute = minutes;
var date = new Date();
var h = date.getHours();
h = h < 10? "0"+h:h;
var m = date.getMinutes();
m = m < 10? "0"+m:m;
this._timeLabel.string = "" + h + ":" + m;
}
if(this._lastPlayTime != null){
if(Date.now() > this._lastPlayTime + 200){
this.onPlayerOver();
this._lastPlayTime = null;
}
}
else{
this.playVoice();
}
},
onPlayerOver:function(){
cc.vv.audioMgr.resumeAll();
console.log("onPlayCallback:" + this._playingSeat);
var localIndex = this._playingSeat;
this._playingSeat = null;
this._seats[localIndex].voiceMsg(false);
this._seats2[localIndex].voiceMsg(false);
},
onDestroy:function(){
cc.vv.voiceMgr.stop();
// cc.vv.voiceMgr.onPlayCallback = null;
}
});

(1) 根据服务器返回的座位信息,初始化玩家的座位安排。
this.initView();
this.initSeats();
this.initEventHandlers();
(2) 监听从GameNetMgr.js传递过来的事件,以处理游戏中的实时更新和操作。
initEventHandlers:function(){
...
this.node.on('game_begin',function(data){
self.refreshBtns();
self.initSeats();
});
this.node.on('game_num',function(data){
self.refreshBtns();
});
this.node.on('game_huanpai',function(data){
for(var i in self._seats2){
self._seats2[i].refreshXuanPaiState();
}
});
...
(3) 当有新玩家进入时,响应new_user事件,更新游戏场景中的玩家信息。
initEventHandlers:function(){
var self = this;
this.node.on('new_user',function(data){
self.initSingleSeat(data);
});
定缺
1: 在麻将场景中,Canvas下的Game节点包含一个dingque子节点,用于处理定缺功能。

2: DingQue.js被挂载到this.addComponent("DingQue"),并在MJGame.js中引用以实现定缺功能。
3: onQueYiMenClicked方法在按钮点击时触发,将玩家选择的定缺类型发送给服务器。
//---------定缺按钮点击响应时间,----------
onQueYiMenClicked:function(event){
var type = 0;
if(event.target.name == "tong"){
type = 0;
}
else if(event.target.name == "tiao"){
type = 1;
}
else if(event.target.name == "wan"){
type = 2;
}
for(var i = 0; i < this.selected.length; ++i){
this.selected[i].active = false;
}
this.selected[type].active = true;
cc.vv.gameNetMgr.dingque = type;
cc.vv.net.send("dingque",type);
//this.setInteractable(false);
},
//---------定缺消息发送服务器cc.vv.net.send("dingque",type);----------

self.dispatchEvent('game_dingque'); 触发game_dingque事件,通知其他组件或模块处理定缺相关的逻辑。


4: 服务器在定缺完成后,广播game_dingque_finish_push事件,通知所有客户端定缺流程已结束。
//-------------------服务器socket_service收到定缺消息socket.on('dingque',function(data){----------------------------
socket.gameMgr.huanSanZhang(socket.userId,p1,p2,p3);
});
//定缺
socket.on('dingque',function(data){
if(socket.userId == null){
return;
}
var que = data;
socket.gameMgr.dingQue(socket.userId,que);
});
//出牌
socket.on('chupai',function(data){
if(socket.userId == null){
return;
}
var pai = data;
socket.gameMgr.chuPai(socket.userId,pai);
});
//碰
socket.on('peng',function(data){
if(socket.userId == null){
return;
}
socket.gameMgr.peng(socket.userId);
});
//杠
socket.on('gang',function(data){
//--------------------socket.gameMgr.dingQue(socket.userId,que)进行处理---------------------------

5: 如果四名玩家都完成了定缺,服务器会发送game_playing_push消息,通知客户端开始游戏。


客户端在接收到服务器的game_dingque_finish_push后,会广播self.dispatchEvent('game_dingque_finish', data);,随后DingQue.js会通过this.node.on('game_dingque_finish', function())来处理这个事件。
同时,客户端还会接收到服务器通过userMgr.broadcastInRoom('game_playing_push', null, seatData.userId, true);发送的game_playing_push消息。GameNetMgr.js监听到这个消息后,就会触发游戏的开始流程。

游戏操作:
1: 确定当前轮到的玩家,通过服务器消息或本地逻辑更新当前玩家的状态。
2: 广播game_chupai_push事件,通知所有客户端当前玩家出牌的信息。
3: 对出牌后的操作进行检查,判断接下来的玩家是否可以进行胡牌、杠牌等操作,并根据结果触发相应的事件或操作逻辑。
4: 玩家出牌流程: 
在MJGame.js的onMJClicked方法中,当玩家点击麻将牌时,调用cc.vv.net.send('chupai', mjId);将玩家选择的麻将牌mjId发送到服务器,表示玩家出牌。
服务器收到消息处理:
(1) 检测玩家是否能出牌及出牌的合法性:
exports.chuPai = function(userId,pai){
pai = Number.parseInt(pai);
var seatData = gameSeatsOfUsers[userId];
if(seatData == null){
console.log("can't find user game data.");
return;
}
var game = seatData.game;
var seatIndex = seatData.seatIndex;
//如果不该他出,则忽略
if(game.turn != seatData.seatIndex){
console.log("not your turn.");
return;
}
if(seatData.hued){
console.log('you have already hued. no kidding plz.');
return;
}
if(seatData.canChuPai == false){
console.log('no need chupai.');
return;
}
if(hasOperations(seatData)){
console.log('plz guo before you chupai.');
return;
}
//从此人牌中扣除
var index = seatData.holds.indexOf(pai);
if(index == -1){
console.log("holds:" + seatData.holds);
console.log("can't find mj." + pai);
return;
}
seatData.canChuPai = false;
game.chupaiCnt ++;
seatData.guoHuFan = -1;
seatData.holds.splice(index,1);
seatData.countMap[pai] --;
game.chuPai = pai;
recordGameAction(game,seatData.seatIndex,ACTION_CHUPAI,pai);
checkCanTingPai(game,seatData);
userMgr.broacastInRoom('game_chupai_notify_push',{userId:seatData.userId,pai:pai},seatData.userId,true);
//如果出的牌可以胡,则算过胡
if(seatData.tingMap[game.chuPai]){
seatData.guoHuFan = seatData.tingMap[game.chuPai].fan;
}
//检查是否有人要胡,要碰 要杠
var hasActions = false;
for(var i = 0; i < game.gameSeats.length; ++i){
//玩家自己不检查
if(game.turn == i){
continue;
}
var ddd = game.gameSeats[i];
//已经和牌的不再检查
if(ddd.hued){
continue;
}
checkCanHu(game,ddd,pai);
if(seatData.lastFangGangSeat == -1){
if(ddd.canHu && ddd.guoHuFan >= 0 && ddd.tingMap[pai].fan <= ddd.guoHuFan){
console.log("ddd.guoHuFan:" + ddd.guoHuFan);
ddd.canHu = false;
userMgr.sendMsg(ddd.userId,'guohu_push');
}
}
checkCanPeng(game,ddd,pai);
checkCanDianGang(game,ddd,pai);
if(hasOperations(ddd)){
sendOperations(game,ddd,game.chuPai);
hasActions = true;
}
}
//如果没有人有操作,则向下一家发牌,并通知他出牌
if(!hasActions){
setTimeout(function(){
userMgr.broacastInRoom('guo_notify_push',{userId:seatData.userId,pai:game.chuPai},seatData.userId,true);
seatData.folds.push(game.chuPai);
game.chuPai = -1;
moveToNextUser(game);
doUserMoPai(game);
},500);
}
};
(2) 广播玩家出牌的通知:服务器通过game_chupai_push事件,将出牌玩家的ID和牌的ID广播给所有客户端。


GameNetMgr.js发送game_chupai_notify消息到MJGame.js进行处理,更新游戏界面显示出牌动作。
(3) 检测是否有玩家可以胡、碰、杠,并触发相应的操作逻辑。
(4) 轮到下一个玩家继续操作,更新当前玩家并通知客户端。
moveToNextUser(game);
doUserMoPai(game);

5.开始新的一轮:清除上一轮的临时状态,更新游戏状态,并通知所有玩家新一轮开始
//开始新的一局
exports.begin = function(roomId) {
var roomInfo = roomMgr.getRoom(roomId);
if(roomInfo == null){
return;
}
var seats = roomInfo.seats;
var game = {
conf:roomInfo.conf,
roomInfo:roomInfo,
gameIndex:roomInfo.numOfGames,
button:roomInfo.nextButton,
mahjongs:new Array(108),
currentIndex:0,
gameSeats:new Array(4),
numOfQue:0,
turn:0,
chuPai:-1,
state:"idle",
firstHupai:-1,
yipaoduoxiang:-1,
fangpaoshumu:-1,
actionList:[],
hupaiList:[],
chupaiCnt:0,
};
roomInfo.numOfGames++;
for(var i = 0; i < 4; ++i){
var data = game.gameSeats[i] = {};
data.game = game;
data.seatIndex = i;
data.userId = seats[i].userId;
//持有的牌
data.holds = [];
//打出的牌
data.folds = [];
//暗杠的牌
data.angangs = [];
//点杠的牌
data.diangangs = [];
//弯杠的牌
data.wangangs = [];
//碰了的牌
data.pengs = [];
//缺一门
data.que = -1;
//换三张的牌
data.huanpais = null;
//玩家手上的牌的数目,用于快速判定碰杠
data.countMap = {};
//玩家听牌,用于快速判定胡了的番数
data.tingMap = {};
data.pattern = "";
//是否可以杠
data.canGang = false;
//用于记录玩家可以杠的牌
data.gangPai = [];
//是否可以碰
data.canPeng = false;
//是否可以胡
data.canHu = false;
//是否可以出牌
data.canChuPai = false;
//如果guoHuFan >=0 表示处于过胡状态,
//如果过胡状态,那么只能胡大于过胡番数的牌
data.guoHuFan = -1;
//是否胡了
data.hued = false;
//是否是自摸
data.iszimo = false;
data.isGangHu = false;
//
data.actions = [];
data.fan = 0;
data.score = 0;
data.lastFangGangSeat = -1;
//统计信息
data.numZiMo = 0;
data.numJiePao = 0;
data.numDianPao = 0;
data.numAnGang = 0;
data.numMingGang = 0;
data.numChaJiao = 0;
gameSeatsOfUsers[data.userId] = data;
}
games[roomId] = game;
//洗牌
shuffle(game);
//发牌
deal(game);
var numOfMJ = game.mahjongs.length - game.currentIndex;
var huansanzhang = roomInfo.conf.hsz;
for(var i = 0; i < seats.length; ++i){
//开局时,通知前端必要的数据
var s = seats[i];
//通知玩家手牌
userMgr.sendMsg(s.userId,'game_holds_push',game.gameSeats[i].holds);
//通知还剩多少张牌
userMgr.sendMsg(s.userId,'mj_count_push',numOfMJ);
//通知还剩多少局
userMgr.sendMsg(s.userId,'game_num_push',roomInfo.numOfGames);
//通知游戏开始
userMgr.sendMsg(s.userId,'game_begin_push',game.button);
if(huansanzhang == true){
game.state = "huanpai";
//通知准备换牌
userMgr.sendMsg(s.userId,'game_huanpai_push');
}
else{
game.state = "dingque";
//通知准备定缺
userMgr.sendMsg(s.userId,'game_dingque_push');
}
}
};
本节结束,如有侵权,联系删除
766

被折叠的 条评论
为什么被折叠?



