28 KiB
Export接口详细说明
文档目标:详细说明框架调用子游戏的8个必需接口,包括接口功能、参数、返回值、调用时机和实现要点。
📚 目录
1. Export接口概述
1.1 什么是Export接口
Export接口是框架调用子游戏的标准接口集合,定义在export.js文件中。框架通过这些接口来:
- 查询房间创建成本
- 初始化游戏(开战)
- 处理断线重连
- 处理玩家进出
- 处理房间解散
┌─────────────────┐
│ 友乐框架 │
│ (youle_app) │
└────────┬────────┘
│ 调用export接口
↓
┌─────────────────┐
│ export.js │ ← 子游戏实现的8个必需接口
│ (子游戏接口) │
└─────────────────┘
1.2 8个必需接口清单
| 序号 | 接口名称 | 调用时机 | 主要作用 |
|---|---|---|---|
| 1 | get_needroomcard |
创建房间时 | 计算所需房卡数 |
| 2 | get_asetcount |
创建房间时 | 获取房间总局数 |
| 3 | get_needroomcard_joinroom |
玩家加入房间时 | 计算加入所需房卡数 |
| 4 | makewar |
游戏开始时 | 初始化游戏状态 ⭐ |
| 5 | get_deskinfo |
断线重连/中途加入时 | 返回当前游戏状态 |
| 6 | get_disbandRoom |
解散房间时 | 清理游戏资源 |
| 7 | player_enter |
玩家进入房间时 | 处理玩家加入 |
| 8 | player_leave |
玩家离开房间时 | 处理玩家离开 |
1.3 实现方式
// export.js文件结构
var cls_jinxianmahjong_export = {
new: function() {
var exp = {};
// 实现8个必需接口
exp.get_needroomcard = function(roomtype, o_game_config) { };
exp.get_asetcount = function(roomtype, o_game_config) { };
exp.get_needroomcard_joinroom = function(roomtype, o_game_config) { };
exp.makewar = function(o_room, o_game_config) { };
exp.get_deskinfo = function(o_room, seat) { };
exp.get_disbandRoom = function(o_room) { };
exp.player_enter = function(o_room, seat) { };
exp.player_leave = function(o_room, seat) { };
return exp;
}
};
// 挂载到模块
mod_jinxianmahjong.export = cls_jinxianmahjong_export.new();
2. 8个必需接口详解
2.1 get_needroomcard - 获取创建房间所需房卡
功能说明
计算创建房间需要消耗的房卡数量,由框架在玩家创建房间时调用。
接口定义
exp.get_needroomcard = function(roomtype, o_game_config) {
// 返回所需房卡数
return number;
}
参数说明
roomtype - 房间类型配置
- 类型:
string或Array - 格式1: 字符串
"1234567890"(10位) - 格式2: 数组
["1","2","3","4","5","6","7","8","9","0"] - 说明: 每位代表不同的游戏配置
进贤麻将roomtype结构:
// 索引 含义 取值
// [0] 局数配置 "1"=8局, "2"=16局, "3"=24局
// [1] 精牌玩法 "1"=带精, "2"=不带精
// [2] 胡牌类型 "1"=平胡, "2"=清一色等
// [3] 庄家规则 "1"=轮流坐庄, "2"=固定庄家
// [4] 上下翻 "1"=启用, "2"=禁用
// [5] 埋地雷 "1"=启用, "2"=禁用
// [6] 同一首歌 "1"=启用, "2"=禁用
// [7] AA制扣卡 "1"=房主扣卡, "2"=AA制
// [8] 允许中途加入 "1"=允许, "2"=不允许
// [9] 保留位 暂时未用
// 示例
"1312111000" // 8局、带精、清一色、轮流坐庄、启用上下翻、启用埋地雷、启用同一首歌
o_game_config - 游戏配置对象(可选)
- 类型:
Object - 包含: 解析后的游戏配置信息
返回值
类型: Number
规则(进贤麻将):
- 8局房间:1张房卡
- 16局房间:2张房卡
- 24局房间:3张房卡
调用时机
用户点击"创建房间"
↓
客户端发送创建房间请求
↓
框架调用 get_needroomcard() ← 这里
↓
检查房主房卡数量是否足够
↓
扣除房卡,创建房间
实现示例(进贤麻将)
exp.get_needroomcard = function(roomtype, o_game_config) {
console.log("[export.get_needroomcard] 计算房卡需求,roomtype:", roomtype);
// 调用RoomAdapter统一处理
if (!RoomAdapter) {
throw new Error("RoomAdapter未加载");
}
return RoomAdapter.calculateRoomCardCost(roomtype, o_game_config);
};
// RoomAdapter中的实现
RoomAdapter.calculateRoomCardCost = function(roomtype, config) {
// 解析roomtype
var rtArray = Array.isArray(roomtype) ? roomtype : roomtype.split('');
var roundsConfig = rtArray[0]; // 局数配置
// 根据局数返回房卡数
switch(roundsConfig) {
case "1": return 1; // 8局
case "2": return 2; // 16局
case "3": return 3; // 24局
default: return 1;
}
};
2.2 get_asetcount - 获取房间局数
功能说明
返回房间的总局数设置,框架用此值设置o_room.asetcount属性。
接口定义
exp.get_asetcount = function(roomtype, o_game_config) {
// 返回房间总局数
return number;
}
参数说明
参数与get_needroomcard相同。
返回值
类型: Number
规则(进贤麻将):
- roomtype[0] = "1" → 返回 8
- roomtype[0] = "2" → 返回 16
- roomtype[0] = "3" → 返回 24
调用时机
与get_needroomcard在同一时刻调用,用于设置房间的局数上限。
实现示例
exp.get_asetcount = function(roomtype, o_game_config) {
console.log("[export.get_asetcount] 获取房间局数,roomtype:", roomtype);
if (!RoomAdapter) {
throw new Error("RoomAdapter未加载");
}
return RoomAdapter.getRoomTotalRounds(roomtype, o_game_config);
};
// RoomAdapter中的实现
RoomAdapter.getRoomTotalRounds = function(roomtype, config) {
var rtArray = Array.isArray(roomtype) ? roomtype : roomtype.split('');
var roundsConfig = rtArray[0];
switch(roundsConfig) {
case "1": return 8; // 8局
case "2": return 16; // 16局
case "3": return 24; // 24局
default: return 8;
}
};
2.3 get_needroomcard_joinroom - 获取加入房间所需房卡
功能说明
计算玩家加入已存在房间时需要消耗的房卡数量。大多数房卡游戏返回0(加入不扣卡)。
接口定义
exp.get_needroomcard_joinroom = function(roomtype, o_game_config) {
// 返回加入所需房卡数
return number;
}
参数说明
参数与get_needroomcard相同。
返回值
类型: Number
常见值:
0- 加入房间不消耗房卡(房主付费模式)> 0- AA制,每人都需要房卡
进贤麻将规则:
- 根据roomtype[7]判断:
- "1" = 房主付费模式,返回 0
- "2" = AA制,返回与
get_needroomcard相同的值
实现示例
exp.get_needroomcard_joinroom = function(roomtype, o_game_config) {
console.log("[export.get_needroomcard_joinroom] 计算加入房间房卡需求");
if (!RoomAdapter) {
throw new Error("RoomAdapter未加载");
}
return RoomAdapter.calculateJoinRoomCardCost(roomtype, o_game_config);
};
// RoomAdapter中的实现
RoomAdapter.calculateJoinRoomCardCost = function(roomtype, config) {
var rtArray = Array.isArray(roomtype) ? roomtype : roomtype.split('');
var payMode = rtArray[7]; // AA制配置
if (payMode === "2") {
// AA制:每人都需要房卡
return this.calculateRoomCardCost(roomtype, config);
}
// 房主付费模式
return 0;
};
2.4 makewar - 开战函数 ⭐⭐⭐⭐⭐
功能说明
最重要的接口,当房间满员或房主手动开战时调用。负责:
- 创建游戏桌对象(o_desk)
- 初始化游戏状态(gameState)
- 发牌、选精等游戏初始化
- 返回开战数据包给所有玩家
接口定义
exp.makewar = function(o_room, o_game_config) {
// 返回开战结果和初始游戏状态
return Object;
}
参数说明
o_room - 平台房间对象
{
roomcode: 100001, // 房间号码
roomtype: "1312111000", // 房间类型配置
asetcount: 8, // 总局数
currentRound: 1, // 当前局数(初始为1)
status: 1, // 房间状态
seatlist: [ // 玩家座位列表
{
seat: 0,
playerid: "player001",
nickname: "玩家1",
avatar: "avatar1.jpg",
status: 1 // 玩家状态
},
// ... 其他座位
],
method: { // 房间方法(发包接口)
sendpack_toall: function(msg) {}, // 广播给所有人
sendpack_toseat: function(msg, seat) {}, // 发给指定座位
sendpack_toother: function(msg, seat) {} // 发给其他人
}
}
o_game_config - 游戏配置对象(可选)
返回值
类型: Object
标准返回结构:
{
success: true, // 操作是否成功
message: "游戏开始", // 结果消息
gameState: { // 初始游戏状态
phase: "dealing", // 游戏阶段
currentRound: 1, // 当前局数
dealer: 0, // 庄家座位号
playersState: [ // 玩家状态数组
{
seat: 0,
playerid: "player001",
handCards: [...], // 手牌(对该玩家可见)
openMelds: [], // 明牌(吃碰杠)
discardedCards: [],// 出牌区
score: 0, // 分数
status: "playing" // 状态
}
// ...
],
gameData: { // 游戏数据
deck: [...], // 剩余牌堆
jingCard: "5m", // 精牌
jingStatus: { }, // 精牌状态
currentPlayer: 0 // 当前操作玩家
}
}
}
调用时机
房间满员或房主点击"开始游戏"
↓
框架调用 makewar(o_room, config) ← 这里
↓
子游戏创建o_desk对象
↓
初始化gameState
↓
发牌、选精
↓
返回开战数据包
↓
框架广播给所有玩家
核心要点
1. 创建游戏桌对象
// 必须创建o_desk对象
var o_desk = {
o_room: o_room, // 指向房间对象
gameState: gameState, // 游戏状态
gameController: controller, // 游戏控制器
debug: { // 调试接口
save_receivepack: function() {},
save_sendpack: function() {}
}
};
// 双向关联
o_room.o_desk = o_desk;
o_desk.o_room = o_room;
2. 初始化gameState
// 使用GameStateManager创建游戏状态
var gameState = GameStateManager.createGameState({
roomcode: o_room.roomcode,
roomtype: o_room.roomtype,
players: o_room.seatlist
});
3. 游戏初始化流程
1. 洗牌、发牌
2. 丢骰子选精
3. 确定庄家
4. 给庄家14张牌(其他玩家13张)
5. 初始化玩家状态
实现示例(进贤麻将)
exp.makewar = function(o_room, o_game_config) {
try {
console.log("[export.makewar] 开始游戏,房间ID:", o_room.roomcode);
// 使用RoomAdapter处理开战逻辑
if (RoomAdapter) {
return RoomAdapter.handleMakeWar(o_room, o_game_config);
} else {
throw new Error("RoomAdapter未加载");
}
} catch (error) {
console.error("[export.makewar] 开战失败:", error);
return {
success: false,
message: '游戏初始化失败: ' + error.message
};
}
};
// RoomAdapter中的实现(简化版)
RoomAdapter.handleMakeWar = function(o_room, o_game_config) {
// 1. 解析房间配置
var roomConfig = RuleConfigParser.parse(o_room.roomtype);
// 2. 创建游戏状态
var gameState = GameStateManager.createGameState({
roomcode: o_room.roomcode,
roomtype: o_room.roomtype,
players: o_room.seatlist,
rulesConfig: roomConfig
});
// 3. 创建游戏桌对象
var o_desk = {
o_room: o_room,
gameState: gameState,
gameService: new MahjongGameService(gameState),
debug: createDebugInterface()
};
// 4. 双向关联
o_room.o_desk = o_desk;
o_desk.o_room = o_room;
// 5. 初始化游戏(发牌、选精)
o_desk.gameService.initializeRound();
// 6. 返回开战数据
return {
success: true,
message: "游戏开始",
gameState: gameState
};
};
2.5 get_deskinfo - 获取牌桌信息(断线重连)
功能说明
玩家断线重连或中途加入房间时,返回当前的完整游戏状态,让玩家能够继续游戏。
接口定义
exp.get_deskinfo = function(o_room, seat) {
// 返回玩家的游戏状态
return Object;
}
参数说明
o_room - 房间对象(同makewar)
seat - 玩家座位号
- 类型:
Number - 范围: 0-3(4人麻将)
- 说明: 重连玩家的座位号
返回值
类型: Object
返回结构(与makewar类似,但要考虑玩家视角):
{
success: true,
roomcode: 100001,
seat: 0, // 该玩家的座位
phase: "playing", // 当前游戏阶段
currentRound: 3, // 当前局数
totalRounds: 8, // 总局数
dealer: 1, // 当前庄家
currentPlayer: 2, // 当前操作玩家
myState: { // 我的状态
handCards: [...], // 我的手牌
openMelds: [...], // 我的明牌
discardedCards: [...], // 我的出牌区
score: 150, // 我的分数
availableOperations: [...] // 可执行的操作
},
othersState: [ // 其他玩家状态
{
seat: 1,
handCardCount: 13, // 手牌数量(不显示具体牌)
openMelds: [...], // 明牌
discardedCards: [...], // 出牌区
score: -50
}
// ...
],
gameData: { // 公共游戏数据
remainingCards: 52, // 剩余牌数
jingCard: "5m", // 精牌
lastDiscard: "3p" // 最后打出的牌
}
}
调用时机
玩家断线
↓
玩家重新连接
↓
客户端发送重连请求
↓
框架调用 get_deskinfo(o_room, seat) ← 这里
↓
返回当前游戏状态
↓
客户端重建界面
重要注意事项
1. 信息可见性
- 只返回该玩家应该看到的信息
- 不能泄露其他玩家的手牌
- 公共信息(明牌、出牌区)全部可见
2. 状态完整性
- 必须包含所有重建界面所需的信息
- 当前游戏进度、玩家状态、可执行操作等
3. 性能考虑
- 断线重连是常见操作,需要快速响应
- 数据结构要紧凑,避免冗余信息
实现示例
exp.get_deskinfo = function(o_room, seat) {
try {
console.log("[export.get_deskinfo] 获取桌面信息,座位:", seat);
if (!o_room.o_desk) {
return {
success: false,
message: "房间尚未开始游戏"
};
}
// 使用RoomAdapter获取桌面信息
if (RoomAdapter) {
return RoomAdapter.getDeskInfo(o_room, seat);
} else {
throw new Error("RoomAdapter未加载");
}
} catch (error) {
console.error("[export.get_deskinfo] 获取桌面信息失败:", error);
return {
success: false,
message: '获取牌桌信息失败: ' + error.message
};
}
};
// RoomAdapter中的实现
RoomAdapter.getDeskInfo = function(o_room, seat) {
var gameState = o_room.o_desk.gameState;
var playerState = gameState.playersState[seat];
// 构建玩家视角的游戏状态
return {
success: true,
roomcode: o_room.roomcode,
seat: seat,
phase: gameState.phase,
currentRound: gameState.currentRound,
totalRounds: gameState.rulesConfig.gameRules.maxRounds,
dealer: gameState.dealer,
currentPlayer: gameState.currentPlayer,
// 我的状态(完整信息)
myState: {
handCards: playerState.handCards,
openMelds: playerState.openMelds,
discardedCards: playerState.discardedCards,
score: playerState.score,
availableOperations: this.getAvailableOperations(gameState, seat)
},
// 其他玩家状态(部分信息)
othersState: this.getOthersState(gameState, seat),
// 公共游戏数据
gameData: {
remainingCards: gameState.gameData.deck.length,
jingCard: gameState.gameData.jingCard,
lastDiscard: gameState.lastDiscard
}
};
};
2.6 get_disbandRoom - 解散房间处理
功能说明
房间解散时的清理工作,释放资源,保存最后的状态。
接口定义
exp.get_disbandRoom = function(o_room) {
// 返回解散结果
return Object;
}
参数说明
o_room - 房间对象
返回值
{
success: true,
message: "房间已解散",
finalScores: [ // 最终分数
{ seat: 0, playerid: "p1", score: 100 },
{ seat: 1, playerid: "p2", score: -50 },
// ...
],
statistics: { // 统计信息
totalRounds: 5, // 完成局数
timestamp: 1234567890
}
}
调用时机
玩家申请解散房间
↓
投票通过
↓
框架调用 get_disbandRoom(o_room) ← 这里
↓
子游戏清理资源
↓
返回最终结果
↓
框架广播解散消息
实现示例
exp.get_disbandRoom = function(o_room) {
try {
console.log("[export.get_disbandRoom] 解散房间,房间ID:", o_room.roomcode);
// 使用RoomAdapter处理房间解散
if (RoomAdapter) {
return RoomAdapter.handleDisbandRoom(o_room);
} else {
throw new Error("RoomAdapter未加载");
}
} catch (error) {
console.error("[export.get_disbandRoom] 解散房间失败:", error);
return {
success: false,
message: '解散房间失败: ' + error.message
};
}
};
2.7 player_enter - 玩家进入房间
功能说明
玩家加入房间时的处理,更新玩家列表,通知其他玩家。
接口定义
exp.player_enter = function(o_room, seat) {
// 返回进入结果
return Object;
}
参数说明
o_room - 房间对象
seat - 玩家座位号
返回值
{
success: true,
seat: 0,
playerInfo: {
playerid: "player001",
nickname: "玩家1",
avatar: "avatar1.jpg"
}
}
实现示例
exp.player_enter = function(o_room, seat) {
try {
console.log("[export.player_enter] 玩家进入房间,座位:", seat);
if (RoomAdapter) {
return RoomAdapter.handlePlayerEnter(o_room, seat);
} else {
throw new Error("RoomAdapter未加载");
}
} catch (error) {
console.error("[export.player_enter] 玩家进入失败:", error);
return {
success: false,
message: error.message
};
}
};
2.8 player_leave - 玩家离开房间
功能说明
玩家离开房间时的处理,更新房间状态,通知其他玩家。
接口定义
exp.player_leave = function(o_room, seat) {
// 返回离开结果
return Object;
}
参数说明
o_room - 房间对象
seat - 玩家座位号
返回值
{
success: true,
seat: 0,
message: "玩家已离开"
}
实现示例
exp.player_leave = function(o_room, seat) {
try {
console.log("[export.player_leave] 玩家离开房间,座位:", seat);
if (RoomAdapter) {
return RoomAdapter.handlePlayerLeave(o_room, seat);
} else {
throw new Error("RoomAdapter未加载");
}
} catch (error) {
console.error("[export.player_leave] 玩家离开失败:", error);
return {
success: false,
message: error.message
};
}
};
3. RoomAdapter适配器
3.1 RoomAdapter的作用
RoomAdapter是进贤麻将中用于统一处理Export接口逻辑的适配器类,负责:
- 接口实现集中管理:所有Export接口的实际逻辑
- 配置解析:解析roomtype配置
- 状态管理:管理游戏状态的创建和更新
- 资源管理:管理游戏资源的分配和释放
3.2 架构关系
export.js (接口定义)
↓ 调用
RoomAdapter (实现逻辑)
↓ 调用
GameStateManager (状态管理)
GameController (游戏控制)
MahjongGameService (游戏服务)
3.3 为什么使用RoomAdapter
优点:
- 解耦:export.js只负责接口定义,不包含具体逻辑
- 复用:多个接口可以共享相同的逻辑代码
- 测试:可以独立测试RoomAdapter的逻辑
- 维护:修改逻辑只需要修改RoomAdapter
示例:
// export.js - 简洁的接口定义
exp.makewar = function(o_room, o_game_config) {
return RoomAdapter.handleMakeWar(o_room, o_game_config);
};
// RoomAdapter.js - 复杂的实现逻辑
RoomAdapter.handleMakeWar = function(o_room, o_game_config) {
// 1. 解析配置
var config = RuleConfigParser.parse(o_room.roomtype);
// 2. 创建状态
var gameState = GameStateManager.createGameState({...});
// 3. 创建服务
var gameService = new MahjongGameService(gameState);
// 4. 初始化游戏
gameService.initializeRound();
// 5. 返回结果
return { success: true, gameState: gameState };
};
4. 配置解析机制
4.1 roomtype配置解析
进贤麻将使用10位配置字符串:
// roomtype示例: "1312111000"
// 位置0: "1" = 8局
// 位置1: "3" = 带精(某种变体)
// 位置2: "1" = 平胡
// 位置3: "2" = 固定庄家
// 位置4: "1" = 启用上下翻
// 位置5: "1" = 启用埋地雷
// 位置6: "1" = 启用同一首歌
// 位置7: "0" = 房主付费
// 位置8-9: 保留
4.2 RuleConfigParser
// 使用RuleConfigParser解析配置
var config = RuleConfigParser.parse(roomtype);
// 返回结构
{
gameRules: {
maxRounds: 8, // 总局数
useJing: true, // 是否使用精牌
dealerRule: "rotate" // 庄家规则
},
startHuRules: {
mainRule: "8子起" // 起胡规则
},
specialRules: {
shangxiafan: true, // 上下翻
maileilei: true, // 埋地雷
tongyishouage: true // 同一首歌
},
paymentRules: {
mode: "owner" // 付费模式
}
}
5. 实现模板和示例
5.1 完整的export.js模板
var cls_jinxianmahjong_export = {
new: function() {
var exp = {};
// 1. 获取创建房间所需房卡数
exp.get_needroomcard = function(roomtype, o_game_config) {
return RoomAdapter.calculateRoomCardCost(roomtype, o_game_config);
};
// 2. 获取房间局数
exp.get_asetcount = function(roomtype, o_game_config) {
return RoomAdapter.getRoomTotalRounds(roomtype, o_game_config);
};
// 3. 获取加入房间所需房卡数
exp.get_needroomcard_joinroom = function(roomtype, o_game_config) {
return RoomAdapter.calculateJoinRoomCardCost(roomtype, o_game_config);
};
// 4. 开战函数
exp.makewar = function(o_room, o_game_config) {
return RoomAdapter.handleMakeWar(o_room, o_game_config);
};
// 5. 获取牌桌信息(断线重连)
exp.get_deskinfo = function(o_room, seat) {
return RoomAdapter.getDeskInfo(o_room, seat);
};
// 6. 解散房间处理
exp.get_disbandRoom = function(o_room) {
return RoomAdapter.handleDisbandRoom(o_room);
};
// 7. 玩家进入房间
exp.player_enter = function(o_room, seat) {
return RoomAdapter.handlePlayerEnter(o_room, seat);
};
// 8. 玩家离开房间
exp.player_leave = function(o_room, seat) {
return RoomAdapter.handlePlayerLeave(o_room, seat);
};
return exp;
}
};
// 挂载到模块
mod_jinxianmahjong.export = cls_jinxianmahjong_export.new();
6. 常见问题
Q1: 为什么get_needroomcard和get_asetcount都需要roomtype参数?
A: 因为房卡数量和局数都由roomtype决定。例如:
- 8局房间:1张房卡
- 16局房间:2张房卡
- 24局房间:3张房卡
Q2: makewar什么时候调用?
A: 两种情况:
- 房间满员时自动开战
- 房主手动点击"开始游戏"
Q3: o_room和o_desk的关系?
A:
o_room:平台管理的房间对象(框架提供)o_desk:子游戏的桌对象(子游戏创建)- 通过
o_room.o_desk和o_desk.o_room双向关联
Q4: get_deskinfo需要返回什么?
A: 需要返回足够重建游戏界面的所有信息:
- 当前游戏进度
- 玩家手牌(仅该玩家)
- 所有玩家的明牌和出牌区
- 当前可执行的操作
Q5: 断线重连和中途加入有什么区别?
A:
- 断线重连:玩家之前在游戏中,重新连接
- 中途加入:观战者或新玩家加入正在进行的游戏
两者都调用get_deskinfo,但返回的信息可能不同。
Q6: RoomAdapter是必需的吗?
A: 不是必需的,但强烈推荐。你也可以直接在export.js中实现所有逻辑,但这会导致代码难以维护。
Q7: 如何测试Export接口?
A:
// 模拟房间对象
var mockRoom = {
roomcode: 100001,
roomtype: "1312111000",
seatlist: [/* ... */]
};
// 测试get_needroomcard
var cost = mod_jinxianmahjong.export.get_needroomcard(
mockRoom.roomtype,
{}
);
console.log("房卡需求:", cost); // 应该输出: 1
// 测试get_asetcount
var rounds = mod_jinxianmahjong.export.get_asetcount(
mockRoom.roomtype,
{}
);
console.log("房间局数:", rounds); // 应该输出: 8
7. 下一步
阅读以下文档继续学习:
- 02-Import接口说明 - 子游戏调用框架的接口
- 03-RPC处理机制 - 客户端请求的处理流程
- 04-游戏核心服务 - GameController、MahjongGameService等
相关代码文件:
server/games2/jinxianmahjong/export.jsserver/games2/jinxianmahjong/game/RoomAdapter.jsserver/games2/jinxianmahjong/rules/RuleConfigParser.js