[
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "README.md",
    "content": "# gamex\n棋牌类游戏框架\n\n技术基于node.js，写这个东西是因为最近接触了一部分棋牌的代码，过程实在是不那么让人愉快，代码规范性，可读性，稳定性，扩展性都不好。于是想用一个能够快速开发的工具来实现一个自己的框架。\n\ncenter_svr  目前只做监控和服务器间的列表维护\ngate_svr    用户请求转发到服务器\nlogin_svr   用户帐号及登录授权\nhall_svr    游戏信息及游戏服务器信息\ngame_svr    游戏服务器\n\n除了center_svr以外，其它的服务器都可以水平扩展，可以多台分布式布署。center_svr是设计中的单点。\n客户端与网关间应该只有一个连接，在开发阶段客户端可直连游戏服务器，不通过网关，降低调试复杂度。\n对于多个同类服务器，使用随机访问的方式进行负载均衡。\n服务器间通信协议都使用json，因为简单，好调试，易扩展。\n服务间通信是自己模拟的rpc通信，需要严格的稳定性的话，要使用专门的rpc组件来实现。包括leader选举，广播等。目前的实现方式的问题在于，服务宕机了，不能及时通知到所有的服务。服务在调用时还需要自行判定被调者是否存活。\n\n游戏服务器包含多个房间，每个房间参数可配置，每个房间里有一个桌子，每个桌子有玩家。由于node.js的天性，它是io处理强，cpu计算弱的。所以不要在游戏服务器中添加大量的计算类任务。\n\n数据库使用SQL拼接的方式来写的。练手，练手。正确的做法是使用orm组件，避免大量重复的代码。\n\n第三方游戏支付：\n骏付通\n爱贝\n\n没想到这个项目有了这么多star……………………由于身体健康原因，项目停止了两年多了。\n"
  },
  {
    "path": "center_svr/README.md",
    "content": "\n中央服务器\n\n功能：服务注册，服务查询\n每一类服务器都有一个单独编号，编号是递增的。如果服务停掉了，这个编号可能会被下一个启动的服务使用。\n每个服务都可以带有扩展信息，但是不建议使用，因为一旦添加了扩展信息就意味着中央服务器要修改，中央服务器修改后就需要重启。而中央服务器是单点，一重启就会导致客户端全部掉线。这个编号是给客户端用的，但同一个游戏有多个服务器的时候，用户连上网关以后，网关需要根据这个编号去找对应的服务器。否则就要保证服务端端口永不重复。"
  },
  {
    "path": "center_svr/center-handler.js",
    "content": "\n\n//-------------------------------------\n//          redis\n//  type : server1 server2 server3\n//\n//\n//-------------------------------------\n\nconst rdsOper = require('./redis-oper');\nconst cmdDefine = require('../protocol/cmd-define');\nconst packet = require('../protocol/packet');\n\n//serverInfo\n// {\n//      name\n//      type\n//      ip\n//      port\n//      activeTime\n//      serverNo\n//      ext\n// }\nfunction registerServer(serverInfo) {\n\n    rdsOper.saveServer(serverInfo);\n}\n\nfunction removeServer() {\n\n    rdsOper.removeServer(serverInfo)\n}\n\nasync function getServer(socket, serverInfo) {\n\n    let serverList = await rdsOper.getServerList(serverInfo.type);\n}\n\nfunction process(socket, data) {\n\n\n    let jObj = JSON.parse(data);\n\n    console.log(jObj);\n\n    let mainCmd = jObj.head.mcmd;\n    let subCmd = jObj.head.scmd;\n    switch (subCmd) {\n        //更新服务状态\n        case cmdDefine.SUB_CENTER_UPDATE:\n            let serverInfo = jObj.body;\n            serverInfo.ip = socket.remoteAddress;\n            serverInfo.activeTime = Math.floor(new Date().getTime()/1000);\n            registerServer(serverInfo);\n            break;\n        //查询服务可用列表\n        case cmdDefine.SUB_CENTER_GET:\n            let serverList = getServer(socket, serverInfo);\n            let retObj = packet.getPacket(mainCmd,subCmd+100);\n            retObj.body = serverList;\n            socket.write(retObj);\n            break;\n        default:\n            break\n    }\n\n}\n\nexports.process = process;\n"
  },
  {
    "path": "center_svr/center-server.js",
    "content": "const net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\nconst process = require('process');\nconst centerHandler = require('./center-handler');\n\nconst server = net.createServer();\n\n//attention:\n//1.to be more stable\n//all request should put into a queue\n\nfunction startServer() {\n\n    server.on('listening', function () {\n        logger.info(\"server is listening on port:%d\", sysConfig.svrPort);\n    });\n\n    server.on('connection', function (socket) {\n\n        logger.info(\"connection income,ip:%s\", socket.remoteAddress);\n\n        socket.setTimeout(60 * 1000, function () {\n            logger.error(\"ip:%s,idle timeout, disconncting, bye\", socket.remoteAddress);\n            socket.end('idle timeout, disconnecting, bye!');\n            socket.destroy();\n        });\n\n        socket.on('data', function (data) {\n            logger.info(\"ip:%s, get data\", socket.remoteAddress);\n            console.debug(data);\n            centerHandler.process(socket, data);\n        });\n\n        socket.on('error', function (err) {\n            logger.info(\"ip:%s,error\", socket.remoteAddress);\n            logger.error(err);\n            socket.destroy();\n        });\n\n        socket.on('close', function (had_error) {\n            logger.info(\"ip:%s,socket closed\", socket.remoteAddress);\n            if (!socket.destroyed) {\n                logger.info(\"destroy socket\");\n                socket.destroy();\n            }\n        })\n    });\n\n    server.on('error', (err) => {\n        logger.error(err);\n        process.exit(1);\n    });\n\n    server.listen(sysConfig.svrPort, sysConfig.svrHost, () => {\n        logger.info('server bound on host:%s,port:%d', sysConfig.svrHost, sysConfig.svrPort);\n    });\n}\n\nexports.startServer = startServer;"
  },
  {
    "path": "center_svr/center.js",
    "content": "\ncenterServer = require('./center-server');\n\ncenterServer.startServer();"
  },
  {
    "path": "center_svr/config/log4js.json",
    "content": "{\n  \"appenders\": {\n    \"logfile\": {\n      \"type\": \"file\",\n      \"filename\": \"log/center.log\",\n      \"maxLogSize\": 10485760,\n      \"numBackups\": 3\n    },\n    \"console\": {\n      \"type\": \"console\",\n      \"level\": \"debug\"\n    }\n  },\n  \"categories\": {\n    \"default\": {\n      \"appenders\": [\n        \"console\",\n        \"logfile\"\n      ],\n      \"level\": \"debug\"\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "center_svr/config/sys-config.json",
    "content": "\n{\n  \"svrHost\":\"127.0.0.1\",\n  \"svrPort\":9200,\n  \"redisHost\":\"127.0.0.1\",\n  \"redisPort\":6379,\n  \"redisPassword\":\"\"\n}"
  },
  {
    "path": "center_svr/logger.js",
    "content": "\nconst config = require('./config/log4js.json');\n\nconst log4js = require('log4js');\nlog4js.configure(config);\n\nmodule.exports = log4js.getLogger();"
  },
  {
    "path": "center_svr/redis-oper.js",
    "content": "\n//\n// 将服务信息保存在redis中，如果服务比较多，会造成冲突。每5秒一次心跳，极端情况下可能有服务永远注册不上。\n// 仅有几个服务甚至十几个服务可以忽略上面的问题。有几十个服务就不要使用这种方式了，应该用rpc。\n//\n\nconst sysConfig = require('./config/sys-config.json');\nconst redis = require('redis');\nconst rdsKey = require('../lib/rds-key');\nconst logger = require('./logger');\n\nconst host = sysConfig.redisHost;\nconst port = sysConfig.redisPort;\nconst password = sysConfig.redisPassword;\nlet rds = null;\n\nfunction getConnection() {\n\n    if (rds) return rds;\n\n    if (!!password && password.length > 0) {\n        rds = redis.createClient({host: host, port: port, password: password});\n    } else {\n        rds = redis.createClient({host: host, port: port});\n    }\n    return rds;\n}\n\ngetConnection();\n\n//保存服务信息\nfunction saveServer(serverInfo) {\n\n    let keyName = rdsKey.KEY_SERVER_TYPE + serverInfo.type;\n\n    rds.get(keyName, function (err, reply) {\n        if (err) {\n            logger.error(err);\n            return;\n        }\n\n        jArray = JSON.parse(reply);\n\n        if (!jArray) {\n            jArray = [];\n        }\n\n        let serverIDList = [];\n        for (let v of jArray) {\n            serverIDList.push(v.serverNo);\n        }\n\n        serverIDList.sort(function (a,b) {\n            if(a<b) return -1;\n            else if(a===b) return 0;\n            else return 1;\n        });\n\n        let serverId = 1;\n        let prevID = 0;\n        let count = 0;\n        for(let v in serverIDList) {\n            prevID = v;\n            count++;\n            if(count === 1) {\n                serverId = prevID;\n                continue;\n            }\n            // get a usefull id, TODO cocurrency problem\n            if(Math.abs(v-prevID) > 1) {\n                serverId = prevID + 1;\n                break;\n            }\n        }\n\n        // set server number\n        if(maxServerId > 1000) {\n            maxServerId = 1;\n        }\n        serverInfo.serverNo = maxServerId + 1;\n\n\n        let found = false;\n        for (let v of jArray) {\n            if (v.ip === serverInfo.ip) {\n                // do nothing\n                found = true;\n                break;\n            }\n        }\n\n        if (!found) {\n            jArray.push(serverInfo);\n            let json = JSON.stringify(jArray);\n            rds.set(keyName, json, 60*1000);\n        }\n\n    });\n}\n\n//查询可用服务列表\nfunction getServerList(type) {\n\n    return new Promise(function (resolve, reject) {\n        let keyName = rdsKey.KEY_SERVER_TYPE + type;\n        rds.get(keyName, function (err, reply) {\n            if (err) {\n                reject(err);\n                return;\n            }\n            resolve(reply);\n        })\n    });\n}\n\n//移除掉线的服务\nasync function removeServer(serverInfo) {\n    let serverList = await getServerList(serverInfo.type);\n    let jArray = JSON.parse(serverList);\n    let idx = 0;\n    for (let v of jArray) {\n        if (v.ip === serverInfo.ip) {\n            delete jArray[idx];\n            break;\n        }\n        idx++;\n    }\n    let keyName = rdsKey.KEY_SERVER_TYPE + serverInfo.type;\n    let json = JSON.stringify(jArray);\n    rds.set(keyName, json, 60*1000);\n}\n\nexports.getServerList = getServerList;\nexports.saveServer = saveServer;\nexports.removeServer = removeServer;"
  },
  {
    "path": "center_svr/short-ID.js",
    "content": "\nvar idx = 1024;\n\n function getNextID() {\n    if(idx > 4200000000) {\n        idx = 1024;\n    }\n    idx++;\n    return idx;\n}\n\n\nexports.getNextID = getNextID;\n"
  },
  {
    "path": "game_svr/README.md",
    "content": "\n游戏服务器可以水平扩展。\n游戏通过插件方式写在plugin目录下，同时将插件信息配置到config/sys-config中。\n每个游戏一到多个游戏服务器，但不支持一个游戏服务器跑多个游戏。\n每个游戏服务器有多个房间列表，房间相关配置信息在数据库中。玩家也可以自定义房间配置，覆盖默认的数据库中的配置。\n游戏服务器启动后会自动往center服务器上报自己的信息。\n游戏服务器会调用插件中的方法，如果插件不实现某个方法将会导致整个服务崩溃。\n每个服务可开启的房间数是有上限的，现在限制为2000个房间。"
  },
  {
    "path": "game_svr/config/log4js.json",
    "content": "{\n  \"appenders\": {\n    \"logfile\": {\n      \"type\": \"file\",\n      \"filename\": \"log/game.log\",\n      \"maxLogSize\": 10485760,\n      \"numBackups\": 3\n    },\n    \"console\": {\n      \"type\": \"console\",\n      \"level\": \"debug\"\n    }\n  },\n  \"categories\": {\n    \"default\": {\n      \"appenders\": [\n        \"console\",\n        \"logfile\"\n      ],\n      \"level\": \"debug\"\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "game_svr/config/sys-config.json",
    "content": "\n{\n  \"svrHost\":\"127.0.0.1\",\n  \"svrPort\":9000,\n  \"redisHost\":\"127.0.0.1\",\n  \"redisPort\":6379,\n  \"redisPassword\":\"\",\n  \"centerSvrHost\":\"127.0.0.1\",\n  \"centerSvrPort\":9200,\n  \"plugin\":\"ddz.js\"\n}"
  },
  {
    "path": "game_svr/desk.js",
    "content": "\n\nconst sysConfig = require('./config/sys-config.json');\nconst cmdDefine = require('../protocol/cmd-define');\n\nfunction init() {\n    gameInst = require('./plugin/'+sysConfig.plugin);\n}\n\nfunction playerOpeCard(socket, packet) {\n\n    const cardEvent = packet.body.cardEvent;\n    switch (cardEvent.ope) {\n        case cmdDefine.EVENT_OUT_CARD:\n            gameInst.outCards(cardEvent);\n            break;\n        case cmdDefine.EVENT_PASS_CARD:\n            gameInst.passCards(cardEvent);\n            break;\n        default:\n            break;\n    }\n}"
  },
  {
    "path": "game_svr/game-handler.js",
    "content": "\n\nconst packet = require('../protocol/packet');\nconst cmdDefine = require('../protocol/cmd-define');\nconst room = require('./room');\n\nfunction process(socket, data) {\n\n    let jObj = JSON.parse(data);\n    let mainCmd = jObj.head.mcmd;\n    let subCmd = jObj.head.scmd;\n\n    switch (subCmd) {\n        case cmdDefine.SUB_GAME_CREATE_ROOM:    //创建房间\n            room.createRoom(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_ENTER_ROOM:     //加入房间\n            room.enterRoom(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_LEAVE_ROOM:     //离开房间\n            room.leaveRoom(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DISMISS_ROOM:   //解散房间\n            room.dismissRoom(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_REENTER_ROOM:   //断线重进房间\n            room.reenterRoom(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_QUERY_PLAYER_INFO:     //查询玩家游戏状态\n            room.queryPlayerInfo(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DESK_SIT_DOWN:  //坐下\n            room.playerSitDown(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DESK_READY:     //准备\n            room.playerReady(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DESK_STAND_UP:  //起立\n            room.playerStandUp(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DESK_WATCH:     //观战\n            room.playerWatch(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DESK_OPE_CARD:  //出牌，过牌，吃牌，碰，杠....等牌操作\n            room.playerOpeCard(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_DESK_WIN:       //胡牌\n            room.playerWin(socket, jObj);\n            break;\n        case cmdDefine.SUB_GAME_KICK_PLAYER:    //房主踢人\n            room.kickPlayer(socket, jObj);\n            break;\n        default:\n            logger.error(\"unknown mainCmd:%d,subCmd:%d\", mainCmd, subCmd);\n            break;\n    }\n}\n\n\nexports.process = process;"
  },
  {
    "path": "game_svr/game-server.js",
    "content": "const net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\nconst process = require('process');\nconst gameHandler = require('./gane-handler');\n\nconst server = net.createServer();\n\nfunction startServer() {\n\n    server.on('listening', function () {\n        logger.info(\"server is listening on port:%d\", sysConfig.svrPort);\n    });\n\n    server.on('connection', function (socket) {\n\n        socket.setTimeout(60 * 1000, function () {\n            logger.error(\"ip:%s,idle timeout, disconncting, bye\", socket.remoteAddress);\n            socket.end('idle timeout, disconnecting, bye!');\n            socket.destroy();\n        });\n\n        socket.on('data', function (data) {\n            logger.info(\"ip:%s, get data\", socket.remoteAddress);\n            console.debug(data);\n            gameHandler.process(socket, data);\n        });\n\n        socket.on('error', function (err) {\n            logger.info(\"ip:%s,error\", socket.remoteAddress);\n            logger.error(err);\n            socket.destroy();\n        });\n\n        socket.on('close', function (had_error) {\n            logger.info(\"ip:%s,socket closed\", socket.remoteAddress);\n            if (!socket.destroyed) {\n                logger.info(\"destroy socket\");\n                socket.destroy();\n            }\n        })\n    });\n\n    server.on('error', (err) => {\n        logger.error(err);\n        process.exit(1);\n    });\n\n    server.listen(sysConfig.svrPort, sysConfig.svrHost, () => {\n        logger.info('server bound on host:%s,port:%d', sysConfig.svrHost, sysConfig.svrPort);\n    });\n}\n\nexports.startServer = startServer;"
  },
  {
    "path": "game_svr/game.js",
    "content": "\n\nconst registerCenter = require('./register-center');\nconst gameServer = require('./game-server');\nconst sysConfig = require('./config/sys-config.json');\n\n\nconsole.log(sysConfig);\n\nsetInterval(registerCenter.registerSelf, 5*1000);\n\ngameServer.startServer();\n\n"
  },
  {
    "path": "game_svr/plugin/ddz-rule.js",
    "content": "/*\n    牌列表说明：\n每个牌的每个花色都有一个单独的ID，一共54个ID，ID按黑红梅方的顺序生成\na,2,3,....q,k 黑桃 [1,2,3,4,5,6,7,8,9,10,11,12,13]\na,2,3,....q,k 红桃 [1,2,3,4,5,6,7,8,9,10,11,12,13] * 2\na,2,3,....q,k 梅花 [1,2,3,4,5,6,7,8,9,10,11,12,13] * 3\na,2,3,....1,k 方块 [1,2,3,4,5,6,7,8,9,10,11,12,13] * 4\n小王\n大王\n*/\n\nconst  assert = require('assert');\n\n// 花色定义\nlet SUIT_TYPE = {\n    SPADE: 1,       //黑桃\n    HEART: 2,       //红桃\n    CLUB: 3,        //梅花\n    DIAMOND: 4,     //方块\n    JOKER: 5,       //大小王\n};\n\n// 牌面显示定义\nlet CARD_CHAR = [\n    \"null\",\"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"J\", \"Q\", \"K\", \"Kinglet\", \"King\",\n];\n\n// 出牌类型\nlet CARD_TYPE = {\n    ILL: 0,\n    DAN: 1,                 //单张\n    DUI_ZI: 2,              //对子\n    WANG_ZHA: 3,            //王炸\n    ZHA_DAN: 4,             //炸弹\n    SHUN_ZI: 5,             //顺子\n    LIAN_DUI: 6,            //连对\n    SAN: 7,                 //三张（不带）\n    SAN_DAI_YI: 8,          //三带一\n    SAN_DAI_YI_DUI: 9,      //三带一对\n    FEI_JI: 10,             //飞机（不带）\n    FEI_JI_DAI_YI: 11,      //飞机（带单）\n    FEI_JI_DAI_DUI: 12,     //飞机（带对子）\n    SI_DAI_ER: 13,          //四带二\n    SI_DAI_LIANG_DUI: 14,   //四带两对\n};\n\n// 获取花色\n// 3-q,k,a,2 每张牌有四个花色\nfunction getSuitType(id) {\n\n    let suitType = null;\n\n    if (id >= 1 && id <= 13)\n        suitType = SUIT_TYPE.SPADE;\n    else if (id >= 14 && id <= 26)\n        suitType = SUIT_TYPE.HEART;\n    else if (id >= 27 && id <= 39)\n        suitType = SUIT_TYPE.CLUB;\n    else if (id >= 40 && id <= 52)\n        suitType = SUIT_TYPE.DIAMOND;\n    else if (id === 53 || id === 54)\n        suitType = SUIT_TYPE.JOKER;\n\n    return suitType\n}\n\n// 获取显示字\nfunction getCardChar(id) {\n\n    let displayChar = null;\n\n    if (id >= 1 && id <= 52)\n        displayChar = CARD_CHAR[(id - 1) % 13 + 1];\n    else if (id === 53)\n        displayChar = CARD_CHAR[14];\n    else if (id === 54)\n        displayChar = CARD_CHAR[15];\n\n    return displayChar;\n}\n\n\n// 获取牌等级\n// 大王 > 小王 > 2 > A > K .... > 3\nfunction getGrade(id) {\n    let grade = 0;\n    if (id === 54) {       //大王\n        grade = 17;\n    } else if (id === 53) {    //小王\n        grade = 16;\n    } else {\n        let modResult = id % 13;\n        if (modResult === 2)       // 2\n            grade = 15;\n        else if (modResult === 1)   // A\n            grade = 14;\n        else if (modResult === 0)  // K\n            grade = 13;\n        else if (modResult >= 3 && modResult < 13)   // 3到Q\n            grade = modResult;\n    }\n\n    return grade;\n}\n\n\nlet AllCards = [];\nfor (let i = 1; i <= 54; i++) {\n    AllCards[i] = {\n        id: i,\n        grade: getGrade(i),        // 等级\n        suit: getSuitType(i),     // 花色\n        display: getCardChar(i),     // 牌面\n    };\n}\n\nconsole.log(AllCards);\n\nfunction sortCards(cards) {\n    cards.sort(function (a, b) {\n        if(AllCards[a].grade > AllCards[b].grade) return -1;\n        else if(AllCards[a].grade === AllCards[b].grade) return 0;\n        else  return 1;\n    });\n}\n\nfunction getGrades(cards) {\n    let t = {};\n    for (let v of cards) {\n        let grade = AllCards[v].grade;\n        if (! (grade in t)) {\n            t[grade] = 0;\n        }\n        t[grade] = t[grade] + 1;\n    }\n    return t;\n}\n\n\nfunction dump(cards) {\n    sortCards(cards);\n    for (let v of cards) {\n        console.log(\"花色：%d，牌面：%s, 权重：%d\", AllCards[v].suit, AllCards[v].display, AllCards[v].grade);\n    }\n}\n\n\n//单张\nfunction isDan(cards) {\n    return cards.length === 1;\n}\n\n\nassert(isDan([33]));\nassert(isDan([23, 5]) === false);\n\n//对子\nfunction isDuiZi(cards) {\n    return cards.length === 2\n        && AllCards[cards[0]].grade === AllCards[cards[1]].grade;\n}\n\nassert(isDuiZi([30, 43]));\nassert(isDuiZi([30, 52]) === false);\n\n//大小王炸弹\nfunction isDuiWang(cards) {\n    return (cards.length === 2) && (cards[0] + cards[1] === 107);\n\n}\n\nassert(isDuiWang([53, 54]));\nassert(isDuiWang([53, 52]) === false);\n\n\n//三张\nfunction isSan(cards) {\n    return cards.length === 3\n        && (AllCards[cards[0]].grade === AllCards[cards[1]].grade)\n        && (AllCards[cards[1]].grade === AllCards[cards[2]].grade);\n}\n\nassert(isSan([30, 43, 17]));   //4 4 4\nassert(isSan([30, 43, 15]) === false);//4 4 2\n\n//三带一\nfunction isSanDaiYi(cards) {\n    if (cards.length !== 4) {\n        return false;\n    }\n\n    sortCards(cards);\n    if (AllCards[cards[0]].grade !== AllCards[cards[1]].grade) {\n        let t = cards[3];\n        cards[3] = cards[0];\n        cards[0] = t;\n    }\n\n    if (AllCards[cards[0]].grade === AllCards[cards[1]].grade) {\n        if (AllCards[cards[0]].grade === AllCards[cards[2]].grade\n            && AllCards[cards[0]].grade !== AllCards[cards[3]].grade) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nassert(isSanDaiYi([30, 43, 17, 5]));  //4 4 4 3\nassert(isSanDaiYi([30, 43, 17, 4]) === false); //4 4 4 4\n\n//三带一对\nfunction isSanDaiDui(cards) {\n    if (cards.length !== 5) {\n        return false;\n    }\n\n    sortCards(cards);\n    if (AllCards[cards[0]].grade !== AllCards[cards[2]].grade) {\n        let t = cards[3];\n        cards[3] = cards[0];\n        cards[0] = t;\n\n        t = cards[1];\n        cards[1] = cards[4] ;\n        cards[4] = t;\n    }\n\n    if (AllCards[cards[0]].grade === AllCards[cards[1]].grade) {\n        if (AllCards[cards[0]].grade === AllCards[cards[2]].grade\n            && AllCards[cards[2]].grade !== AllCards[cards[3]].grade\n            && AllCards[cards[3]].grade === AllCards[cards[4]].grade) {\n            return true;\n        }\n    } else {\n        return false;\n    }\n    return false;\n}\n\nassert(isSanDaiDui([30, 43, 17, 5, 31]));// 4 4 4 5 5\nassert(isSanDaiDui([30, 43, 17, 3, 31]) === false);//4 4 4 5 3\n\n\n//四张（炸弹）\nfunction isSi(cards) {\n    if (cards.length !== 4) {\n        return false;\n    }\n    for (let i = 0; i < 3; i++) {\n        if (AllCards[cards[i]].grade !== AllCards[cards[i + 1]].grade) {\n            return false;\n        }\n    }\n    return true;\n}\n\n\nassert(isSi([30, 43, 17, 4]));// 4 4 4 4\nassert(isSi([30, 43, 18, 4]) === false); // 4 4 5 4\n\n//四带二\nfunction isSiDaiEr(cards) {\n    if (cards.length !== 6) {\n        return false;\n    }\n    let grades = getGrades(cards);\n    for (let i in grades) {\n        if (grades[i] === 4) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nassert(isSiDaiEr([30, 43, 17, 4, 3, 5]));// 4 4 4 4 3 5\nassert(isSiDaiEr([30, 43, 17, 4, 3, 16]));// 4 4 4 4 3 3\n\n//四带两对\nfunction isSiDaiLiangDui(cards) {\n    if (cards.length !== 8) {\n        return false\n    }\n    let grades = getGrades(cards);\n    let t1 = 0;\n    let t2 = 0;\n    let t3 = 0;\n    for (let i in grades) {\n        if (!t1) {\n            t1 = grades[i]\n        } else if (!t2) {\n            t2 = grades[i];\n        } else if (!t3) {\n            t3 = grades[i];\n        }\n    }\n\n    let arr = [t1, t2, t3];\n    arr.sort(function (a, b) {\n        if( a > b) { return -1;}\n        else if(a===b) {return 0;}\n        else {return 1;}\n    });\n\n    return (arr.length === 3) &&\n        (arr[0] === 4) && (arr[1] === 2) && (arr[2] === 2);\n}\n\nassert(isSiDaiLiangDui([30, 43, 17, 4, 3, 16, 5, 18]));// 4 4 4 4 3 3 5 5\nassert(isSiDaiLiangDui([3, 16, 5, 18, 30, 43, 17, 4]));// 3 3 5 5 4 4 4 4\nassert(isSiDaiLiangDui([30, 43, 17, 4, 3, 6, 5, 18]) === false); // 4 4 4 4 3 6 5 5\n\n\n//顺子\nfunction isShunZi(cards) {\n\n    // 顺子必须超过五张\n    if (cards.length < 5)\n        return false;\n\n    sortCards(cards);\n\n    //最大的牌不能超过A\n    if (AllCards[cards[0]].grade > 14)\n        return false;\n\n    //如果不连续\n    for (let i=0;i<cards.length-1;i++) {\n        if (AllCards[cards[i]].grade !== AllCards[cards[i + 1]].grade + 1)\n            return false;\n    }\n\n    return true;\n}\n\nassert(isShunZi([13, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1]));\nassert(isShunZi([35, 21, 20, 6, 31, 4]));//9 8 7 6 5 4\nassert(isShunZi([21, 20, 4, 31]) === false);  //9 8 7 4 5\nassert(isShunZi([35, 21, 5, 6, 31]) === false);  //9 8 5 6 5 4\n\n//连对\nfunction isLianDui(cards)\n{\n    //连对必须有三对以上\n    if (cards.length < 6)\n        return false;\n\n    //连对必须牌数是偶数\n    if (cards.length % 2 === 1)\n        return false;\n\n    sortCards(cards);\n\n    //最大的牌不能超过A\n    if (AllCards[cards[0]].grade > 14)\n        return false;\n\n    //如果牌不连续\n    for (let i=0; i < cards.length / 2 - 1; i++) {\n        //先判定是不是都是对子\n        if (AllCards[cards[i * 2]].grade !== AllCards[cards[i * 2 + 1]].grade)\n            return false;\n\n        //再判定连续对\n        if (i < cards.length / 2 - 1\n            && AllCards[cards[i * 2 + 1]].grade !== AllCards[cards[(i+1) * 2 ]].grade + 1)\n            return false;\n    }\n\n    return true;\n}\n\nassert(isLianDui([35, 9, 21, 8, 20, 7]));// 9 9 8 8 7 7\nassert(isLianDui([35, 9, 21, 8, 20, 7, 6, 19]));// 9 9 8 8 7 7 6 6\nassert(isLianDui([35, 9, 21, 8, 20, 7, 33]) === false);// 9 9 8 8 7 7 7\n\n//飞机(不带)\nfunction isFeiJi(cards) {\n\n    if (cards.length !== 6 && cards.length !== 9)\n        return false;\n\n    let grades = getGrades(cards);\n\n    // 如果不是三张，就判错\n    for (let i in grades) {\n        if (grades[i] !== 3)\n            return false;\n    }\n\n    //如果是三张组合，还要判定是不是连续的\n    let t1 = 0;\n    let t2 = 0;\n    let t3 = 0;\n    for(k in grades) {\n        if (grades[k] === 3) {\n            if (!t1) t1 = Number(k);\n            else if(!t2) t2 = Number(k);\n            else if(!t3) t3 = Number(k);\n        }\n    }\n\n    if (cards.length === 6 && t1 && t2 && (t1 === t2 + 1 || t2 === t1 + 1))\n        return true;\n\n    if (cards.length === 9 && t1 && t2 && t3) {\n        let arr = [t1, t2, t3];\n        arr.sort(function (a, b) {\n            if (a > b) return -1;\n            else if (a === b) return 0;\n            else return 1;\n        });\n\n        if (arr[0] === arr[1] + 1 && arr[1] === arr[2] + 1)\n            return true;\n    }\n\n    return false;\n}\n\nassert(isFeiJi([35, 9, 22, 21, 8, 34]));// 9 9 9 8 8 8\nassert(isFeiJi([35, 9, 22, 21, 8, 34, 7, 20, 33]));// 9 9 9 8 8 8 7 7 7\nassert(isFeiJi([35, 9, 22, 20, 7, 33]) === false);// 9 9 9 7 7 7\n\n//飞机(带单张)\nfunction isFeiJiDaiDan(cards) {\n    if (cards.length !== 8 && cards.length !== 12)\n        return false;\n\n    let grades = getGrades(cards);\n\n    let t1 = 0;\n    let t2 = 0;\n    let t3 = 0;\n    for(let k in grades) {\n        if (grades[k] === 3) {\n            if (!t1) t1 = Number(k);\n            else if(!t2) t2 = Number(k);\n            else if(!t3) t3 = Number(k);\n        }\n    }\n\n    //判定三张是否连续\n    if (cards.length === 8 && t1 && t2 && (t1 === t2 + 1 || t2 === t1 + 1))\n        return true;\n\n    if (cards.length === 12 && t1 && t2 && t3) {\n        let arr = [t1, t2, t3];\n        arr.sort(function (a,b) {\n            if (a>b)return -1;\n            else if(a===b) return 0;\n            else  return 1;\n        });\n\n        if (arr[0] === arr[1] + 1 && arr[1] === arr[2] + 1)\n            return true;\n    }\n\n    return false;\n}\n\nassert(isFeiJiDaiDan([35, 9, 22, 21, 8, 34, 3, 4]));// 9 9 9 8 8 8 3 4\nassert(isFeiJiDaiDan([35, 9, 22, 21, 8, 34, 7, 20, 33, 3, 4, 5]));// 9 9 9 8 8 8 3 4 5\nassert(isFeiJiDaiDan([35, 9, 22, 20, 7, 33, 3, 4]) === false);// 9 9 9 7 7 7\n\n//飞机(带对子)\nfunction isFeiJiDaiDui(cards) {\n    if (cards.length !== 10 && cards.length !== 15)\n        return false;\n\n    let grades = getGrades(cards);\n\n    let t1 = 0;\n    let t2 = 0;\n    let t3 = 0;\n    for(let k in grades) {\n        if (grades[k] === 3) {\n            if (!t1) t1 = Number(k);\n            else if(!t2) t2 = Number(k);\n            else if(!t3) t3 = Number(k);\n        } else if(grades[k] !== 2) {\n            return false;\n        }\n    }\n\n    if (cards.length === 10 && t1 && t2 && (t1 === t2 + 1 || t2 === t1 + 1) )\n        return true;\n\n    if (cards.length === 15 && t1 && t2 && t3) {\n        let arr = [t1, t2, t3];\n        arr.sort(function (a, b) {\n            if (a > b) return -1;\n            else if (a === b) return 0;\n            else return 1;\n        });\n        if (arr[0] === arr[1] + 1 && arr[1] === arr[2] + 1)\n            return true;\n    }\n\n    return false;\n}\n\nassert(isFeiJiDaiDui([35, 9, 22, 21, 8, 34, 7, 20, 33, 3, 16, 4, 17, 5, 18]));//9 9 9 8 8 8 7 7 7 3 3 4 4 5 5\nassert(isFeiJiDaiDui([35, 9, 22, 21, 8, 34, 3, 4, 16, 17]));// 9 9 9 8 8 8 3 3 4 4\nassert(isFeiJiDaiDui([35, 9, 22, 21, 8, 34, 3, 4, 16, 18]) === false); // 9 9 9 8 8 8 3 3 4 5\n\nfunction getCardType(cards) {\n    let c = cards.length;\n    if (c === 1) {         //单张\n        return CARD_TYPE.DAN;\n    } else if (c === 2) {     //两张只可能是王炸或一对\n        if (isDuiWang(cards)) {\n            return CARD_TYPE.WANG_ZHA;\n        } else if (isDuiZi(cards)) {\n            return CARD_TYPE.DUI_ZI;\n        }\n    } else if (c === 3) {    //三张只可能是单出三张\n        if (isSan(cards))\n            return CARD_TYPE.SAN;\n    } else if (c === 4) {    //四张只可能是炸弹或三带一\n        if (isSi(cards))\n            return CARD_TYPE.ZHA_DAN;\n        else if (isS && aiYi(cards))\n            return CARD_TYPE.SAN_DAI_YI;\n    } else if (c === 5) {    //5张只可能是顺子或三带一对\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n        else if (isSanDaiDui(cards))\n            return CARD_TYPE.SAN_DAI_YI_DUI;\n    } else if (c === 6) {    //6张可能是顺子、飞机（不带）、四带二、连对\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n        else if (isFeiJi(cards))\n            return CARD_TYPE.FEI_JI;\n        else if (isSiDaiEr(cards))\n            return CARD_TYPE.SI_DAI_ER;\n        else if (isLianDui(cards))\n            return CARD_TYPE.LIAN_DUI;\n    } else if (c === 7 || c === 11) {  //7或11张只可能是顺子\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n    } else if (c === 8) {     //8张可能是顺子、连对、飞机（带单）\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n        else if (isFeiJiDaiDan(cards))\n            return CARD_TYPE.FEI_JI;\n        else if (isLianDui(cards))\n            return CARD_TYPE.LIAN_DUI;\n    } else if (c === 9) {      //9张只可能是顺子、飞机（3头不带）\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n        else if (isFeiJi(cards))\n            return CARD_TYPE.FEI_JI;\n    } else if (c === 10) {    //10张可能是顺子、飞机(2头带对子)、连对\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n        else if (isFeiJiDaiDui(cards))\n            return CARD_TYPE.FEI_JI;\n        else if (isLi && ui(cards))\n            return CARD_TYPE.LIAN_DUI;\n    } else if (c === 12) {    //12张可能是顺子、飞机(3头带单张)、连对\n        if (isShunZi(cards))\n            return CARD_TYPE.SHUN_ZI;\n        else if (isFeiJiDaiDan(cards))\n            return CARD_TYPE.FEI_JI;\n        else if (isLi && ui(cards))\n            return CARD_TYPE.LIAN_DUI;\n    } else if (c ===13) {\n        //13张不合法，顺子带'2'，不可能是飞机，不可能是4带\n    } else if (c === 14) {   //14张只可能是连对\n        if (isLi && ui(cards))\n            return CARD_TYPE.LIAN_DUI;\n    } else if (c === 15) {  //15张只可能是飞机（3头带对子）\n        if (isFeiJiDaiDui(cards))\n            return CARD_TYPE.FEI_JI;\n    }\n\n    return CARD_TYPE.ILL;\n}\n\nassert(getCardType([54]) === CARD_TYPE.DAN);\nassert(getCardType([54, 53]) === CARD_TYPE.WANG_ZHA);\nassert(getCardType([3, 3]) === CARD_TYPE.DUI_ZI);\nassert(getCardType([3, 3, 4, 4, 5, 5]) === CARD_TYPE.LIAN_DUI);\nassert(getCardType([3, 4, 5, 6, 7]) === CARD_TYPE.SHUN_ZI);\nassert(getCardType([13, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1]) === CARD_TYPE.SHUN_ZI);\nassert(getCardType([3, 3, 3, 3]) === CARD_TYPE.ZHA_DAN);\nassert(getCardType([3, 3, 3, 3, 4, 4, 4, 4]) !== CARD_TYPE.FEI_JI);\nassert(getCardType([3, 4, 5, 6, 7, 8, 9]) === CARD_TYPE.SHUN_ZI);\nassert(getCardType([3, 3, 3, 4, 4, 4, 5, 5, 5]) === CARD_TYPE.FEI_JI);\nassert(getCardType([3, 3, 3, 1, 4, 4, 4, 2, 5, 5, 5, 7]) === CARD_TYPE.FEI_JI);\nassert(getCardType([3, 3, 3, 1, 1, 4, 4, 4, 2, 2, 5, 5, 5, 7, 7]) === CARD_TYPE.FEI_JI);\nassert(getCardType([3, 3, 4, 4, 2, 2]) === CARD_TYPE.ILL);\nassert(getCardType([3, 3, 3, 4, 2]) === CARD_TYPE.ILL);\nassert(getCardType([3, 3, 3, 4, 4, 4, 2]) === CARD_TYPE.ILL);\nassert(getCardType([2, 3, 4, 5, 6]) === CARD_TYPE.ILL);\nassert(getCardType([3, 4, 5, 6, 7, 7]) === CARD_TYPE.ILL);\n////////////////////////////////////////////-\n\n\n// 获取牌信息，花色，牌面，权重\nfunction getCardDetail(card) {\n    return AllCards[card];\n}\n\n\n// 将多张牌排序后返回牌信息\nfunction getSortedCardsDetail(cards) {\n    sortCards(cards);\n    let cardDetails = [];\n    for (let i in cards) {\n        cardDetails[i] = getCardDetail(cards[i]);\n    }\n    return cardDetails;\n}\n\n\n// 检查能不能出牌\nfunction checkCards(preCards, curCards) {\n    let card_type = getCardType(curCards);\n    if (card_type === CARD_TYPE.ILL)\n        return false;\n\n    //头次打\n    if (preCards === null)\n        return true;\n\n    // 上家牌类型\n    let pre_act = getCardType(preCards);\n    if (card_type === CARD_TYPE.WANG_ZHA) {   // 王炸无视任何牌\n        return true;\n    } else if (card_type === CARD_TYPE.ZHA_DAN) {  //出炸弹，炸弹需要和上家比大小\n        if (pre_act === CARD_TYPE.WANG_ZHA) {\n            return false;\n        } else if (pre_act === CARD_TYPE.ZHA_DAN) {\n            if (AllCards[preCards[1]].grade < AllCards[curCards[1]].grade)\n                return true;\n            else\n                return false;\n        } else {\n            return true;\n        }\n    } else if (pre_act !== card_type) {  // 和上家比牌型，上家出什么自己也要出什么\n        return false;\n    } else if (preCards.length !== curCards.length) {\n        return false;\n    } else {                          // 牌型一样，但必须比上家大\n        sortCards(preCards);\n        sortCards(curCards);\n        if (AllCards[preCards[1]].grade < AllCards[curCards[1]].grade)\n            return true;\n        else\n            return false;\n    }\n    return false;\n}\n\nfunction getAllCards() {\n    return AllCards;\n}\n\nexports.checkCards      = checkCards;\nexports.getCardDetail   = getCardDetail;\nexports.getSortedCardsDetail = getSortedCardsDetail;\nexports.getCardType     = getCardType;\nexports.getAllCards     = getAllCards;\nexports.dump            = dump;\n"
  },
  {
    "path": "game_svr/plugin/ddz.js",
    "content": "\nconst ddzRule = require('./ddz-rule');\n\nclass DouDiZhu {\n\n    constructor() {\n        //牌池\n        this.cardPool = [];\n        //座位\n        this.seats = {};\n        //地主\n        this.diZhu = 0;\n        //底牌\n        this.diPai = [];\n    }\n\n    //初始化\n    initGame() {\n\n        let full = [];\n        let allCards = ddzRule.getAllCards();\n        for(let i=0;i<54;i++) {\n            full.push(allCards[i+1].id);\n        }\n\n        let pos = 1;\n        this.seats[pos] = {};\n        this.seats[pos].handCards = []; //手上的牌\n        this.seats[pos].outCards = [];  //已经打出的牌\n\n        //洗牌，每人发17张\n        let arrLength = full.length;\n        for(let i=0;i<17;i++) {\n            let j = Math.floor(Math.random()*arrLength);\n            this.seats[pos].handCards.push(full[j]);\n            full.splice(j,1);\n            arrLength--;\n        }\n\n        pos = 2;\n        this.seats[pos] = {};\n        this.seats[pos].handCards = []; //手上的牌\n        this.seats[pos].outCards = [];  //已经打出的牌\n\n        for(let i=0;i<17;i++) {\n            let j = Math.floor(Math.random()*arrLength);\n            this.seats[pos].handCards.push(full[j]);\n            full.splice(j,1);\n            arrLength--;\n        }\n\n        pos = 3;\n        this.seats[pos] = {};\n        this.seats[pos].handCards = []; //手上的牌\n        this.seats[pos].outCards = [];  //已经打出的牌\n\n        for(let i=0;i<17;i++) {\n            let j = Math.floor(Math.random()*arrLength);\n            this.seats[pos].handCards.push(full[j]);\n            full.splice(j,1);\n            arrLength--;\n        }\n\n        //留下三张给地主\n        this.diPai = full;\n\n        //ddzRule.dump(this.seats[1].handCards);\n        //ddzRule.dump(this.seats[2].handCards);\n        //ddzRule.dump(this.seats[3].handCards);\n        //ddzRule.dump(full);\n    }\n\n    //发牌\n    sendCards() {\n\n    }\n\n    //出牌\n    outCards() {\n\n    }\n\n    //过牌\n    passCards() {\n\n    }\n\n\n    //叫地主\n    qiangDiZhu() {\n\n    }\n\n}\n\nlet ddz = new DouDiZhu();\nddz.initGame();\n"
  },
  {
    "path": "game_svr/register-center.js",
    "content": "\nconst net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst packet = require('../protocol/packet');\nconst cmdDefine = require('../protocol/cmd-define');\nconst constDefine = require('../lib/const-define');\nconst shortID = require('./short-ID');\nconst logger = require('./logger');\n\nlet conn = null;\n\nfunction registerSelf() {\n\n    let port = sysConfig.centerSvrPort;\n    let host = sysConfig.centerSvrHost;\n\n    console.log(\"connec to host:%s,port:%d\", host, port);\n    conn = net.createConnection({port:port,host:host}, function () {\n        const psudoID = shortID.getNextID();\n        conn.psudoID = psudoID;\n        conn.ip = host;\n\n        let jObj = packet.getPacket(cmdDefine.CENTER, cmdDefine.SUB_CENTER_UPDATE);\n\n        let serverInfo = {};\n        serverInfo.type = constDefine.SERVER_TYPE_GAME;\n        serverInfo.port = sysConfig.svrPort;\n        serverInfo.name = \"game_svr\";\n\n        jObj.body = serverInfo;\n\n        let json = JSON.stringify(jObj);\n        console.log(json);\n        conn.write(json);\n\n        conn.destroy();\n    });\n\n    conn.on('end', function () {\n\n        logger.info(\"server connection closed,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n    });\n\n    conn.on('error', function (err) {\n\n        logger.info(\"server connection error,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n        logger.error(err);\n    });\n\n    conn.on('data', function (data) {\n        console.log(\"server fd:%d get data:%s\", conn.psudoID, data);\n    });\n\n}\n\n\nexports.registerSelf = registerSelf;"
  },
  {
    "path": "game_svr/room-list.js",
    "content": "\n//\n// 房间号生成规则：游戏ID+6个数字，6个数字随机生成，每次服务重启生成的数字都不一样\n// 避免被猜到房间号，每个房间号带一个4位随机码，获取房间的时候动态生成\n//\n\n//\n// [1,2,3,4,5] 方便随机数获取\n// {1:\"room1\",2:\"room2\"....}\n//\n\nroomIDList = [];\nroomList = {};\n\nconst Room = require('./room');\n\n\n//初始化房间列表\nfunction initRoomList(gid,maxRoomCount) {\n\n    for (let i=0;i<maxRoomCount;i++) {\n        roomIDList.push(Math.ceil(Math.random()*100000));\n    }\n\n    //随机数有可能有重复，无所谓了\n\n    for(v in roomIDList) {\n        let r = new Room();\n        r.roomID = v;\n        r.gid = gid;\n        roomList[v] = r;\n    }\n\n}\n\n//获取一个可用的房间\nfunction getRoom() {\n\n    let max = roomIDList.length;\n    let pos = Math.ceil(max * Math.random());\n\n    //从0开始\n    let k = pos-1;\n\n    //fuck this api\n    roomList.splice(k, 1);\n\n    let r = roomList[k];\n    return r;\n}\n\n//销毁房间\nfunction destroyRoom(roomId) {\n\n    roomIDList.push(roomId);\n\n    let r = new Room();\n    r.roomID = roomId;\n    r.gid = gid;\n    roomList[roomId] = r;\n}\n\nexports.initRoomList = initRoomList;\nexports.getRoom = getRoom;\n\n"
  },
  {
    "path": "game_svr/room.js",
    "content": "\n// room - desk\n\n//房间列表暂时不存数据库了\n\nconst desk = require('./desk');\n\n//创建一个房间，实际是从已有房间里取一个\nfunction createRoom(socket, packet) {\n\n}\n\n//进入房间。可以游戏，可以围观\nfunction enterRoom(socket, packet) {\n\n}\n\n//离开房间\nfunction leaveRoom(socket, packet) {\n\n}\n\n//解散房间，将房间归还到房间列表\nfunction dismissRoom(socket, packet) {\n\n}\n\n//这个掉线重入，是不是要这么设计还在取舍中\nfunction reenterRoom(socket, packet) {\n\n}\n\n//进房间后，要查询玩家列表\nfunction queryPlayInfo(socket, packet) {\n\n}\n\n//坐桌子\nfunction playerSitDown(socket, packet) {\n\n}\n\n//准备游戏，准备人数满员就开始游戏\nfunction playerReady(socket, packet) {\n\n}\n\n//离开桌子\nfunction playerStandUp(socket, packet) {\n\n}\n\n//围观，主要是广播\nfunction playerWatch(socket, packet) {\n\n}\n\n//游戏中\nfunction playerOpeCard(socket, packet) {\n    desk.playerOpeCard(socket, packet);\n}\n\n//赢了\nfunction playerWin(socket, packet) {\n\n}\n\n//房主踢人，服务器踢+客户端通知\nfunction kickPlayer(socket, packet) {\n\n}\n\nexports.createRoom      = createRoom;\nexports.enterRoom       = enterRoom;\nexports.dismissRoom     = dismissRoom;\nexports.reenterRoom     = reenterRoom;\nexports.leaveRoom       = leaveRoom;\nexports.queryPlayerInfo = queryPlayInfo;\nexports.playerSitDown   = playerSitDown;\nexports.playerReady     = playerReady;\nexports.playerStandUp   = playerStandUp;\nexports.playerWatch     = playerWatch;\nexports.playerOpeCard   = playerOpeCard;\nexports.playerWin       = playerWin;\nexports.kickPlayer      = kickPlayer;\n"
  },
  {
    "path": "gate_svr/README.md",
    "content": "\ngate服务的设计原则\n\ngate服务按固定时间间隔上报状态到center服务器\ngate服务按固定时间间隔获取所有类型服务器列表（列表不能过超过1M大小，超过了redis会有卡顿现象）\ngate服务器在玩家登陆请求时转发请求到登陆服务器\n登录成功后，gate服务器在收到客户端连接游戏服务器请求时，在指定的游戏服务器建立连接，打通客户端和游戏服务器\ngate服务器在玩家游戏时，将请求转发到固定的游戏服务器，带上一个玩家序号，这个序号可以是固定的玩家ID，没有玩家ID可以是一个伪ID，类似于socket fd这样的东西。（伪ID在被攻击时会产生严重的问题，就是ID耗尽溢出，回绕的时候怎么和正常ID不冲突，这个问题现在是没处理的）\n游戏服务器回包以后，gate服务器通过包内的玩家序号找到对应的玩家（注意不是请求序号，如果是请求序号一个玩家可能对应多个请求）\n\n为了解耦模块间的关联，gate服务器对消息的处理应该统一化，尽量不要有特殊处理。或者说是做成插件化，特殊的业务都在插件中完成"
  },
  {
    "path": "gate_svr/client-handler.js",
    "content": "\n\n//\n//\n// 客户端请求进来时的处理\n//\n//\n\nconst logger = require('./logger');\nconst connections = require('./connections');\nconst cmdDefine = require('../protocol/cmd-define');\n\nfunction onConnection(fd,ws) {\n    logger.info(\"client connected,fd:%d,ip:%s\", fd, ws.ip);\n    connections.addClientConn(fd,ws);\n}\n\nfunction onMessage(ws,msg) {\n\n    logger.info(\"client read data,fd:%d,ip:%s\", ws.psudoID, ws.ip);\n    connections.updateClient(ws);\n\n    var jObj = JSON.parse(msg);\n    var mainCmd = jObj.head.mcmd;\n    var subCmd = jObj.head.scmd;\n\n    switch (mainCmd) {\n        case cmdDefine.HEART_BEAT:\n            ws.send(msg);\n            break;\n        case cmdDefine.LOGIN:\n\n        default:\n            logger.error(\"client fd:%d send unkown cmd:%d\", ws.psudoID, mainCmd);\n    }\n}\n\nfunction onError(ws,err) {\n    logger.error(\"client error,fd:%d,ip:%s\", fd, ws.ip);\n    logger.error(err);\n    const fd = ws.psudoID;\n    connections.removeClientConn(fd);\n}\n\nfunction onClose(ws) {\n    logger.info(\"client close,fd:%d,ip:%s\", fd, ws.ip);\n\n    const fd = ws.psudoID;\n    connections.removeClientConn(fd);\n}"
  },
  {
    "path": "gate_svr/config/log4js.json",
    "content": "{\n  \"appenders\": {\n    \"logfile\": {\n      \"type\": \"file\",\n      \"filename\": \"log/gate.log\",\n      \"maxLogSize\": 10485760,\n      \"numBackups\": 3\n    },\n    \"console\": {\n      \"type\": \"console\",\n      \"level\": \"debug\"\n    }\n  },\n  \"categories\": {\n    \"default\": {\n      \"appenders\": [\n        \"console\",\n        \"logfile\"\n      ],\n      \"level\": \"debug\"\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "gate_svr/config/sys-config.json",
    "content": "\n{\n  \"svrHost\":\"127.0.0.1\",\n  \"svrPort\":9000,\n  \"redisHost\":\"127.0.0.1\",\n  \"redisPort\":6379,\n  \"redisPassword\":\"\",\n  \"centerSvrHost\":\"127.0.0.1\",\n  \"centerSvrPort\":9200\n}"
  },
  {
    "path": "gate_svr/connections.js",
    "content": "\nvar clientConns = {};\nvar svrConns    = {};\nvar mapConns    = {};\n\nfunction addClientConn(fd,conn) {\n    clientConns[fd] = {conn:conn,updateTime:new Date().getTime()};\n}\n\nfunction updateClientConn(conn) {\n    conn.updateTime = new Date().getTime();\n}\n\nfunction addSvrConn(fd, conn) {\n    svrConns[fd] = {conn:conn,updateTime:new Date().getTime()};\n}\n\nfunction addMapConn(fdClient, fdSvr) {\n    mapConns[fdClient]  = fdSvr;\n    mapConns[fdSvr]     = fdClient;\n}\n\nfunction updateSvrConn(conn) {\n    conn.updateTime = new Date().getTime();\n}\n\nfunction getClientConn(fd) {\n    if (fd in clientConns) {\n        return clientConns[fd].conn;\n    }\n    return null;\n}\n\nfunction getSvrConn(fd) {\n    if (fd in svrConns) {\n        return svrConns[fd].conn;\n    }\n    return null;\n}\n\nfunction getMapConn(fd) {\n    if (fd in mapConns) {\n        return mapConns[fd];\n    }\n    return 0;\n}\n\nfunction removeSvrConn(fd) {\n    const clientFd = getMapConn(fd);\n    if (!!clientFd) {\n        delete mapConns[clientFd];\n        delete mapConns[fd];\n    }\n    if (fd in svrConns) {\n        delete svrConns[fd];\n    }\n    if (clientFd in clientConns) {\n        const conn = clientConns[clientFd];\n        if (!!conn) conn.close();\n        delete clientConns[clientFd];\n    }\n}\n\nfunction removeClientConn(fd) {\n    const svrFd = getMapConn(fd);\n    if (!!svrFd) {\n        delete mapConns[svrFd];\n        delete mapConns[fd];\n    }\n    if (fd in clientConns) {\n        delete clientConns[fd];\n    }\n    if (svrFd in svrConns) {\n        const conn = [svrFd];\n        if (!!conn) conn.destroy();\n        delete svrConns[svrFd];\n    }\n}\n\nfunction checkTimeout() {\n\n    var outTimeFd = [];\n    const now = new Date().getTime();\n    for (k in clientConns) {\n        if ((now - clientConns[k].updateTime) > 60*1000) {\n            outTimeFd.push(k);\n        }\n    }\n\n    for (var i=0; i<outTimeFd.length;i++ ) {\n        logger.error(\"client fd:%d timeout\", outTimeFd[i]);\n        const fd = outTimeFd[i];\n        if (fd in clientConns) {\n            delete clientConns[fd];\n            const svrFd = mapConns[fd];\n            if (!!svrFd) {\n                if (svrFd in svrConns) {\n                    svrConns[svrFd].conn.destroy();\n                    delete svrConns[svrFd];\n                }\n            }\n        }\n    }\n\n    outTimeFd = []\n    for (k in svrConns) {\n        if((now - svrConns[k].updateTime) > 60*1000) {\n            outTimeFd.push(k);\n        }\n    }\n\n    for (var i=0; i<outTimeFd.length;i++ ) {\n        logger.error(\"server fd:%d timeout\", outTimeFd[i]);\n        const fd = outTimeFd[i];\n        if (fd in clientConns) {\n            svrConns[fd].conn.destroy();\n            delete svrConns[fd];\n            const clientFd = mapConns[fd];\n            if (!!clientFd) {\n                if (clientFd in clientConns) {\n                    clientConns[svrFd].conn.close();\n                    delete clientConns[svrFd];\n                }\n            }\n        }\n    }\n\n\n}\n\nsetInterval(checkTimeout, 10*1000);\n\nexports.addClientConn   = addClientConn;\nexports.updateClientConn = updateClientConn;\nexports.addSvrConn      = addSvrConn;\nexports.updateSvrConn   = updateSvrConn;\nexports.getClientConn   = getClientConn;\nexports.getSvrConn      = getSvrConn;\nexports.addMapConn      = addMapConn;\nexports.getMapConn      = getMapConn;\nexports.removeClientConn = removeClientConn;\nexports.removeSvrConn   = removeSvrConn;"
  },
  {
    "path": "gate_svr/gate.js",
    "content": "\nconst wsServer = require('./ws-server');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\nconst registerCenter = require('./register-center');\nconst sysCache = require('./sys-cache');\n\nlogger.debug(sysConfig);\n\nsysCache.readCached();\nsetInterval(registerCenter.registerSelf, 5*1000);\n\nwsServer.startServer(sysConfig);"
  },
  {
    "path": "gate_svr/logger.js",
    "content": "\nconst config = require('./config/log4js.json');\n\nconst log4js = require('log4js');\nlog4js.configure(config);\n\nmodule.exports = log4js.getLogger();"
  },
  {
    "path": "gate_svr/redis-oper.js",
    "content": "\nconst sysConfig = require('./config/sys-config.json');\nconst redis = require('redis');\nconst redisKey = require('../lib/rds-key');\n\nvar host = sysConfig.redisHost;\nvar port = sysConfig.redisPort;\nvar password = sysConfig.redisPassword;\n\nfunction readCachedServers(cb) {\n    var rds = null;\n    if (!!password && password.length > 0) {\n        rds = redis.createClient({host:host,port:port,password:password});\n    } else {\n        rds = redis.createClient({host:host,port:port});\n    }\n\n    for (let v of redisKey.SERVER_TYPE_LIST) {\n        let key = redisKey.KEY_SERVER_TYPE + v;\n        rds.get(key, function (error,reply) {\n            cb (v, reply);\n        });\n    }\n}\n\nexports.readCachedServers = readCachedServers;"
  },
  {
    "path": "gate_svr/register-center.js",
    "content": "\nconst net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst packet = require('../protocol/packet');\nconst cmdDefine = require('../protocol/cmd-define');\nconst constDefine = require('../lib/const-define');\nconst shortID = require('./short-ID');\nconst logger = require('./logger');\n\nlet conn = null;\n\nfunction registerSelf() {\n\n    let port = sysConfig.centerSvrPort;\n    let host = sysConfig.centerSvrHost;\n\n    console.log(\"connec to host:%s,port:%d\", host, port);\n    conn = net.createConnection({port:port,host:host}, function () {\n        const psudoID = shortID.getNextID();\n        conn.psudoID = psudoID;\n        conn.ip = host;\n\n        let jObj = packet.getPacket(cmdDefine.CENTER, cmdDefine.SUB_CENTER_UPDATE);\n\n        let serverInfo = {};\n        serverInfo.type = constDefine.SERVER_TYPE_GATE;\n        serverInfo.port = sysConfig.svrPort;\n        serverInfo.name = \"gate_svr\";\n\n        jObj.body = serverInfo;\n\n        let json = JSON.stringify(jObj);\n        console.log(json);\n        conn.write(json);\n\n        conn.destroy();\n    });\n\n    conn.on('end', function () {\n\n        logger.info(\"server connection closed,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n    });\n\n    conn.on('error', function (err) {\n\n        logger.info(\"server connection error,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n        logger.error(err);\n    });\n\n    conn.on('data', function (data) {\n        console.log(\"server fd:%d get data:%s\", conn.psudoID, data);\n    });\n\n}\n\n\nexports.registerSelf = registerSelf;"
  },
  {
    "path": "gate_svr/server-handler.js",
    "content": "\n//\n//\n// 服务器返回数据时的处理\n//\n//\n\nconst logger = require('./logger');\nconst connections = require('./connections');\n\nfunction onConnection(fd,tcpConn) {\n    logger.info(\"client connected,fd:%d,ip:%s\", fd, tcpConn.ip);\n    connections.addSvrConn(fd,tcpConn);\n}\n\nfunction onMessage(tcpConn,msg) {\n    logger.info(\"client read data,fd:%d,ip:%s\", fd, tcpConn.ip);\n    connections.updateSvrConn(tcpConn);\n\n    var jObj = JSON.parse(msg);\n}\n\nfunction onError(tcpConn,err) {\n    logger.error(\"client error,fd:%d,ip:%s\", fd, tcpConn.ip);\n    logger.error(err);\n    const fd = ws.psudoID;\n    connections.removeSvrConn(fd);\n}\n\nfunction onClose(tcpConn) {\n    logger.info(\"client close,fd:%d,ip:%s\", fd, tcpConn.ip);\n\n    const fd = tcpConn.psudoID;\n    connections.removeSvrConn(fd);\n}"
  },
  {
    "path": "gate_svr/short-ID.js",
    "content": "\nvar idx = 1024;\n\n function getNextID() {\n    if(idx > 4200000000) {\n        idx = 1024;\n    }\n    idx++;\n    return idx;\n}\n\n\nexports.getNextID = getNextID;\n"
  },
  {
    "path": "gate_svr/sys-cache.js",
    "content": "\nconst redisOper = require('./redis-oper');\n\nvar cached = {};\n\nfunction saveServerInfo(type,serverList) {\n    let jArray = JSON.parse(serverList);\n    cached[type] = jArray;\n}\n\n// read servers info from redis\nfunction readCached() {\n\n    redisOper.readCachedServers(saveServerInfo);\n}\n\nfunction getServerList(type) {\n\n    return cached[type];\n}\n\nsetInterval(readCached, 60*1000);\n\nexports.getServerList = getServerList;\nexports.readCached = readCached;"
  },
  {
    "path": "gate_svr/tcp-client.js",
    "content": "\n\nconst net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst serverHandler = require('./server-handler');\nconst logger = require('./logger');\nconst shortID = require('./short-ID');\n\nfunction getNewConnection(port,host) {\n\n    if (!port) port = sysConfig.centerSvrPort;\n    if (!host) host = sysConfig.centerSvrHost;\n\n    var conn = net.createConnection({port:port,host:host}, function () {\n        const psudoID = shortID.getNextID();\n        conn.psudoID = psudoID;\n        conn.ip = host;\n\n        logger.info(\"connect to server,ip:%s,port:%d\", host, port);\n        serverHandler.onConnect(psudoID, conn);\n    });\n\n    conn.on('end', function () {\n\n        logger.info(\"server connection closed,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n        serverHandler.onClose(conn);\n    });\n\n    conn.on('error', function (err) {\n\n        logger.info(\"server connection error,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n        logger.error(conn,err);\n        serverHandler.onError(conn);\n    });\n\n    conn.on('data', function (data) {\n        console.log(\"server fd:%d get data:%s\", conn.psudoID, data);\n        serverHandler.onData(data);\n    });\n\n};\n\nexports.getNewConnection = getNewConnection;"
  },
  {
    "path": "gate_svr/ws-server.js",
    "content": "\n\nconst WebSocket = require('ws');\nconst clientHandler = require('./client-handler');\nconst shortID = require('./short-ID');\n\n\nfunction startServer(sysConfig) {\n\n    const host = sysConfig.svrHost;\n    const port = sysConfig.svrPort;\n    const wss = new WebSocket.Server({host:host,port:port});\n\n    wss.on('connection', function (ws) {\n\n        const psudoID = shortID.getNextID();\n        const ip = req.connection.remoteAddress;\n        ws.psudoID = psudoID;\n        ws.ip = ip;\n\n        clientHandler.onConnection(ws);\n\n\n        ws.on('message', function (msg) {\n            clientHandler.onMessage(ws, msg);\n        });\n\n        ws.on('error', function (err) {\n            clientHandler.onError(ws,err);\n        });\n\n        ws.on('close', function () {\n            clientHandler.onClose(ws);\n        });\n    });\n}\n\nexports.startServer = startServer;\n"
  },
  {
    "path": "hall_svr/config/log4js.json",
    "content": "{\n  \"appenders\": {\n    \"logfile\": {\n      \"type\": \"file\",\n      \"filename\": \"log/hall.log\",\n      \"maxLogSize\": 10485760,\n      \"numBackups\": 3\n    },\n    \"console\": {\n      \"type\": \"console\",\n      \"level\": \"debug\"\n    }\n  },\n  \"categories\": {\n    \"default\": {\n      \"appenders\": [\n        \"console\",\n        \"logfile\"\n      ],\n      \"level\": \"debug\"\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "hall_svr/config/sys-config.json",
    "content": "\n{\n  \"svrHost\":\"127.0.0.1\",\n  \"svrPort\":9300,\n  \"centerSvrHost\":\"127.0.0.1\",\n  \"centerSvrPort\":9200,\n  \"redisHost\":\"127.0.0.1\",\n  \"redisPort\":6379,\n  \"redisPassword\":\"\",\n  \"mysqlHost\":\"127.0.0.1\",\n  \"mysqlPort\":3306,\n  \"mysqlUsername\":\"root\",\n  \"mysqlPassword\":\"123456\"\n}"
  },
  {
    "path": "hall_svr/db-oper.js",
    "content": "const mysql = require('mysql');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\n\n\nvar pool = null;\n\nfunction getPool() {\n    pool = mysql.createPool(\n        {\n            connectionLimit: 50,\n            host: sysConfig.mysqlHost,\n            port: sysConfig.mysqlPort,\n            user: sysConfig.mysqlUsername,\n            password: sysConfig.mysqlPassword,\n            database: 'gamedb'\n        }\n    );\n}\n\ngetPool();\n\nfunction getGameList() {\n    return new Promise(function (resolve, reject) {\n        try {\n            //avoid sql injection\n            let sql = 'select * from game_list';\n            console.log(sql);\n\n            pool.getConnection(function (err, conn) {\n                conn.query(sql, function (err, results, fields) {\n\n                    conn.release();\n                    if (err) {\n                        throw  err;\n                    }\n\n                    resolve(results);\n                });\n            });\n\n        } catch (ex) {\n            logger.error(ex);\n            reject(ex);\n        }\n    });\n\n}\n\nfunction getGameServerList(gameID) {\n\n    return new Promise(function (resolve, reject) {\n        try {\n            let sql = \"select * from game_server_list where gid=\" + mysql.escape(gameID);\n            console.log(sql);\n\n            pool.getConnection(function (err, conn) {\n                conn.query(sql, function (err, results, fields) {\n\n                    conn.release();\n                    if (err) {\n                        throw  err;\n                    }\n\n                    resolve(results);\n                });\n            });\n\n        } catch (ex) {\n            logger.error(ex);\n            reject(ex);\n        }\n    });\n\n}\n\nexports.getGameList = getGameList;\nexports.getGameServerList = getGameServerList;\n\n"
  },
  {
    "path": "hall_svr/hall-handler.js",
    "content": "\nconst cmdDefine = require('../protocol/cmd-define');\nconst dbOper = require('./db-oper');\nconst packet = require('../protocol/packet');\nconst logger = require('./logger');\n\nasync function process(socket,data) {\n\n    let jObj = JSON.parse(data);\n    console.log(jObj);\n\n    let mainCmd = jObj.head.mcmd;\n    let subCmd = jObj.head.scmd;\n\n    switch (subCmd) {\n        case cmdDefine.SUB_HALL_GAME_LIST:\n            let gameList = await dbOper.getGameList();\n            let retObj = packet.getPacket(mainCmd, subCmd+100);\n            retObj.body = gameList;\n            let json = JSON.stringify(retObj);\n            socket.write(json);\n            break;\n        default:\n            logger.error(\"unsupport mainCmd:%d,subCmd:%d\", mainCmd,subCmd);\n            break\n    }\n\n}\n\n\nexports.process = process;"
  },
  {
    "path": "hall_svr/hall-server.js",
    "content": "const net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\nconst process = require('process');\nconst hallHandler = require('./hall-handler');\n\nconst server = net.createServer();\n\nfunction startServer() {\n\n    server.on('listening', function () {\n        logger.info(\"server is listening on port:%d\", sysConfig.svrPort);\n    });\n\n    server.on('connection', function (socket) {\n\n        socket.setTimeout(60 * 1000, function () {\n            logger.error(\"ip:%s,idle timeout, disconncting, bye\", socket.remoteAddress);\n            socket.end('idle timeout, disconnecting, bye!');\n            socket.destroy();\n        });\n\n        socket.on('data', function (data) {\n            logger.info(\"ip:%s, get data\", socket.remoteAddress);\n            console.debug(data);\n            hallHandler.process(socket, data);\n        });\n\n        socket.on('error', function (err) {\n            logger.info(\"ip:%s,error\", socket.remoteAddress);\n            logger.error(err);\n            socket.destroy();\n        });\n\n        socket.on('close', function (had_error) {\n            logger.info(\"ip:%s,socket closed\", socket.remoteAddress);\n            if (!socket.destroyed) {\n                logger.info(\"destroy socket\");\n                socket.destroy();\n            }\n        })\n    });\n\n    server.on('error', (err) => {\n        logger.error(err);\n        process.exit(1);\n    });\n\n    server.listen(sysConfig.svrPort, sysConfig.svrHost, () => {\n        logger.info('server bound on host:%s,port:%d', sysConfig.svrHost, sysConfig.svrPort);\n    });\n}\n\nexports.startServer = startServer;"
  },
  {
    "path": "hall_svr/hall.js",
    "content": "\n// 大厅提供以下功能：\n//      游戏列表（游戏类型，游戏种类)\n//      服务器列表\n//      在线人数等其它信息\n\n\nconst hallServer = require('./hall-server');\nconst registerCenter = require('./register-center');\n\nsetInterval(registerCenter.registerSelf, 5*1000);\n\nhallServer.startServer();\n"
  },
  {
    "path": "hall_svr/logger.js",
    "content": "\nconst config = require('./config/log4js.json');\n\nconst log4js = require('log4js');\nlog4js.configure(config);\n\nmodule.exports = log4js.getLogger();"
  },
  {
    "path": "hall_svr/register-center.js",
    "content": "\nconst net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst packet = require('../protocol/packet');\nconst cmdDefine = require('../protocol/cmd-define');\nconst constDefine = require('../lib/const-define');\nconst shortID = require('./short-ID');\nconst logger = require('./logger');\n\nlet conn = null;\n\nfunction registerSelf() {\n\n    let port = sysConfig.centerSvrPort;\n    let host = sysConfig.centerSvrHost;\n\n    console.log(\"connec to host:%s,port:%d\", host, port);\n    conn = net.createConnection({port:port,host:host}, function () {\n        const psudoID = shortID.getNextID();\n        conn.psudoID = psudoID;\n        conn.ip = host;\n\n        let jObj = packet.getPacket(cmdDefine.CENTER, cmdDefine.SUB_CENTER_UPDATE);\n\n        let serverInfo = {};\n        serverInfo.type = constDefine.SERVER_TYPE_HALL;\n        serverInfo.port = sysConfig.svrPort;\n        serverInfo.name = \"login_svr\";\n\n        jObj.body = serverInfo;\n\n        let json = JSON.stringify(jObj);\n        console.log(json);\n        conn.write(json);\n\n        conn.destroy();\n    });\n\n    conn.on('end', function () {\n\n        logger.info(\"server connection closed,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n    });\n\n    conn.on('error', function (err) {\n\n        logger.info(\"server connection error,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n        logger.error(err);\n    });\n\n    conn.on('data', function (data) {\n        console.log(\"server fd:%d get data:%s\", conn.psudoID, data);\n    });\n\n}\n\n\nexports.registerSelf = registerSelf;"
  },
  {
    "path": "hall_svr/short-ID.js",
    "content": "\nvar idx = 1024;\n\n function getNextID() {\n    if(idx > 4200000000) {\n        idx = 1024;\n    }\n    idx++;\n    return idx;\n}\n\n\nexports.getNextID = getNextID;\n"
  },
  {
    "path": "lib/black-ip.js",
    "content": "\n//ip黑名单"
  },
  {
    "path": "lib/const-define.js",
    "content": "\nmodule.exports = {\n    SERVER_TYPE_LOGIN   :       2,\n    SERVER_TYPE_CENTER  :       1,\n    SERVER_TYPE_GATE    :       3,\n    SERVER_TYPE_HALL    :       4,\n    SERVER_TYPE_GAME    :       5\n}"
  },
  {
    "path": "lib/rds-key.js",
    "content": "\nmodule.exports = {\n\n    KEY_SERVER_TYPE     :       \"server_type\",\n    SERVER_TYPE_LIST    :       [1,2,3],\n\n}"
  },
  {
    "path": "login_svr/config/log4js.json",
    "content": "{\n  \"appenders\": {\n    \"logfile\": {\n      \"type\": \"file\",\n      \"filename\": \"log/login.log\",\n      \"maxLogSize\": 10485760,\n      \"numBackups\": 3\n    },\n    \"console\": {\n      \"type\": \"console\",\n      \"level\": \"debug\"\n    }\n  },\n  \"categories\": {\n    \"default\": {\n      \"appenders\": [\n        \"console\",\n        \"logfile\"\n      ],\n      \"level\": \"debug\"\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "login_svr/config/sys-config.json",
    "content": "\n{\n  \"svrHost\":\"127.0.0.1\",\n  \"svrPort\":9100,\n  \"centerSvrHost\":\"127.0.0.1\",\n  \"centerSvrPort\":9200,\n  \"redisHost\":\"127.0.0.1\",\n  \"redisPort\":6379,\n  \"redisPassword\":\"\",\n  \"mysqlHost\":\"127.0.0.1\",\n  \"mysqlPort\":3306,\n  \"mysqlUsername\":\"root\",\n  \"mysqlPassword\":\"123456\"\n}"
  },
  {
    "path": "login_svr/db-oper.js",
    "content": "const mysql = require('mysql');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\n\n\nvar pool = null;\n\nfunction getPool() {\n    pool = mysql.createPool(\n        {\n            connectionLimit: 50,  //TODO config in file\n            host: sysConfig.mysqlHost,\n            port: sysConfig.mysqlPort,\n            user: sysConfig.mysqlUsername,\n            password: sysConfig.mysqlPassword,\n            database: 'userdb'\n        }\n    );\n}\n\ngetPool();\n\nfunction login(userInfo) {\n    return new Promise(function (resolve, reject) {\n        try {\n            //avoid sql injection\n            let sql = 'select * from user_base where account=' + mysql.escape(userInfo.account) +\n                ' and password=' + mysql.escape(userInfo.password);\n            console.log(sql);\n\n            pool.getConnection(function (err, conn) {\n                conn.query(sql, function (err, results, fields) {\n\n                    conn.release();\n                    if (err) {\n                        throw  err;\n                    }\n\n                    resolve(results);\n                });\n            });\n\n        } catch (ex) {\n            logger.error(ex);\n            reject(ex);\n        }\n    });\n\n}\n\nfunction register(userInfo) {\n\n    return new Promise(function (resolve, reject) {\n        try {\n            let sql = 'insert into user_base(accuounts, password) values(' +\n                mysq.escape(userInfo.accounts) + ',' + mysq.escape(userInfo.password) + ')';\n\n            console.log(sql);\n\n            pool.getConnection(function (err, conn) {\n                conn.query(sql, function (err, results, fields) {\n                    conn.release();\n                    if(err) {\n                        throw  ex;\n                    }\n\n                    resolve(results);\n                })\n            })\n        } catch (ex) {\n            logger.error(ex);\n            reject(ex);\n        }\n    });\n\n}\n\nexports.login = login;\nexports.register = register;\n\n"
  },
  {
    "path": "login_svr/logger.js",
    "content": "\nconst config = require('./config/log4js.json');\n\nconst log4js = require('log4js');\nlog4js.configure(config);\n\nmodule.exports = log4js.getLogger();"
  },
  {
    "path": "login_svr/login-handler.js",
    "content": "const cmdDefine = require('../protocol/cmd-define');\nconst dbOper = require('./db-oper');\nconst packet = require('../protocol/packet');\n\nasync function process(socket, jObj) {\n\n    const mainCmd = jObj.head.mcmd;\n    const subCmd = jObj.head.scmd;\n\n    switch (subCmd) {\n        case cmdDefine.SUB_LOGIN_ACCOUNT:\n            let data = await dbOper.login(userInfo);\n\n            if (data.length > 0) {\n                data.token = \"just for test\";\n\n                var retObj = packet.getPacket(mainCmd,subCmd+100);\n                retObj.body = data;\n                socket.write(JSON.stringify(retObj));\n            }\n\n            break;\n        case cmdDefine.SUB_LOGIN_PHONE:\n            break;\n        case cmdDefine.SUB_LOGIN_VISITOR:\n            break;\n    }\n}"
  },
  {
    "path": "login_svr/login.js",
    "content": "\nserver = require('./server');\nconst registerCenter = require('./register-center');\nconst logger = require('./logger');\nconst sysConfig = require('./config/sys-config.json');\n\nlogger.debug(sysConfig);\n\nsetInterval(registerCenter.registerSelf, 5*1000);\n\nserver.startServer();\n"
  },
  {
    "path": "login_svr/register-center.js",
    "content": "\nconst net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst packet = require('../protocol/packet');\nconst cmdDefine = require('../protocol/cmd-define');\nconst constDefine = require('../lib/const-define');\nconst shortID = require('./short-ID');\nconst logger = require('./logger');\n\nlet conn = null;\n\nfunction registerSelf() {\n\n    let port = sysConfig.centerSvrPort;\n    let host = sysConfig.centerSvrHost;\n\n    console.log(\"connec to host:%s,port:%d\", host, port);\n    conn = net.createConnection({port:port,host:host}, function () {\n        const psudoID = shortID.getNextID();\n        conn.psudoID = psudoID;\n        conn.ip = host;\n\n        let jObj = packet.getPacket(cmdDefine.CENTER, cmdDefine.SUB_CENTER_UPDATE);\n\n        let serverInfo = {};\n        serverInfo.type = constDefine.SERVER_TYPE_LOGIN;\n        serverInfo.port = sysConfig.svrPort;\n        serverInfo.name = \"login_svr\";\n\n        jObj.body = serverInfo;\n\n        let json = JSON.stringify(jObj);\n        console.log(json);\n        conn.write(json);\n\n        conn.destroy();\n    });\n\n    conn.on('end', function () {\n\n        logger.info(\"server connection closed,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n    });\n\n    conn.on('error', function (err) {\n\n        logger.info(\"server connection error,fd:%d,ip:%s\", conn.psudoID, conn.ip);\n        logger.error(err);\n    });\n\n    conn.on('data', function (data) {\n        console.log(\"server fd:%d get data:%s\", conn.psudoID, data);\n    });\n\n}\n\n\nexports.registerSelf = registerSelf;"
  },
  {
    "path": "login_svr/server.js",
    "content": "const net = require('net');\nconst sysConfig = require('./config/sys-config.json');\nconst logger = require('./logger');\nconst process = require('process');\nconst loginHandler = require('./login-handler');\n\nconst server = net.createServer();\n\nfunction startServer() {\n\n    server.on('listening', function () {\n        logger.info(\"server is listening on port:%d\", sysConfig.svrPort);\n    });\n\n    server.on('connection', function (socket) {\n\n        socket.setTimeout(60 * 1000, function () {\n            logger.error(\"ip:%s,idle timeout, disconncting, bye\", socket.remoteAddress);\n            socket.end('idle timeout, disconnecting, bye!');\n            socket.destroy();\n        });\n\n        socket.on('data', function (data) {\n            logger.info(\"ip:%s, get data\", socket.remoteAddress);\n            console.debug(data);\n            loginHandler.process(socket, data);\n        });\n\n        socket.on('error', function (err) {\n            logger.info(\"ip:%s,error\", socket.remoteAddress);\n            logger.error(err);\n            socket.destroy();\n        });\n\n        socket.on('close', function (had_error) {\n            logger.info(\"ip:%s,socket closed\", socket.remoteAddress);\n            if (!socket.destroyed) {\n                logger.info(\"destroy socket\");\n                socket.destroy();\n            }\n        })\n    });\n\n    server.on('error', (err) => {\n        logger.error(err);\n        process.exit(1);\n    });\n\n    server.listen(sysConfig.svrPort, sysConfig.svrHost, () => {\n        logger.info('server bound on host:%s,port:%d', sysConfig.svrHost, sysConfig.svrPort);\n    });\n}\n\nexports.startServer = startServer;"
  },
  {
    "path": "login_svr/short-ID.js",
    "content": "\nvar idx = 1024;\n\n function getNextID() {\n    if(idx > 4200000000) {\n        idx = 1024;\n    }\n    idx++;\n    return idx;\n}\n\n\nexports.getNextID = getNextID;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"gamex\",\n  \"version\": \"1.0.0\",\n  \"description\": \"gamex\",\n  \"main\": \"null.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"keywords\": [\n    \"gamex\"\n  ],\n  \"author\": \"shp\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"express\": \"^4.16.2\",\n    \"log4js\": \"^2.3.5\",\n    \"mysql\": \"^2.15.0\",\n    \"net\": \"^1.0.2\",\n    \"process\": \"^0.11.10\",\n    \"redis\": \"^2.8.0\",\n    \"redis-pool-connection\": \"^1.4.0\",\n    \"ws\": \"^3.2.0\"\n  }\n}\n"
  },
  {
    "path": "protocol/client-protocol.md",
    "content": "\n本文件包含客户端与服务端交互协议"
  },
  {
    "path": "protocol/cmd-define.js",
    "content": "\n/*\n\n1-1000 // framework\n\n1001-1200 // login server\n1201-1300 // center server\n1301-1400 // hall server\n3000-10000 // game server\n\n10000-20000 //client <-> server\n\n//packet:{\n//    \"head\":{\n//          \"mcmd\":1,\n//          \"scmd:\":1,\n//          \"remoteAddress\":\"127.0.0.1\",\n//          \"seqNo\":0\n//     },\n//   \"body\": {\n//        //as your wish\n//    }\n//}\n\ncenter server\n//\n// serverInfo:{\n//      name\n//      type\n//      ip\n//      port\n//      activeTime\n//      serverNo\n//      ext\n// }\n*/\n\nmodule.exports = {\n    HEART_BEAT:1,\n\n    LOGIN:1001,\n    SUB_LOGIN_ACCOUNT:1,            //帐号登录\n    SUB_LOGIN_PHONE:2,              //手机登录\n    SUB_LOGIN_VISITOR:3,            //游客登录\n\n    CENTER:1201,\n    SUB_CENTER_UPDATE:1,            //向中央服务器上报状态\n    SUB_CENTER_GET:2,               //从中央服务器获取对应的服务列表\n    SUB_CENTER_PLAYER_OL,           //玩家登陆,玩家退出游戏也是这个状态\n    SUB_CENTER_PLAYER_PL,           //玩家游戏中\n\n    HALL:1301,\n    SUB_HALL_GAME_LIST:1,           //游戏列表\n    SUB_GAME_SERVER_LIST:2,         //游戏服务列表\n\n    GAME:1401,\n    SUB_GAME_CREATE_ROOM:1,         //创建房间\n    SUB_GAME_ENTER_ROOM:2,          //进入房间\n    SUB_GAME_LEAVE_ROOM:3,          //退出房间\n    SUB_GAME_DISMISS_ROOM:4,        //解散房间\n    SUB_GAME_DESK_SIT_DOWN:5,       //坐下\n    SUB_GAME_DESK_READY:6,          //准备\n    SUB_GAME_DESK_STAND_UP:7,       //起立\n    SUB_GAME_DESK_OPE_CARD:8,       //操作牌\n    SUB_GAME_DESK_WIN:9,            //胡牌\n    SUB_GAME_REENTER_ROOM:10,       //断线重进房间\n    SUB_GAME_QUERY_PLAYER_INFO:11,         //查询玩家游戏状态\n    SUB_GAME_DESK_WATCH:12,          //观战\n    SUB_GAME_KICK_PLAYER:1,           //房主踢人\n\n    EVENT_OUT_CARD:1,\n    EVENT_PASS_CARD:2,\n};"
  },
  {
    "path": "protocol/packet.js",
    "content": "\nfunction Packet(mainCmd,subCmd) {\n\n    this.head = {};\n    this.head.mcmd = mainCmd;\n    this.head.scmd = subCmd;\n    this.body = {};\n}\n\nfunction getPacket(mainCmd,subCmd) {\n    return new Packet(mainCmd,subCmd);\n}\n\nexports.getPacket = getPacket;\n"
  },
  {
    "path": "protocol/server-server-protocol.md",
    "content": "\n本文件包含服务器内部协议定义"
  },
  {
    "path": "sql/gamedb.sql",
    "content": "\ncreate database if not exists gamedb default charset utf8;\n\nuse gamedb;\n\n-- 游戏表\n-- 游戏id手工维护\ncreate table if not exists game_list (\n\n    gid     int         not null default '0'    comment '游戏ID',\n    name    varchar(32) not null default ''     comment '游戏名称',\n    iconURL varchar(255) not null default ''    comment '游戏图标',\n    index(gid)\n) engine=innodb default charset 'utf8';\n\n-- 游戏服相关信息\n-- 注意游戏服ip和端口均为外网ip和端口\ncreate table if not exists game_server_list (\n\n    gid         int         not null default '0'    comment '游戏ID',\n    name        varchar(64) not null default ''     comment '游戏服名称',\n    ip          char(16)    not null default ''     comment '游戏服ip',\n    port        int         not null default '0'    comment '游戏服端口',\n    lowScore    bigint      not null default '0'    comment '允许最低分,为0表示不限制',\n    upScore     bigint      not null default '0'    comment '允许最高分,为0表示不限制',\n    status      int         not null default '0'    comment '1为正常，2为维护中，3为停服',\n    maxRoom     int         not null default '100'  comment '最大房间数',\n    createTime  timestamp   not null default  0     comment '创建时间',\n    modifyTime  timestamp   not null default  0     comment '修改时间',\n    index(gid,status)\n) engine=innodb default charset 'utf8';\n\n-- 每个用户在每个游戏中都有一份单独的数据，保证分数和金币数不乱\ncreate table if not exists user_game_info (\n\n    uid         int         not null default '0'    comment '用户id',\n    uname       varchar(64) not null default ''     comment '用户昵称',\n    gid         int         not null default '0'    comment '游戏ID',\n    score       bigint      not null default '0'    comment '用户得分',\n    coins       bigint      not null default '0'    comment '用户金币数',\n    gems        int         not null default '0'    comment '用户钻石数',\n    loginTime   timestamp   not null default  0     comment '登录时间',\n    logoutTime  timestamp   not null default  0     comment '退出时间',\n    index(uid,gid)\n) engine = innodb default charset 'utf8';\n\n\n"
  },
  {
    "path": "sql/userdb.sql",
    "content": "\ncreate database if not exists userdb default charset utf8;\n\nuse userdb;\n\n-- 用户表\n-- 用户只能单点登陆，单点登陆在redis中实现，不写数据库，这个表应该是读多写少的\ncreate table if not exists user_base (\n    uid         int         not null default 0  comment '用户ID',\n    account     varchar(64) not null default '' comment '用户帐号',\n    type        int         not null default 0  comment '用户类型[渠道],1原始,2游客,3微信',\n    name        varchar(64) not null default '' comment '用户名',\n    nickName    varchar(64) not null default '' comment '昵称',\n    password    char(32)    not null default '' comment '用户密码',\n    token       char(32)    not null default '' comment '用户动态token',\n    sex         int         not null default 0  comment '性别',\n    city        int         not null default 0  comment '城市',\n    province    int         not null default 0  comment '省份',\n    faceURL     char(255)   not null default '' comment '用户头像',\n    createTime  timestamp   not null default 0  comment '创建时间',\n    index(account,password)\n) engine=innodb default charset 'utf8';"
  }
]