1.当用户登录到socket之后,会获取房间里的其他用户信息,返回给当前登录用户作为login_result,同时会在房间内广播new_user_comes_push消息,告知其他用户自己的相关信息,同时设置用户ready为true,socket.gameMgr.setReady(userId);
最后检查是否又解散消息,有的话推给用户
在游戏管理器setReady的地方不断检查是否有足够的用户可以开始一局游戏,如果有,则调用begin方法
//开始新的一局
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:[],
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.actions = [];
//是否是自摸
data.iszimo = false;
data.isGangHu = false;
data.fan = 0;
data.score = 0;
data.huInfo = [];
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');
}
}
};
在这个方法里新建了一个游戏对象,然后把游戏对象存入全局游戏数组中,最后洗牌,发牌
系统发完牌后,如果房间里有换三张的规则,显示换牌界面,如果选定了要换的牌,则向服务器发送换牌信息
cc.vv.net.send("huanpai",data);
服务器收到换牌信息后
exports.huanSanZhang = function(userId,p1,p2,p3){
var seatData = gameSeatsOfUsers[userId];
if(seatData == null){
console.log("can't find user game data.");
return;
}
var game = seatData.game;
if(game.state != "huanpai"){
console.log("can't recv huansanzhang when game.state == " + game.state);
return;
}
if(seatData.huanpais != null){
console.log("player has done this action.");
return;
}
if(seatData.countMap[p1] == null || seatData.countMap[p1] == 0){
return;
}
seatData.countMap[p1]--;
if(seatData.countMap[p2] == null || seatData.countMap[p2] == 0){
seatData.countMap[p1]++;
return;
}
seatData.countMap[p2]--;
if(seatData.countMap[p3] == null || seatData.countMap[p3] == 0){
seatData.countMap[p1]++;
seatData.countMap[p2]++;
return;
}
seatData.countMap[p1]++;
seatData.countMap[p2]++;
seatData.huanpais = [p1,p2,p3];
for(var i = 0; i < seatData.huanpais.length; ++i){
var p = seatData.huanpais[i];
var idx = seatData.holds.indexOf(p);
seatData.holds.splice(idx,1);
seatData.countMap[p] --;
}
userMgr.sendMsg(seatData.userId,'game_holds_push',seatData.holds);
for(var i = 0; i < game.gameSeats.length; ++i){
var sd = game.gameSeats[i];
if(sd == seatData){
var rd = {
si:seatData.userId,
huanpais:seatData.huanpais
};
userMgr.sendMsg(sd.userId,'huanpai_notify',rd);
}
else{
var rd = {
si:seatData.userId,
huanpais:[]
};
userMgr.sendMsg(sd.userId,'huanpai_notify',rd);
}
}
//如果还有未换牌的玩家,则继承等待
for(var i = 0; i < game.gameSeats.length; ++i){
if(game.gameSeats[i].huanpais == null){
return;
}
}
//换牌函数
var fn = function(s1,huanjin){
for(var i = 0; i < huanjin.length; ++i){
var p = huanjin[i];
s1.holds.push(p);
if(s1.countMap[p] == null){
s1.countMap[p] = 0;
}
s1.countMap[p] ++;
}
}
//开始换牌
var f = Math.random();
var s = game.gameSeats;
var huanpaiMethod = 0;
//对家换牌
if(f < 0.33){
fn(s[0],s[2].huanpais);
fn(s[1],s[3].huanpais);
fn(s[2],s[0].huanpais);
fn(s[3],s[1].huanpais);
huanpaiMethod = 0;
}
//换下家的牌
else if(f < 0.66){
fn(s[0],s[1].huanpais);
fn(s[1],s[2].huanpais);
fn(s[2],s[3].huanpais);
fn(s[3],s[0].huanpais);
huanpaiMethod = 1;
}
//换上家的牌
else{
fn(s[0],s[3].huanpais);
fn(s[1],s[0].huanpais);
fn(s[2],s[1].huanpais);
fn(s[3],s[2].huanpais);
huanpaiMethod = 2;
}
var rd = {
method:huanpaiMethod,
}
game.huanpaiMethod = huanpaiMethod;
game.state = "dingque";
for(var i = 0; i < s.length; ++i){
var userId = s[i].userId;
userMgr.sendMsg(userId,'game_huanpai_over_push',rd);
userMgr.sendMsg(userId,'game_holds_push',s[i].holds);
//通知准备定缺
userMgr.sendMsg(userId,'game_dingque_push');
}
};
如果四个玩家都换完拍了,在服务端给玩家把牌交换,然后进入定缺阶段,同时通知玩家手牌变化和状态变化
玩家定缺之后,给服务器发送一个消息
exports.dingQue = function(userId,type){
var seatData = gameSeatsOfUsers[userId];
if(seatData == null){
console.log("can't find user game data.");
return;
}
var game = seatData.game;
if(game.state != "dingque"){
console.log("can't recv dingQue when game.state == " + game.state);
return;
}
if(seatData.que < 0){
game.numOfQue++;
}
seatData.que = type;
//检查玩家可以做的动作
//如果4个人都定缺了,通知庄家出牌
if(game.numOfQue == 4){
construct_game_base_info(game);
var arr = [1,1,1,1];
for(var i = 0; i < game.gameSeats.length; ++i){
arr[i] = game.gameSeats[i].que;
}
userMgr.broacastInRoom('game_dingque_finish_push',arr,seatData.userId,true);
userMgr.broacastInRoom('game_playing_push',null,seatData.userId,true);
//进行听牌检查
for(var i = 0; i < game.gameSeats.length; ++i){
var duoyu = -1;
var gs = game.gameSeats[i];
if(gs.holds.length == 14){
duoyu = gs.holds.pop();
gs.countMap[duoyu] -= 1;
}
checkCanTingPai(game,gs);
if(duoyu >= 0){
gs.holds.push(duoyu);
gs.countMap[duoyu] ++;
}
}
var turnSeat = game.gameSeats[game.turn];
game.state = "playing";
//通知玩家出牌方
turnSeat.canChuPai = true;
userMgr.broacastInRoom('game_chupai_push',turnSeat.userId,turnSeat.userId,true);
//检查是否可以暗杠或者胡
//直杠
checkCanAnGang(game,turnSeat);
//检查胡 用最后一张来检查
checkCanHu(game,turnSeat,turnSeat.holds[turnSeat.holds.length - 1]);
//通知前端
sendOperations(game,turnSeat,game.chuPai);
}
else{
userMgr.broacastInRoom('game_dingque_notify_push',seatData.userId,seatData.userId,true);
}
};
如果某一个玩家定缺了,给客户端推送定缺的提示,直到所有的都定缺,则通知庄家开始出牌
定缺完会有一个game_dingque_finish的通知,用来在玩家右上角显示缺哪门牌
最后给玩家推送game_chupai_push,客户端收到通知后
cc.vv.net.addHandler("game_chupai_push",function(data){
console.log('game_chupai_push');
//console.log(data);
var turnUserID = data;
var si = self.getSeatIndexByID(turnUserID);
self.doTurnChange(si);
});
doTurnChange:function(si){
var data = {
last:this.turn,
turn:si,
}
this.turn = si;
this.dispatchEvent('game_chupai',data);
},
轮到玩家出牌的时候,玩家点击两次麻将出牌,服务端进行出牌操作
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.canChuPai == false){
console.log('no need chupai.');
return;
}
if(hasOperations(seatData)){
console.log('plz guo before you chupai.');
return;
}
//如果是胡了的人,则只能打最后一张牌
if(seatData.hued){
if(seatData.holds[seatData.holds.length - 1] != pai){
console.log('only deal last one when hued.');
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){
checkCanPeng(game,ddd,pai);
checkCanDianGang(game,ddd,pai);
}
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');
}
}
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);
}
};
最后没操作的话给下一个玩家发牌,doUserMoPai就是给玩家发牌
function doUserMoPai(game){
game.chuPai = -1;
var turnSeat = game.gameSeats[game.turn];
turnSeat.lastFangGangSeat = -1;
turnSeat.guoHuFan = -1;
var pai = mopai(game,game.turn);
//牌摸完了,结束
if(pai == -1){
doGameOver(game,turnSeat.userId);
return;
}
else{
var numOfMJ = game.mahjongs.length - game.currentIndex;
userMgr.broacastInRoom('mj_count_push',numOfMJ,turnSeat.userId,true);
}
recordGameAction(game,game.turn,ACTION_MOPAI,pai);
//通知前端新摸的牌
userMgr.sendMsg(turnSeat.userId,'game_mopai_push',pai);
//检查是否可以暗杠或者胡
//检查胡,直杠,弯杠
if(!turnSeat.hued){
checkCanAnGang(game,turnSeat);
}
//如果未胡牌,或者摸起来的牌可以杠,才检查弯杠
if(!turnSeat.hued || turnSeat.holds[turnSeat.holds.length-1] == pai){
checkCanWanGang(game,turnSeat,pai);
}
//检查看是否可以和
checkCanHu(game,turnSeat,pai);
//广播通知玩家出牌方
turnSeat.canChuPai = true;
userMgr.broacastInRoom('game_chupai_push',turnSeat.userId,turnSeat.userId,true);
//通知玩家做对应操作
sendOperations(game,turnSeat,game.chuPai);
}
//通知前端新摸的牌
userMgr.sendMsg(turnSeat.userId,'game_mopai_push',pai);
下面是客户端显示摸到牌的代码
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);
}
});