Files
youlegames/codes/games/server/docs/guides/development/10-快速入门指南.md
2026-02-04 23:47:45 +08:00

22 KiB
Raw Blame History

快速入门指南

📋 文档概述

本文档为进贤麻将子游戏提供快速入门指南,帮助开发者:

  • 快速理解 - 10分钟理解项目核心结构
  • 快速定位 - 知道去哪里找需要的代码
  • 快速上手 - 掌握常见开发任务的实现方法
  • 快速解决 - 查找常见问题的解决方案

文档目标: 让新开发者能在2小时内理解项目结构并完成第一个功能修改。


🚀 10分钟快速理解

核心概念速览

1 三文件架构 (最重要!)

mod.js     → 模块入口,加载所有文件
export.js  → 框架调用游戏的14个接口
import.js  → 游戏调用框架的13个接口

记住:

  • 框架要执行游戏逻辑 → 调用 export 接口
  • 游戏要通知客户端 → 调用 import 接口

2 请求处理流程

客户端发送操作
    ↓
packet.js 路由到 mod.js
    ↓
RpcHandler 处理请求
    ↓
GameController 执行逻辑
    ↓
import 发送响应给客户端

3 核心目录结构

games2/jinxianmahjong/
├── mod.js, export.js, import.js    // 三文件架构
├── rpc/RpcHandler.js                 // 客户端请求处理(34个RPC方法)
├── game/
   ├── GameController.js             // 游戏流程控制(开始、发牌、结算)
   ├── OperationManager.js           // 操作管理(吃碰杠胡验证)
   └── MahjongGameService.js         // 胡牌检测和计分
├── shared/
   ├── algorithms/                   // 算法(精牌、胡牌检测、计分)
   ├── dataStructures/               // 数据结构(牌对象、状态管理)
   ├── config/                       // 配置解析
   └── constants/                    // 常量定义(12个模块)
└── utils/                            // 工具(Logger、ErrorHandler)

关键文件速查表

需求 去哪里找 核心文件
客户端请求如何处理? rpc/ RpcHandler.js
游戏怎么开始的? game/ GameController.js
怎么判断能否胡牌? shared/algorithms/ WinDetectionFactory.js
精牌怎么计算的? shared/algorithms/ JingAlgorithm.js
分数怎么算的? shared/algorithms/ ScoreCalculation.js
游戏状态怎么管理? shared/dataStructures/ GameStateManager.js
房间配置怎么解析? shared/config/ RoomConfigUtils.js
如何发送消息给客户端? 根目录 import.js
框架如何调用游戏? 根目录 export.js

📖 学习路径

路径1: 新手入门 (推荐顺序)

第1步: 理解架构
    ↓
阅读 00-框架基础概述.md
理解三文件架构和RPC机制
预计时间: 30分钟

第2步: 理解接口
    ↓
阅读 01-Export接口说明.md
阅读 02-Import接口说明.md
理解框架与游戏的交互
预计时间: 30分钟

第3步: 理解流程
    ↓
阅读 08-游戏流程概述.md
理解游戏从开始到结束的完整流程
预计时间: 40分钟

第4步: 动手实践
    ↓
完成一个简单的功能修改
(例如: 添加日志、修改分数)
预计时间: 20分钟

总计: 2小时入门

路径2: 算法研究 (进阶开发者)

1. 阅读 05-共享代码模块.md (核心算法详解)
2. 研究 JingAlgorithm.js (精牌系统)
3. 研究 WinDetectionFactory.js (胡牌检测)
4. 研究 ScoreCalculation.js (计分系统)

路径3: 功能开发 (实战导向)

1. 阅读 03-RPC处理机制.md (理解请求处理)
2. 阅读 04-游戏核心服务.md (理解服务层)
3. 阅读 09-代码框架总结.md (理解架构设计)
4. 开始功能开发

💡 常见开发任务

任务1: 添加新的RPC方法

场景: 需要处理客户端的新操作请求

步骤:

// 1. 在 rpc/RpcHandler.js 中添加方法
RpcHandler.prototype.player_new_operation = function(pack, room, callback) {
  var playerId = pack.playerid;
  var data = pack.data;
  
  // 验证操作
  var validation = this._validateOperation(room.gameState, playerId, data);
  if (!validation.valid) {
    return callback({ error: validation.error });
  }
  
  // 执行操作
  var result = this._performOperation(room.gameState, playerId, data);
  
  // 广播消息
  room.import.broadcast(room, {
    type: 'new_operation',
    playerId: playerId,
    data: result
  });
  
  callback({ success: true, data: result });
};

// 2. 在 mod.js 中注册RPC方法
mod_jinxianmahjong.player_new_operation = function(pack) {
  var room = this.getRoom(pack.roomcode);
  return this.rpcHandler.player_new_operation(pack, room, function(result) {
    return result;
  });
};

相关文档: 03-RPC处理机制.md


任务2: 修改计分规则

场景: 需要调整某种牌型的分数

步骤:

// 在 shared/algorithms/ScoreCalculation.js 中修改
ScoreCalculation.calculateWinScore = function(winResult, gameState) {
  var scores = {
    baseScore: 0,
    bonusScore: 0,
    jingScore: 0,
    totalScore: 0
  };
  
  // 修改牌型分数
  var pattern = winResult.bestPattern;
  switch (pattern.type) {
    case 'pinghu':
      scores.baseScore = pattern.hasJing ? 4 : 8;
      break;
    case 'qidui':
      scores.baseScore = pattern.hasJing ? 8 : 64;
      break;
    case 'custom_pattern':  // 新增牌型
      scores.baseScore = 16;
      break;
    // ...
  }
  
  // 计算精分
  scores.jingScore = JingAlgorithm.calculateJingScore(
    gameState.players[winResult.winnerId].handCards,
    gameState.jingInfo
  );
  
  // 总分
  scores.totalScore = scores.baseScore + scores.bonusScore + scores.jingScore;
  
  return scores;
};

相关文档: 05-共享代码模块.md


任务3: 添加新的游戏规则

场景: 需要支持新的房间配置选项

步骤:

// 1. 在 shared/config/RoomConfigUtils.js 中添加解析
RoomConfigUtils.parse = function(roomtype) {
  // ...现有解析逻辑
  
  // 新增规则位解析(假设使用第14位)
  var enableNewRule = roomtype.charAt(13) === '1';
  
  return {
    // ...现有配置
    specialRules: {
      enableNewRule: enableNewRule,  // 新规则
      // ...其他规则
    }
  };
};

// 2. 在游戏逻辑中使用规则
// game/OperationManager.js
OperationManager.validateOperation = function(gameState, operation) {
  // 检查新规则
  if (gameState.rules.specialRules.enableNewRule) {
    // 执行特殊验证逻辑
  }
  
  // ...其他验证
};

相关文档: 06-规则配置系统.md


任务4: 添加日志和调试信息

场景: 需要追踪代码执行流程

步骤:

var Logger = require('./utils/Logger.js');

// 在任何需要调试的地方添加日志
function someFunction(param) {
  Logger.info('函数开始执行', { param: param });
  
  try {
    // 业务逻辑
    Logger.debug('中间状态', { state: currentState });
    
    // 更多逻辑
    Logger.info('操作成功', { result: result });
    
    return result;
  } catch (error) {
    Logger.error('操作失败', {
      error: error.message,
      stack: error.stack
    });
    throw error;
  }
}

// 查看日志历史
var history = Logger.getHistory();
console.log('最近100条日志:', history.slice(-100));

// 查看日志统计
var stats = Logger.getStatistics();
console.log('错误数量:', stats.ERROR);

相关文档: 07-工具模块.md


任务5: 添加新的牌型检测

场景: 需要支持新的胡牌牌型

步骤:

// 在 shared/algorithms/WinDetectionFactory.js 中添加
WinDetectionFactory.detectWin = function(handTiles, jingInfo, gameState) {
  var patterns = [];
  
  // ...现有牌型检测
  
  // 添加新牌型检测
  var customPatternResult = this._checkCustomPattern(handTiles, jingInfo);
  if (customPatternResult.canWin) {
    patterns.push({
      type: 'custom_pattern',
      name: '自定义牌型',
      hasJing: customPatternResult.hasJing,
      baseScore: 32,
      description: '特殊组合牌型',
      combinations: customPatternResult.combinations
    });
  }
  
  return {
    canWin: patterns.length > 0,
    patterns: patterns,
    bestPattern: this._selectBestPattern(patterns)
  };
};

// 实现检测逻辑
WinDetectionFactory._checkCustomPattern = function(handTiles, jingInfo) {
  // 实现牌型检测算法
  // 返回 { canWin: boolean, hasJing: boolean, combinations: [...] }
};

相关文档: 05-共享代码模块.md


🔍 代码导航技巧

技巧1: 快速查找功能入口

// 想知道某个操作是如何处理的?
// 1. 找到客户端发送的RPC方法名,例如: "player_discard"
// 2. 在 rpc/RpcHandler.js 中搜索该方法
// 3. 阅读该方法的实现

// 示例: 查找出牌操作
// → 搜索 "player_discard"
// → 找到 RpcHandler.prototype.player_discard
// → 理解处理流程

技巧2: 追踪数据流

// 数据流追踪顺序:
// 1. 客户端发送 → packet.js
// 2. 路由到模块 → mod.js
// 3. RPC处理 → rpc/RpcHandler.js
// 4. 业务逻辑 → game/GameController.js 或其他服务
// 5. 状态更新 → shared/dataStructures/GameStateManager.js
// 6. 响应发送 → import.js → 客户端接收

技巧3: 查找算法实现

// 想知道某个算法是如何实现的?
// 1. 确定算法类型(精牌/胡牌检测/计分等)
// 2. 到 shared/algorithms/ 目录查找
// 3. 查看对应的算法文件

// 示例算法文件:
// - JingAlgorithm.js       → 精牌相关算法
// - WinDetectionFactory.js → 胡牌检测
// - ScoreCalculation.js    → 计分计算
// - PatternFactory.js      → 牌型分析

技巧4: 理解配置选项

// 想知道某个规则位的含义?
// 1. 打开 shared/config/RoomConfigUtils.js
// 2. 查看 parse() 方法
// 3. 找到对应位的解析代码

// 或者查看 shared/constants/RoomConstants.js
// 查看所有规则的定义和说明

常见问题解答 (FAQ)

Q1: 如何查看游戏当前状态?

A: 游戏状态保存在 room.gameState 对象中

// 在任何有 room 对象的地方
console.log('当前阶段:', room.gameState.phase);
console.log('当前玩家:', room.gameState.currentPlayer);
console.log('庄家:', room.gameState.dealer);
console.log('精牌信息:', room.gameState.jingInfo);

// 查看玩家手牌
var player = room.gameState.players[0];
console.log('玩家0手牌:', player.handCards);
console.log('玩家0分数:', player.score);

相关文档: 04-游戏核心服务.md


Q2: 如何判断一个操作是否合法?

A: 使用 OperationManager 进行验证

var OperationManager = require('./game/OperationManager.js');

// 验证出牌操作
var validation = OperationManager.validateDiscard(
  gameState,
  playerId,
  tileCode
);

if (!validation.valid) {
  console.error('操作不合法:', validation.error);
  return { error: validation.error };
}

// 验证吃牌操作
var validation = OperationManager.validateChow(
  gameState,
  playerId,
  tiles
);

相关文档: 04-游戏核心服务.md


Q3: 精牌系统是如何工作的?

A: 精牌系统由 JingAlgorithm.js 实现

var JingAlgorithm = require('./shared/algorithms/JingAlgorithm.js');

// 1. 确定精牌
var jingInfo = JingAlgorithm.determineJingCards(
  flippedCard,  // 翻开的牌
  diceResult    // 骰子结果
);
// 返回: { zhengJing: '5m', fuJing: '6m' }

// 2. 计算精分
var jingScore = JingAlgorithm.calculateJingScore(
  handCards,
  jingInfo
);

// 3. 检查是否比精
var biJingResult = JingAlgorithm.checkBiJing(
  winnerCards,
  loserCards,
  jingInfo
);

相关文档: 05-共享代码模块.md


Q4: 如何发送消息给客户端?

A: 使用 import 接口

// 1. 发送给单个玩家
room.import.sendToPlayer(playerId, {
  cmd: 'game_update',
  data: { /* ... */ }
});

// 2. 广播给所有玩家
room.import.broadcast(room, {
  cmd: 'player_operation',
  data: { /* ... */ }
});

// 3. 广播给除某玩家外的其他玩家
room.import.broadcastExcept(room, excludePlayerId, {
  cmd: 'player_discard',
  data: { /* ... */ }
});

相关文档: 02-Import接口说明.md


Q5: gameState 和 room 有什么区别?

A:

  • room: 框架层的房间对象,包含玩家列表、房间配置等
  • gameState: 游戏层的状态对象,包含游戏逻辑相关的数据
// room 对象(框架层)
room.roomcode    // 房间号
room.roomtype    // 房间配置
room.players     // 玩家列表(框架格式)

// gameState 对象(游戏层)
room.gameState.phase           // 游戏阶段
room.gameState.players         // 玩家状态(游戏格式)
room.gameState.jingInfo        // 精牌信息
room.gameState.currentPlayer   // 当前玩家

// gameState 保存在 room 对象中
// 通过 room.gameState 访问

Q6: 如何添加错误处理?

A: 使用 ErrorHandler 统一处理

var ErrorHandler = require('./utils/ErrorHandler.js');

try {
  // 可能出错的代码
  someRiskyOperation();
} catch (error) {
  // 记录错误
  ErrorHandler.handle(error, {
    context: 'someRiskyOperation',
    severity: 'high',
    playerId: playerId,
    roomcode: roomcode
  });
  
  // 返回错误信息
  return {
    success: false,
    error: 'OPERATION_FAILED',
    message: error.message
  };
}

// 查看错误统计
var stats = ErrorHandler.getStatistics();
console.log('错误分类统计:', stats.byCategory);

相关文档: 07-工具模块.md


Q7: 如何理解游戏阶段(phase)?

A: 游戏有7个主要阶段

// 游戏阶段常量
GAME_PHASES = {
  WAITING: 'waiting',              // 等待玩家准备
  DEALING: 'dealing',              // 发牌阶段
  JING_DETERMINING: 'jing_determining',  // 确定精牌
  PLAYING: 'playing',              // 游戏进行中
  RESPONDING: 'responding',        // 等待玩家响应
  ROUND_END: 'round_end',         // 单局结束
  GAME_END: 'game_end'            // 游戏结束
};

// 状态转换示例
// WAITING → DEALING → JING_DETERMINING → PLAYING → ROUND_END

相关文档: 08-游戏流程概述.md


Q8: 如何修改房间配置解析?

A: 修改 RoomConfigUtils.parse() 方法

// shared/config/RoomConfigUtils.js
RoomConfigUtils.parse = function(roomtype) {
  // roomtype 是13位字符串,例如: "1311111110000"
  
  // 解析各位配置
  var config = {
    roundCount: this._parseRoundCount(roomtype[0]),
    useJing: roomtype[1] === '1',
    allowChiPengGang: roomtype[2] === '1',
    // ...更多配置
  };
  
  return config;
};

相关文档: 06-规则配置系统.md


Q9: 如何追踪某个牌的来源?

A: 使用 MahjongCardsourceInfo 属性

// 每张牌都有来源信息
var card = {
  code: '3m',
  uniqueId: 'card_1234567890',
  sourceInfo: {
    sourceType: 'dealt',      // dealt/drawn/discarded等
    playerId: 'player1',
    timestamp: 1234567890,
    round: 1
  }
};

// 检查牌的来源
if (card.sourceInfo.sourceType === 'dealt') {
  console.log('这张牌是发牌时获得的');
} else if (card.sourceInfo.sourceType === 'drawn') {
  console.log('这张牌是摸牌时获得的');
}

相关文档: 05-共享代码模块.md


Q10: 如何查看所有可用的常量?

A: 查看 shared/constants/index.js

var Constants = require('./shared/constants/index.js');

// 12个常量模块
console.log(Constants.GameConstants);       // 游戏常量
console.log(Constants.OperationTypes);      // 操作类型
console.log(Constants.ErrorMessages);       // 错误消息
console.log(Constants.RoomConstants);       // 房间常量
console.log(Constants.ScoreConstants);      // 分数常量
console.log(Constants.TimeoutConstants);    // 超时常量
// ... 更多常量模块

// 使用常量
var GAME_PHASES = Constants.GameConstants.GAME_PHASES;
var ERROR_INVALID_OPERATION = Constants.ErrorMessages.INVALID_OPERATION;

相关文档: 06-规则配置系统.md


📚 进阶学习资源

完整文档列表

框架基础

  1. 00-框架基础概述.md - 友乐平台架构和ES5规范
  2. 01-Export接口说明.md - 框架调用游戏的14个接口
  3. 02-Import接口说明.md - 游戏调用框架的13个接口
  4. 03-RPC处理机制.md - 客户端请求处理流程

核心服务

  1. 04-游戏核心服务.md - GameController等5大核心服务
  2. 05-共享代码模块.md - 精牌算法等核心算法详解
  3. 06-规则配置系统.md - 房间配置和规则解析

架构设计

  1. 08-游戏流程概述.md - 完整游戏流程和状态机
  2. 09-代码框架总结.md - 架构设计和设计模式

开发工具

  1. 07-工具模块.md - Logger和ErrorHandler使用指南

外部参考资料

  • docs/important/game/进贤麻将规则手册.md - 完整游戏规则
  • docs/important/game/进贤麻将技术设计要点.md - 技术设计细节
  • docs/important/server/服务器子游戏开发要求.md - 开发规范
  • docs/analysis/JingAlgorithm核心算法分析.md - 精牌算法深度分析

🎯 学习检查清单

完成以下检查项,确保你已经掌握了基础知识:

基础理解

  • 理解三文件架构(mod.js/export.js/import.js)
  • 知道RPC请求的处理流程
  • 了解游戏的7个阶段
  • 知道gameState对象的结构
  • 理解精牌系统的基本概念

代码定位

  • 能快速找到RPC方法的实现位置
  • 知道算法文件在哪个目录
  • 知道如何查看游戏配置的解析
  • 能找到日志和错误处理的工具类

实践能力

  • 能添加一条日志语句
  • 能修改一个计分规则
  • 能添加一个简单的RPC方法
  • 能查看和理解gameState的内容

问题解决

  • 遇到错误知道如何查看日志
  • 知道如何验证操作是否合法
  • 知道如何发送消息给客户端
  • 知道去哪里查找算法实现

💪 实战练习

练习1: 添加一条日志 (难度: )

任务: 在玩家出牌时添加日志记录

// 在 rpc/RpcHandler.js 的 player_discard 方法中
RpcHandler.prototype.player_discard = function(pack, room, callback) {
  var Logger = require('../utils/Logger.js');
  
  // 添加这行日志
  Logger.info('玩家出牌', {
    playerId: pack.playerid,
    tile: pack.data.tile,
    roomcode: room.roomcode
  });
  
  // ...原有代码
};

练习2: 修改七对分数 (难度: )

任务: 将有精七对的分数从8分改为10分

// 在 shared/algorithms/ScoreCalculation.js 中找到
case 'qidui':
  scores.baseScore = pattern.hasJing ? 8 : 64;  // 原来
  
// 修改为
case 'qidui':
  scores.baseScore = pattern.hasJing ? 10 : 64;  // 修改后

练习3: 添加操作验证 (难度: )

任务: 添加一个验证,禁止玩家在特定情况下出某张牌

// 在 game/OperationManager.js 中
OperationManager.validateDiscard = function(gameState, playerId, tileCode) {
  // 原有验证...
  
  // 添加自定义验证
  if (gameState.rules.specialRules.forbidSpecialCard) {
    if (tileCode === '1m') {  // 禁止出1万
      return {
        valid: false,
        error: 'FORBIDDEN_CARD',
        message: '当前规则禁止出这张牌'
      };
    }
  }
  
  return { valid: true };
};

🔗 快速链接

核心文件直达

算法文件直达

工具文件直达


📞 获取帮助

遇到问题时的步骤

  1. 查看日志: 检查Logger输出,了解程序执行流程
  2. 查看文档: 在对应的文档中查找相关说明
  3. 搜索代码: 在项目中搜索相关关键词
  4. 查看示例: 参考其他类似功能的实现
  5. 调试代码: 添加日志语句追踪执行流程

文档导航

  • 概念不清楚 → 查看框架基础文档(00-03)
  • 不知道怎么实现 → 查看核心服务文档(04-06)
  • 不理解流程 → 查看游戏流程文档(08)
  • 想了解设计 → 查看架构总结文档(09)

文档版本: v1.0
最后更新: 2025年10月15日
维护者: 进贤麻将开发团队


🎉 开始你的开发之旅!

现在你已经掌握了快速入门的所有知识,可以开始实际开发了!

建议的第一步:

  1. 选择一个感兴趣的功能
  2. 找到对应的代码位置
  3. 添加一些日志了解执行流程
  4. 尝试做一个小修改
  5. 测试验证你的修改

记住:

  • 📖 遇到问题先查文档
  • 🔍 善用搜索找代码
  • 📝 多写日志帮助理解
  • 🧪 改完代码记得测试

祝你开发愉快! 🚀