design.md - 修正选主/埋牌顺序笔误(确认「先选主后埋牌」) - 投降改为选主阶段与选主互斥的选择(不选主/不埋牌直接结算),算奖用庄家36张、无连对链 - 70分暗牌改为「庄家摸牌之前、向所有玩家亮3秒」 - 选主阶段前端显示每花色对数、70分并列投降按钮 - 新增 §12「完整牌局游玩流程」(含大局与阶段速览) packet_protocol.md(随代码如实同步) - 结算 aset/seatlist 新结构(multiple=基础子数、upgrade 判定倍率、bangwang/climb、 chongguan/wang/naward/grade_aw);shangzhuang bottomcards 按70分门控 + ancard3s - maipai/PushCards 亮牌 liangpai;chupai info/baozhu 按查牌门控;新增 mingpai 收发包 - step 枚举去掉废弃的投降4;删除 Surrender 重连视图 docs/compliance/(新增) - 00 初步不一致清单、01 design 合规逐节核对(含整改进度与 roomtype 位串约定) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
22 KiB
二七王协议包列表
说明:data 均为发送方与接收方之间约定的 JSON 内容;成败判定只看推送包里的 data.success。
1. 发牌(fapai)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:fapai
| 参数名 | 类型 | 说明 |
|---|---|---|
| asetidx | 整数 | 当前局数 |
| asetcount | 整数 | 总局数 |
| cards | 数组 | 自己得到的牌id列表 |
| seat | 整数 | 当前等待叫分者的位置 |
| countdown | 整数 | 叫分倒计时 |
2. 叫分或不叫(jiaofen,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:jiaofen
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 叫分者的位置序号 |
| call | 整数 | 分数,0表示不叫 |
3. 叫分或不叫(jiaofen,服务器→客户端)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:jiaofen
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 叫分者的位置序号 |
| call | 整数 | 分数,0表示不叫 |
| currcall | 整数 | 当前叫到的分数 |
| multiple | 整数 | 叫分对应的基础子数(get_multiple_bycall,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0) |
| nextseat | 整数 | 下一个叫分者的位置序号 |
| countdown | 整数 | 叫分倒计时 |
4. 上庄(shangzhuang)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:shangzhuang
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 叫分者的位置序号 |
| call | 整数 | 分数,0表示不叫 |
| banker | 整数 | 庄家的位置序号 |
| grade | 整数 | 庄家的叫分 |
| multiple | 整数 | 叫分对应的基础子数(get_multiple_bycall,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0) |
| bottomcards | 数组 | 8 张暗牌。庄家恒有;闲家仅当 70 分坐庄时才有(供 3 秒亮牌用),非 70 分时闲家没有该属性(暗牌只有庄家可见,design §4) |
| ancard3s | 整数 | 仅 70 分坐庄时出现且为 1:表示庄家把暗牌摸入手前,需将 bottomcards 这 8 张暗牌向所有玩家亮出 3 秒(design §4/§7.1);非 70 分无此属性 |
| cards | 数组 | 拿了暗牌后手上的牌(36 张,含暗牌),庄家才有此属性,闲家没有该属性 |
| countdown | 整数 | 选主倒计时 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许(仅 70 分坐庄为 1)。投降与选主互斥、同为选主阶段(step2)的决策,见 touxiang 包与 design §4 |
5. 投降(touxiang,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:touxiang
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 庄家的位置序号。投降是选主阶段(step 2)与选主互斥的选择:mod.touxiang 仅在 step==2、banker==seat、call==70 时受理;点投降即直接结算,不选主、不埋牌、不出牌(design §4) |
6. 选主(xuanzhu,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:xuanzhu
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 选主者的位置序号 |
| flower | 整数 | 花色 1方块 2梅花 3红心 4黑桃 |
7. 选主(xuanzhu,服务器→客户端)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:xuanzhu
| 参数名 | 类型 | 说明 |
|---|---|---|
| banker | 整数 | 庄家的位置序号 |
| flower | 整数 | 花色 1方块 2梅花 3红心 4黑桃 |
| countdown | 整数 | 埋牌倒计时 |
| cards | 数组 | 选主后自己手上的牌id列表 |
8. 埋牌(maipai,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:maipai
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 埋牌者的位置序号 |
| cards | 数组 | 埋牌的id列表 |
9. 埋牌(maipai,服务器→客户端)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:maipai
| 参数名 | 类型 | 说明 |
|---|---|---|
| cards | 数组 | 埋牌后手上的牌,去掉了埋牌,庄家才有此属性,闲家没有该属性 |
| bottomcards | 数组 | 埋的牌,庄家才有此属性,闲家没有该属性 |
| seat | 整数 | 出牌者的位置序号(即将首出的庄家) |
| countdown | 整数 | 出牌倒计时 |
| liangpai | json | 庄家亮牌信息(design §8.2),只有闲家、且可查牌模式、且庄家埋牌后手牌达标时才有。结构(各字段按是否达标出现):zhu 主牌总数、zhupair 主对子数、zhutuo 主拖拉机组数(固定主牌王+2+7 总数 ≥10 时有这三项);wang 王数(王≥3 时);qi 7 数(7≥6 时);er 2 数(2≥6 时)。都不达标则无此属性 |
10. 出牌(chupai,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:chupai
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 出牌的id列表 |
11. 第一个玩家出牌(chupai1)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:chupai1
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 实际打出的牌id列表;甩错时(见 shuaicuo)为被强制打出的那一张最小主牌单张 |
| shuaicuo | 整数 | 甩错标志,仅甩错时出现且为 1(design §5.4.5:甩牌未通过最大性判定,整套甩牌收回,本轮只强制打出最小一张、失去本轮甩牌资格);正常出牌无此属性 |
| info | 数组 | 出牌者的座位牌况 o_paiju.seatlist[seat],仅可查牌模式下发(不查牌无此属性,design §9)。5 元素数组:前 4 个对应花色1~4 的 [无该花色标志, 该花色无对标志](0/1),第 5 个为报副情况 [剩余主牌数, 剩余主对数](初始 [-1,-1],报无主后为实际数量) |
| count | 整数 | 出牌数量(甩错时为 1) |
| flower | 整数 | 出牌花色 |
| cardtype | 整数 | 出牌牌型 |
| nextseat | 整数 | 下一个出牌者的位置序号 |
| countdown | 整数 | 下一个出牌者的出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 have_baofu()(任一玩家 seatlist[seat][4][0]==0)。仅可查牌模式下才可能为 1,供客户端展示他家主牌数量/对子结构并出现"明牌"按钮;不查牌模式恒为 0(design §9) |
| cardsinhand | 数组 | 出牌者出牌后手上剩下的牌id列表,只有出牌者才有此属性 |
12. 第二个玩家出牌(chupai2)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:chupai2
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 出的牌id列表 |
| info | 数组 | 出牌者的座位牌况 o_paiju.seatlist[seat],仅可查牌模式下发(不查牌无此属性,design §9)。5 元素数组:前 4 个对应花色1~4 的 [无该花色标志, 该花色无对标志](0/1),第 5 个为报副情况 [剩余主牌数, 剩余主对数](初始 [-1,-1],报无主后为实际数量) |
| nextseat | 整数 | 下一个出牌者的位置序号 |
| countdown | 整数 | 出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 have_baofu()(任一玩家 seatlist[seat][4][0]==0)。仅可查牌模式下才可能为 1,供客户端展示他家主牌数量/对子结构并出现"明牌"按钮;不查牌模式恒为 0(design §9) |
| cardsinhand | 数组 | 出牌后出牌者手上剩下的牌id列表,只有出牌者才有此属性 |
13. 第三个玩家出牌(chupai3)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:chupai3
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 出的牌id列表 |
| info | 数组 | 出牌者的座位牌况 o_paiju.seatlist[seat],仅可查牌模式下发(不查牌无此属性,design §9)。5 元素数组:前 4 个对应花色1~4 的 [无该花色标志, 该花色无对标志](0/1),第 5 个为报副情况 [剩余主牌数, 剩余主对数](初始 [-1,-1],报无主后为实际数量) |
| nextseat | 整数 | 下一个出牌者的位置序号 |
| countdown | 整数 | 出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 have_baofu()(任一玩家 seatlist[seat][4][0]==0)。仅可查牌模式下才可能为 1,供客户端展示他家主牌数量/对子结构并出现"明牌"按钮;不查牌模式恒为 0(design §9) |
| cardsinhand | 数组 | 出牌后出牌者手上剩下的牌id列表,只有出牌者才有此属性 |
| maxseat | 整数 | 本轮出牌谁最大,只有本轮最后一个玩家出牌后才有此属性 |
| grade | 整数 | 本轮闲家得分,只有本轮最后一个玩家出牌后且闲家有得分才有此属性 |
13.5 明牌(mingpai,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:mingpai
查看另外两家手中全部主牌的具体牌面(design §9)。服务端仅在可查牌模式、出牌阶段(step5)、且已有玩家报无主(have_baofu())时受理;否则忽略。
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 请求者的位置序号 |
13.6 明牌(mingpai,服务器→客户端)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:mingpai (只回发给请求者)
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 请求者的位置序号 |
| others | 数组 | 另外两家各自未出的全部主牌,元素 { seat: 位置序号, zhucards: [主牌id列表] } |
"再点一次取消查看"是客户端的显示开关,无需再请求服务端。
14. 结算(jiesuan)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:jiesuan
结算包由以下几个子结构拼装而成。三种结算来源的组成不同:
- 正常出牌结算(
mod.chupai中最后一张牌出完,get_paiju_account(0, ...)):含chupai+bottom+aset(末局再加account)。 - 投降结算(
mod.touxiang,get_paiju_account(1, ...)):只含aset(末局再加account),无chupai、无bottom。 - 解散结算(
export.get_disbandRoom,get_paiju_account(2, ...)):只含aset+account,无chupai、无bottom。
chupai(出牌包,仅正常出牌结算存在)
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 出牌者的位置序号,投降和解散无此属性 |
| cards | 数组 | 出的牌id列表,投降和解散无此属性 |
| maxseat | 整数 | 本轮出牌谁最大,投降和解散无此属性 |
| grade | 整数 | 本轮闲家得分,投降和解散无此属性 |
| gradecards | 数组 | 本轮闲家得分分牌列表,投降和解散无此属性。注意:当前源码中赋值语句被注释(class.paiju.js/mod.js 中相关行均被注释掉),该字段实际永远不会出现在下发的包里,属于失效字段 |
bottom(抠底包,仅正常出牌结算存在;cards 恒有,其余仅闲家抠底时有)
| 参数名 | 类型 | 说明 |
|---|---|---|
| cards | 数组 | 底牌,投降和解散无此属性 |
| multiple | 整数 | 闲家抠底倍数,闲家没抠底无此属性,投降和解散无此属性 |
| grade1 | 整数 | 底牌分数,闲家没抠底无此属性,投降和解散无此属性 |
| grade2 | 整数 | 闲家抠底得分,闲家没抠底无此属性,投降和解散无此属性 |
aset(单局结算包)
| 参数名 | 类型 | 说明 |
|---|---|---|
| banker | 整数 | 庄家的位置序号,-1:无庄 |
| call | 整数 | 庄家叫分,-1:无叫分 |
| multiple | 整数 | 基础子数 get_base_bycall(call, climb)(design §7.1/§7.3.1):常规算子 65→2、60→3、55→4、50 及以下→6、70打牌→2;投降固定 1、解散 0;无叫分 0 |
| flower | 整数 | 主牌花色,-1:无主牌花色 |
| grade | 整数 | 闲家捡分(含抠底) |
| upgrade | 整数 | 判定倍率(带符号,design §7.2.0):3大光 / 2小光 / 1过庄 / -N升N级(倒庄)/ -99投降 / 0解散或无判定 |
| bangwang | 整数 | 本局是否启用傍王规则 0否 1是(roomtype 位2) |
| climb | 整数 | 本局是否启用爬坡规则 0否 1是(roomtype 位3) |
| seatlist | 数组 | 玩家列表,元素结构见下 |
seatlist 数组元素结构:
{
"cards": [], // 算奖相关牌(王/冲关牌)列表
"chongguan": 0, // 常规算奖奖数(只有庄家才计入自己的 N)
"wang": 0, // 手牌中的王数(傍王按此计奖)
"naward": 0, // 该家总奖数 N =(庄家?常规算奖:0)+(傍王?王数:0)
"grade_aw": 0, // 算奖得分 = X×(2Ni−Nj−Nk),X 为每对子子数
"grade_jf": 0, // 捡分子数得分
"grade": 0, // 本局总分 = grade_aw + grade_jf
"score": 0 // 累计得分
}
结算数值模型(design §7~§8):每「庄–闲」对子的基础金额
X = multiple × |upgrade|(投降 X=1、解散 X=0)。捡分子数:庄赢时两闲家各付庄家 X、庄家收 2X;闲赢(升级)时庄家各付两闲家 X。算奖:持有 N 奖的玩家从另外两人各多收X×N,三家两两独立叠加(含闲–闲),即grade_aw = X×(2Ni−Nj−Nk)。
account(大局结算包)
仅当打到最后一局(
o_paiju.idx >= o_room.asetcount)或房间中途解散(解散结算)时,jiesuan包才会带上此分组;普通的中间局结算包没有account字段(class.paiju.jsget_paiju_account)。
| 参数名 | 类型 | 说明 |
|---|---|---|
| account | 数组 | 玩家列表,第一位为累积得分,第二位为每局得分,结构见下 |
[
[0, []],
[0, []],
[0, []]
]
15. 准备(zhunbei,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:zhunbei
| 参数名 | 类型 | 说明 |
|---|---|---|
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 位置序号 |
16. 准备(zhunbei,服务器→客户端)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:zhunbei
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 位置序号 |
断线重连(deskinfo)
由平台在玩家进入房间/断线重连时回调
youle_erqiwang.export.get_deskinfo(o_room, seat)生成并下发(服务器→客户端);下发的 app/route/rpc 由平台的进房/重连流程决定,不在本子游戏代码内固定。下列字段即该函数返回的deskinfo对象结构,按当前paiju.step只带对应阶段的分组。
| 参数名 | 类型 | 说明 |
|---|---|---|
| count | 整数 | 总局数 |
| idx | 整数 | 当前局数 |
| PlayerInfo | 数组 | 三个玩家目前的总积分 [0,0,0] |
| step | 整数 | 牌桌状态:1发完牌叫分 2选主/投降 3埋牌 5出牌 6结算(无独立投降阶段——投降在 step2 与选主互斥;埋牌后直接进入 step5 出牌) |
| MyCards | 数组 | 自己手上的牌 |
CallRun(不在叫分阶段无此属性)
| 参数名 | 类型 | 说明 |
|---|---|---|
| seat | 整数 | 当前叫分位置 |
| countdown | 整数 | 叫分倒计时 |
| nowcall | 整数 | 当前叫分 |
| multiple | 整数 | 叫分对应的基础子数(get_multiple_bycall,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0) |
| call | 数组 | 三家叫分 [null,0,65],null还未叫分,0不叫,>0叫了多少分 |
ChooseMain(step 2 选主/投降阶段,不在此阶段无此属性)
| 参数名 | 类型 | 说明 |
|---|---|---|
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 叫分对应的基础子数(get_multiple_bycall,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0) |
| countdown | 整数 | 选主倒计时 |
| bottomcards | 数组 | 8 张暗牌,只有庄家有此属性(暗牌仅庄家可见;70 分的 3 秒亮牌是上庄时的一次性事件,重连不重放) |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许(仅 70 分坐庄为 1)。投降与选主互斥、同一决策点,见 touxiang 包与 design §4 |
选主阶段前端需在每个花色按钮上显示"该花色在庄家手中的对子数"(design §4/§11)——庄家的完整手牌由
MyCards提供(庄家为 36 张),对子数由前端据此计算,服务端不额外下发。
BuryCards(step 3 埋牌阶段,不在此阶段无此属性)
| 参数名 | 类型 | 说明 |
|---|---|---|
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 叫分对应的基础子数(get_multiple_bycall,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0) |
| flower | 整数 | 主牌花色 |
| countdown | 整数 | 埋牌倒计时 |
| bottomcards | 数组 | 8 张暗牌,只有庄家有此属性(暗牌仅庄家可见;70 分的 3 秒亮牌是上庄时的一次性事件,重连不重放) |
埋牌阶段无投降(投降是 step2 与选主互斥的选择,选主后即不可再投降)。
PushCards(step 5 出牌阶段,不在此阶段无此属性)
| 参数名 | 类型 | 说明 |
|---|---|---|
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 叫分对应的基础子数(get_multiple_bycall,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0) |
| flower | 整数 | 主牌花色 |
| countdown | 整数 | 出牌倒计时 |
| bottomcards | 数组 | 埋的牌,只有庄家有此属性 |
| grade | 整数 | 当前的捡分分数 |
| gradecards | 数组 | 当前的捡分分牌,只有闲家有此属性 |
| playproc | json | 当前出牌情况 o_paiju.playproc:round 第几轮、start 本轮首出位置、currseat 当前出牌位置、startcount 首出张数、startflower 首出花色、starttype 首出牌型、maxseat 本轮最大者位置、maxcard 最大牌编码、cards 本轮三家各自出的牌(数组下标=位置序号) |
| seatlist | 数组 | 三家座位牌况,元素结构同上文 chupai 包的 info(每个玩家一个 5 元素数组:4 个花色的 [无该花色,无对] + 报副 [剩余主牌数,剩余主对数])。仅可查牌模式下有此属性(design §9) |
| liangpai | json | 庄家亮牌信息,结构同 maipai 包的 liangpai;仅可查牌模式、且请求者为闲家、且庄家达标时有(供闲家重连后仍能看到亮牌,design §8.2) |
| pushlist | 数组 | 出牌历史 [[[], [], []], [[], [], []], ...],外层下标=轮次,内层 3 个数组按位置序号存该轮各家出的牌id(已按大小排序) |
Balance(不在结算阶段无此属性)
| 参数名 | 类型 | 说明 |
|---|---|---|
| readystate | 数组 | 所有玩家的准备状态 |
| aset | json | 单局结算包,同上面结算包中的 aset,只有当自己的准备状态为0时才有此属性 |
战绩(大局列表)gameinfo1
非客户端收发包:大局结束/解散时由
class.desk.jsget_desk_account组装,经import.save_grade(o_room, o_gameinfo1, o_gameinfo2, 1)传给平台战绩服务持久化(服务器→平台)。
| 参数名 | 类型 | 说明 |
|---|---|---|
| roomcode | 整数 | 房号 |
| asetcount | 整数 | 实际局数 |
| createtime | 字符 | 开房时间 |
| makewartime | 字符 | 开战时间 |
| players | 数组 | 玩家列表,元素结构见下 |
players 数组元素结构:
{
"seat": 0, // 座位
"playerid": 100001, // 玩家ID
"name": "", // 昵称
"avatar": "", // 头像
"score": 0 // 成绩
}
战绩(大局)gameinfo2
非客户端收发包:与 gameinfo1 同批,由
import.save_grade的第 3 个参数传给平台战绩服务(服务器→平台)。
牌局列表,数组元素结构:
{
"starttime": "", // 开始时间
"endtime": "", // 结束时间
"seatlist": [6, -6, 0], // 玩家成绩
"callproc": [], // 叫分过程,详情见代码中的注释
"banker": 0, // 庄
"call": 0, // 叫分
"flower": 0, // 主牌花色
"result": 0, // 牌局结果
"cards": [] // 发牌出牌情况,详情见代码中的注释
}