崇仁打盾

打独流程

sequenceDiagram 服务器->>玩家一:提示打独|hinttAlone 服务器->>玩家二:提示打独|hinttAlone 服务器->>玩家三:提示打独|hinttAlone 服务器->>玩家四:提示打独|hinttAlone 玩家三-->>服务器:打独|askAlone 服务器->>玩家三:提示等待|waitAlone 玩家二-->>服务器:不打独|askAlone 服务器->>玩家二:提示等待|waitAlone 玩家一-->>服务器:不打独|askAlone 服务器->>玩家一:提示等待|waitAlone 玩家四-->>服务器:打独|askAlone 服务器->>玩家一:广播玩家三打独|useAlone 服务器->>玩家二:广播玩家三打独|useAlone 服务器->>玩家三:广播玩家三打独|useAlone 服务器->>玩家四:广播玩家三打独|useAlone

出牌流程

出牌流程

目录结构

  1. 代码目录: game_9800

  2. 子目录:

    1. client:测试客户端
    2. config:参数配置
    3. event:protobuf自动产生,建议不要手工编辑
    4. flow:实现游戏逻辑
      1. enginer.go: 洗牌算法
      2. game_flow.go:实现流程图的GameFlow
      3. game_room.go:实现流程图的GameRoom
      4. game_record.go:实现流程图的GameRecord
      5. game_round.go:实现流程图的GameRound
      6. status.go:定义打盾专有给客户端错误代码
      7. util_test.go:是单元测试文件
    5. proto:protobuf协议定义,用于打盾和客户app通讯,实现打盾专有的协议
    6. robot:测试机器人
    7. web:测试环境web界面,展示房间列表,手工加机器人等
  3. 子文件:

    1. .gitignore:git代码管理忽略文件
    2. build:供jenkins自动调用,编译脚本
    3. game_9800_dev.conf:是开发环境配置文件
    4. game_9800_test.conf:是测试环境配置文件
    5. game_9800_opt.conf:是正式环境配置文件
    6. main.go:程序入口文件
    7. run:正式环境自动调用,运行脚本
    8. README.md:git代码管理说明文件
  4. git代码管理:dev分支

错误码

从9800到9899

错误码 描述
9801 success
9802 数据反序化失败
9803 地区玩法错误
9804 未知事件
9805 没有该用户
9806 用户未登录
9807 用户验证失败
9808 用户重复登录
9809 已加入其他游戏
9810 已登陆其他服务器
9812 加入房间失败
9813 房间不存在
9814 房间已经满人
9815 用户已在房间中
9816 该用户没有在任何房间
9820 重复申请解散
9821 房间不在解散中
9822 获取距离失败
9823 退出房间失败
9824 房间不在准备中
9827 重复解散投票
9828 用户不在当前房间
9829 房间已开始游戏
9830 坐下失败,游戏币不足
9831 没有空房间
9832 重复准备
9833 用户已在其他设备登陆
9834 用户信息读取失败
9835 准备失败,游戏币不足
9836 创建VIP房间失败,游戏币不足
9837 VIP房间, 加入失败
9838 加入VIP房间失败, 密码错误
9839 非VIP房间
9840 无权打独
9841 房间不在选择打独中
9845 房间不在游戏中
9846 无权不出
9847 不出token错误
9848 必须出牌
9849 出牌token错误
9850 出牌不符合规则
9851 无权出牌
9852 你没有处于托管中
9853 数据库操作失败
9854 取消托管的次数已经用光
9855 你处于托管中

事件定义

从9800到9899 事件流向:

  • c->s 客户端发送到服务器端
  • s->c 服务器端发送到客户端
  • s->m 服务器端广播到客户端
事件编号 事件名称 事件流向 是否返回 描述
9801 登录 c->s true
9802 服务器返回 s->c 服务器返回,包括成功和异常
9803 心跳包 c->s true 服务器回空包
9804 房间列表 s->m 广播给大厅内但不在房间的玩家
9805 房间变化 s->m 广播给大厅内但不在房间的玩家
9806 加入房间 c->s true 快速开始-不用填房间号
9807 房间信息 s->c 加入房间推送或在房间内掉线回来推送
9808 准备 c->s 玩家准备及取消准备
9809 玩家变化 s->m 加入,离开,上线,掉线,准备,取消准备都广播房间内玩家
9810 游戏信息 s->c 开始游戏推送或游戏内掉线回来推送
9811 申请解散 c->s 如果房间未开始,当作退出房间处理
9812 提示玩家投票 s->m 广播给房间内玩家
9813 玩家投票 c->s 玩家是否同意解散
9814 广播解散结果 s->m 广播给房间内玩家
9815 请求LBS c->s
9816 回复LBS s->c
9817 请求退出房间 c->s true 服务器返回
9818 顶号 s->c 同个用户只能一个连接,新的顶替旧的
9819 请求历史积分 c->s
9820 回复历史积分 s->c
9821 近距离提示 m
9822 聊天 m 客户端和服务器广播同一个命令字/PB结构,服务器不做任何处理
9823 游戏信息 s->c 刷新游戏信息
9831 系统发牌 s->m 广播给房间内玩家,每个人信息不同
9832 提示打独 s->m 让玩家选择是否打独
9833 玩家是否打独 c->s 玩家是否打独,出错回包,成功不回
9834 广播是否打独 s->m 通知打独
9835 广播亮牌 s->m 通知找朋友牌
9836 提示出牌 s->m 通知玩家出牌
9837 玩家出牌 c->s 玩家出牌,出错时回包,成功不回
9838 广播出牌 s->m 广播给房间内玩家,玩家出牌
9839 广播清桌 s->m 广播给房间内玩家,通知清桌
9840 摊牌 s->m 广播给房间内玩家
9841 小局结算 s->m 广播给房间内玩家
9842 回放游戏信息
9855 回放
9856 广播用户进入托管模式 s->m
9857 取消托管模式 c->s true
9858 广播托管模式 s->m
9859 创建VIP房间 c->s true
9860 加入VIP房间 c->s true

协议PB结构

玩家结构

syntax = "proto3";
package event;
//玩家信息
message EventPlayerInfo {
 //用户ID
 int32   userId       = 1;
 //在线状态  true -- 在线   false -- 离线
 bool    onlineStatus = 2;
 //准备状态  true --准备 false --未准备
 bool    readyStatus  = 3;
    //游戏币数(精确到分)
 int64 coins = 4;
 //座位ID 0,1,2,3
 int32   seatId       = 5;
 //昵称
 string  nickName     = 6;
 //头像url
 string  headUrl      = 7;
 //用户IP
 string  ip          = 8;
 // 性别 0-没有设置,1--男 2--女
 int32   sex          = 9;
 //总积分
 int32   totalJiFen   = 10;
 //登录地址
 string  loginAddress = 11;
}

创建VIP房间

message EventCreateVIPRoom {

}

加入VIP房间

message EventJoinVIPRoom {
 int32 roomId = 1;
 string pwd = 2;
}

广播托管模式

// 取消托管成功
message EventCancelEntrustDone { // IResponse 9858
 // 取消托管用户座位号
 int32 seat = 1;
}

取消托管模式

// 取消托管
message EventCancelEntrust { // IRequest 9857
 
}

广播用户进入托管模式

// 广播用户进入托管
message EventInEntrust { // IRequest 9856
 // 进入托管用户的座位号
 int32 seat = 1;
 // 还有多少次可以取消托管
 int32 count = 2;
}

登录

syntax = "proto3";
package event;
message EventLogin {
    //用户登录标识
    string token     = 1;
    //客户端校验 md5(token + secret_key) 服务端和客户端约定secret_key
    string secretString    = 2;
    // 登录地址
    string loginAddress    = 3;
    // 登录经度
    string loginLng        = 4;
    // 登录纬度
    string loginLat        = 5;
}

服务器返回

syntax = "proto3";
package event;
message EventReturn{
    //事件ID
    int32     eventId         = 1;
    //返回码-错误编码
    int32     code            = 2;
    //描述
    string    message         = 3;
}

心跳包

syntax = "proto3";
package event;
//心跳包
message EventHeartBeat {
}

房间列表

syntax = "proto3";
package event;
import "room_info.proto";
//房间列表
message EventRoomList {
    // 房间列表
    repeated EventRoomInfo items = 1[packed = false];
 // 开始房间数
    int32 StartedRoomNum = 2;
}

房间变化

syntax = "proto3";
package event;
import "room_info.proto";
// 大厅内有房间内变化,广播所有大厅里不在房间内玩家
message EventUseRoomChange {
    //变化原因 (0-创建,1-更新资料,2-开始游戏,3-解散)
    int32  reason = 1;
    //变化后房间信息
    EventRoomInfo roomInfo = 2;
 // 开始房间数
    int32 StartedRoomNum = 3;
}

加入房间

快速开始-不用填房间号

syntax = "proto3";
package event;
message EventJoinRoom {
  //房间ID,不填房间号,就是系统选房间即快速开始
 int32   roomId         = 1;
}

房间信息

syntax = "proto3";
package event;
import "player_info.proto";
//房间信息
message EventRoomInfo {
 // 房间id
 int32 roomId = 1;
 // 房间状态,0-没开始,1-游戏中,2-小局结束,3-所有已结束,4-房间解散中,5-选择打独中
 int32 status = 2;
 //当前局数
 int32   curGameNum  = 3;
 // 总局数
 int32   totalGameNum    = 4;
 // 底分
 int32   baseScore    = 5;
 // 玩法描述
 string  playDes         = 6;
 // 玩家信息(以座位号为顺序)
 repeated EventPlayerInfo playerInfo = 7[packed = false];
 // 最低局数
    int32 minGameNum = 8;
 // 密码
 string pwd = 9;
}

准备

syntax = "proto3";
package event;
message EventAskReady {
 //true-准备 false--取消准备
 bool  isReady  = 1;
}

玩家变化

syntax = "proto3";
package event;
import "player_info.proto";
// 房间内有玩家变化,广播给房间内玩家
message EventUsePlayerChange {
    //变化原因, 0-加入 1-离开,2-准备 3-取消准备,4-上线 5-离线
    int32  reason = 1;
    //变化玩家座位号
    int32  seatId = 2;
    //变化玩家id
    int32  userId = 3;
    // 变化后,房间内所有玩家信息(以座位号为顺序)
    repeated EventPlayerInfo playerInfo = 4[packed = false];
}

刷新游戏信息

syntax = "proto3";

package event;

// 客户端刷新房间信息,不推送给其他人,
message EventRefreshRoom { // IResponse 9823
}

游戏信息

syntax = "proto3";
package event;

import "use_out.proto";
import "assign_pai.proto";

message PBBombType {
 repeated int32 bts = 1[packed = false];
}

message PBGameInfo { // IResponse 9810
 // 是否打独
 bool isAlone = 1;
 // 找朋友亮牌
 int32 friendPai = 2;
 // 朋友关系是否出现
 bool friendShow = 3;
 // 按座位号记录每个玩家对应朋友关系,朋友关系出现才有效
 repeated int32 friendShip = 4[packed = false];
 // 对家用户id,朋友关系出现才有效
 int32 friendUserId = 5;
 // 叫牌者用户id
 int32 bankerUserId = 6;
 // 叫牌者座位id
 int32 bankerSeatId = 7;
 // 当前桌牌(按出牌顺序)
 repeated PBUseOut desktopPai = 8[packed = false];
 // 当前桌牌分
 int32 desktopScore = 9;
 // 当前手牌
 repeated int32 handPai = 10[packed = false];
 // 每个玩家抓分(座位号为下标)
 repeated int32 catchScore = 11[packed = false];
 // 每个玩家炸弹输赢分(座位号为下标)
 repeated int32 bombScore = 12[packed = false];
 // 能否出牌
 bool canOutPai = 13;
 // 是否必须出牌
 bool mustOutPai = 14;
 // 出牌token
 string outPaiToken = 15;
 // 当前出牌座位号
 int32 curOutPaiSeat = 16;
 // 是否选择打独中
 bool isChoiceAlone = 17;
 // 当前选择打独座位号
    int32 choiceSeatId    =18;
 // 每个玩家出完顺序(座位号为下标),0-没出完,1-头游,2-二游,3-三游,4-四游
 repeated int32 overOrder = 19[packed = false];
 // 每个玩家剩余张数(座位号为下标),小于等于5张才有效
 repeated int32 restNum = 20[packed = false];
 // 每个玩家炸弹赔率(座位号为下标)
 repeated int32 bombOdds = 21[packed = false];
 // 选择打独中时,还剩余多少秒考虑时间
 int32 remainAloneTime = 22;
 // 出牌时的剩余时间
 int32 remainOutTime = 23;
 // 是否处于托管中
 repeated bool isEntrust = 24[packed = false];
 // 还有几次取消托管的机会
 int32 cancelEntrustCount = 25;
 // 炸弹类型,按座位号
 repeated PBBombType bombTypes = 26[packed = false];
 // 打独时所有玩家的手牌
 repeated PBAssignPai allPai = 27[packed = false];
}

申请解散

syntax = "proto3";
package event;
// 申请解散
message EventAskDismassRoom {
}

提示玩家投票

syntax = "proto3";
package event;
//玩家的投票情况
message EventPlayerVote {
    int32 userId = 1;
    string nickName = 2;
    //投票结果 0--未投票 1--同意 2--拒绝
    int32 vote = 3;
}

// 提示玩家投票及广播解散意见,玩家如果没有投票,则提示投票,否则仅显示
message EventQueryDismassRoom {
 //申请的玩家ID
 int32 applyUserId = 1;
 //申请的玩家昵称
 string applyNickName = 2;
 //各个玩家投票情况
    repeated EventPlayerVote playerVote = 3[packed = false];
    //解散还剩的秒数 如:89
    int32 remainderTime = 5;
}

玩家投票

syntax = "proto3";
package event;
// 玩家上传是否同意解散
message EventReportDismassRoom {
 //true--同意  false--拒绝
 bool    isAgree    = 1;
}

广播解散结果

syntax = "proto3";
package event;
//解散房间的结果
message EventNoticeDismassRoom {
    //true -- 解散 false --不解散
    bool isAgree = 1;
    //解散原因, 0-玩家发起解散, 1-房间超过最低局数,有玩家退出, 2-房间达到最高局数
 // 3-有玩家游戏币为负数, 4-长时间没开始,自动解散
    int32 reason = 2;
    //解散文字说明
    string doc = 3;
 // 房间ID
    int32 roomId = 4;
}

请求LBS

syntax = "proto3";
package event;
//玩家间距离请求
message EventAskGeo {
}

回复LBS

syntax = "proto3";
package event;
message EventUseGeoItem {
    //起始用户ID
    int32 srcUid = 1;
    //结束用户ID
    int32 destUid = 2;
    //用户之间距离
    string distance = 3;
    //用户之间距离,数值表示,单位(米)
    int32 gap = 4;
}

//玩家间距离回包
message EventUseGeo {
    // 用户ID列表
    repeated int32 uids = 1[packed = false];
    // 结果列表
    repeated EventUseGeoItem Items = 2[packed = false];
}

请求退出房间

syntax = "proto3";
package event;
//用户请求退出房间
message EventAskExit {
}

顶号

syntax = "proto3";
package event;
//同个用户新登陆踢出旧登陆
message EventKickOut {
 //用户ID
 int32   userId          = 1;
}

请求历史积分

syntax = "proto3";
package event;
// 请求历史积分
message EventAskHistoryScore {
}

回复历史积分

syntax = "proto3";
package event;
message EventHistoryScoreItem {
 // 用户ID
 int32 userId = 1;
 // 下标-局数,值-积分
 repeated int32 scores = 2[packed = false];
}
// 历史积分回包
message EventUseHistoryScore {
 repeated EventHistoryScoreItem items = 1[packed = false];
}

近距离提示

syntax = "proto3";
package event;
//玩家间距离太近提示
message EventDistanceAlert {
    //提示内容
    string alertStr = 1;
}

聊天

syntax = "proto3";
package event;
// 客户端和服务器广播同一个命令字,同一个PB结构
// 服务器纯广播,不做任何处理
message EventChat {
 // 聊天类型
 int32 Type = 1;
 // 聊天内容
 string Content = 2;
 // 聊天扩展
 string Extend = 3;
    // 聊天发起人id
    int32 authorId = 4;
}

系统发牌

syntax = "proto3";
package event;
message PBAssignPai {
 // 系统开始发的手牌
 repeated int32 pai  = 1[packed = false];
}

提示打独

syntax = "proto3";
package event;
message PBHintAlone {
 // 是否能选择打独
 bool canAlone =1;
 // 当前叫牌者座位号
 int32 seatId =2;
 // 当前叫牌者用户id
 int32 userId =3;
 // 剩余多少秒的考虑时间
 int32 countdown = 4;
}

玩家是否打独

syntax = "proto3";
package event;
message PBAskAlone {
 // 是否打独
 bool isAlone =1;
}

广播是否打独

syntax = "proto3";
package event;
import "assign_pai.proto";

message PBUseAlone { // IResponse 9834
    // 是否打独
 bool isAlone = 1;
 // 当前叫牌者座位号
 int32 seatId =2;
 // 当前叫牌者用户id
 int32 userId =3;
 // 所有玩家的手牌
 repeated PBAssignPai allPai = 4[packed = false];
}

广播亮牌

syntax = "proto3";
package event;
message PBUseFriend {
 // 庄家座位号
 int32 seatId =1;
 // 庄家用户id
 int32 userId =2;
 // 找朋友的牌
 int32 friendPai =3;
}

提示出牌

syntax = "proto3";
package event;
message PBHintOut {
 // 出牌token
 string token =1;
 // 当前出牌者座位号
 int32 seatId =2;
 // 是否必须出牌
 bool isForce = 3;
 // 倒计时
 int32 countdown = 4;
}

玩家出牌

syntax = "proto3";
package event;
message PBAskOut{
 // 出牌token,处理重复出牌
 string token=1;
 // 此次出的牌
 repeated int32 pai  = 2[packed = false];
 // 是否不出
 bool isSkip = 3;
}

广播出牌

扑克牌型掩码文档

syntax = "proto3";
package event;
message PBUseOut{
 // 出牌玩家座位号
 int32 seatId =1;
 // 出牌玩家用户id
 int32 userId =2;
 // 出的牌
 repeated int32 pai  = 3[packed = false];
 // 是否包括找朋友牌,出牌者不是叫牌者
 bool containFriendPai =4;
 // 包括此次出牌的桌面分
 int32 desktopScore =5;
 // 牌分类,1-单张,2-对子,3-3张,4-3带1,5-3带2, 6-顺子,7-连对,8-飞机
      // 9-510K,10-4炸,11-5炸,12-6炸,13-7炸,14-8炸,15-纯色双王
     // 16-5炸(四红/四黑),17-6炸(四红/四黑),18-3个510K,19-4个510K
     // 20-三王,21-四王,22-5个510K,23-6个510K,24-7个510K,25-8个510K
     // 26--四炸(四红/四黑),27-(大小王)
 int32 paiClass = 6;
 // 牌掩码,例如单张3,3张3带2,见文档 http://wiki.dw7758.com/#!/game/poker/mask.md
 int32 paiMask = 7;
 // 出完位置,0-没出完,1-头游,2-二游,3-三游,4-四游
 int32 overOrder = 8;
 // 按座位号记录每个玩家此次炸弹或纯色双王的输赢分,赢分为正数,输分为负数
 repeated int32 bombScore = 9[packed = false];
 // 第几轮
 int32 roundIndex = 10;
 // 同一轮第几次
 int32 roundSerial = 11;
 // 按座位号记录每个玩家对应朋友关系,必须出现找朋友牌才有效
 repeated int32 friendShip = 12[packed = false];
 // 是否不出
 bool isSkip =13;
 // 炸弹或纯色双王的赔率
 int32 bombOdds =14;
 // 剩余张数,小于等于5张才有效
 int32 restNum =15;
  // 按座位号记录每个玩家到现在为止打出炸弹/纯色双王赔率(包括此次)
 repeated int32 sumBombOdds = 16[packed = false];
}

广播清桌

syntax = "proto3";
package event;
message PBClearDesktop{
 // 大牌的玩家座位号
 int32 seatId =1;
 // 大牌的玩家用户id
 int32 userId =2;
 // 此次大牌玩家赚的桌面分
 int32 desktopScore =3;
 // 每个玩家抓桌面分(座位号为下标)
 repeated int32 catchScore = 4[packed = false];
 // 第几轮
 int32 roundIndex = 5;
}

摊牌

syntax = "proto3";
package event;
//用户的手牌
message PBTanPaiItem {
    //玩家ID
    int32 userId                        = 1;
    //玩家ID
    int32 seatId                        = 2;
    //手牌
    repeated int32 normalPai            = 3[packed = false];
}
//摊牌
message PBTanPai {
    repeated PBTanPaiItem items = 1[packed = false];
}

小局结算

syntax = "proto3";
package event;
message PBSmallMeta {
    //玩家ID
    int32 userId        = 1;
    //玩家昵称
    string nickName     = 2;
    //当前总游戏币(精确到分)
    int64  totalCoins    = 4;
    //此局输赢分(牌局结束输赢+炸弹),赢分为正数,输分为负数
    int32  winScore    = 5;
    //此局炸弹输赢分,赢分为正数,输分为负数
    int32 bombScore = 6;
    //出完顺序,0-没出完,1-头游,2-二游,3,三游,4-四游
    int32 overOrder = 7;
    //是否双围
    bool isDouble = 8;
    //是否打独
    bool isAlone = 9;
 //炸弹赔率
    int32 bombOdds = 10;
 //是否庄家(此局第一个出牌人)
    bool isBanker = 11;
 //队友座位号(找朋友模式才有效)
 int32 friendSeatId = 12;
    //0-没有原炸,1-小原炸,2-大原炸
    int32 fryState = 13;
    //捡分
    int32 catchScore = 14;
    //牌局结束输赢分,赢分为正数,输分为负数
    int32 cardScore = 15;
 //是否霸奖
    bool isBully = 16;
}

message PBSmallSettlement {
    //房间号
    int32   id          = 1;
    //房间状态 0-未开始 1-小局结束 2-游戏中 3-已结束
    int32   status          = 2;
    //当前局数
    int32   now  = 3;
    //下局局数, 如果房间状态为:3, 这里为:
    int32   next     = 4;
    //总局数
    int32   total    = 5;
    //玩法名称
    string  name        = 6;
    //玩法描述
    string  doc         = 7;
    //时间
    string time = 8;
    //各玩家的情况,座位号为下标
    repeated PBSmallMeta players = 9[packed = false];
 //是否打独
 bool isAlone = 10;
 //为true表示数据(是否打独,是否双围,是否庄家,队友座位号,出完顺序)有效,否则无效
 bool dataValid = 11;
    //房间底分
    int32   baseScore     = 12;
 //结束原因, 0-玩家发起解散, 1-房间超过最低局数,有玩家退出, 2-房间达到最高局数
    // 3-有玩家游戏币为负数
    int32 overReason = 13;
    //结束文字说明
    string overDoc = 14;
}

回放游戏信息

syntax = "proto3";
package event;

// 回放游戏信息
message PBReplayGame { // IResponse 9842
 // 是否打独
 bool isAlone = 1;
 // 找朋友亮牌
 int32 friendPai = 2;
 // 叫牌者用户id
 int32 bankerUserId = 3;
 // 叫牌者座位id
 int32 bankerSeatId = 4;
 // 系统发的手牌,按座位号为下标
 repeated PBAssignPai handPai = 5[packed = false];
}

回放

syntax = "proto3";
package event;

message PBReplayCell {
 //对应的事件ID
    int32 eventId           = 1;
 //事件对应序列化后的proto内容
    bytes eventData     = 2;

}
//回放,客户端要求
message PBReplay { // IResponse 9855
    repeated PBReplayCell cells = 1[packed = false];
}