├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── client.sh ├── client ├── simpleclient.lua ├── simplemessage.lua └── simplesocket.lua ├── config ├── cservice └── DUMMY ├── lualib ├── client.lua ├── ddz_rules.lua ├── log.lua ├── luahelper.lua ├── service.lua └── socket_proxy.lua ├── proto ├── proto.c2s.sproto └── proto.s2c.sproto ├── run.sh ├── run └── DUMMY ├── service ├── agent.lua ├── auth.lua ├── game.lua ├── hub.lua ├── main.lua ├── manager.lua ├── protoloader.lua ├── room.lua └── socket_proxyd.lua └── src └── service_package.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "skynet"] 2 | path = skynet 3 | url = https://github.com/cloudwu/skynet 4 | [submodule "lsocket"] 5 | path = lsocket 6 | url = https://github.com/cloudwu/lsocket 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SKYNET_PATH = skynet 2 | TARGET = cservice/package.so 3 | 4 | $(TARGET) : src/service_package.c 5 | gcc -Wall -O2 --shared -fPIC -dynamiclib -Wl,-undefined,dynamic_lookup -o $@ $^ -I$(SKYNET_PATH)/skynet-src 6 | 7 | socket : 8 | cd lsocket && $(MAKE) LUA_INCLUDE=../skynet/3rd/lua 9 | 10 | clean : 11 | rm $(TARGET) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddz_skynet 2 | simple implemetation of doudizhu(斗地主), based on [skynet](https://github.com/cloudwu/skynet) 3 | 4 | 这是在空闲时研究云风的skynet写的一个demo,实现一个C/S的斗地主的基本逻辑,接口包括: 5 | * 自动注册,已注册时自动登录 6 | * 自动创建房间 7 | * 进入房间或离开房间 8 | * 准备或取消准备游戏 9 | * 游戏开始时叫地主&抢地主的基本逻辑 10 | * 斗地主玩法的全部规则实现 11 | 12 | 大概是花了3天左右时间写的demo,功能比较不完善,主要的时间也花在了斗地主的规则上。而像复用game、room这几个skynet service,以及一些网络处理细节上还未来得及做,留待后续有时间处理吧。 13 | 14 | 使用sproto,具体协议见proto目录。 15 | 16 | ## 如何运行 17 | 进入目录后,服务端使用: 18 | ``` 19 | ./run.sh 20 | ``` 21 | 22 | 客户端使用: 23 | ``` 24 | ./client.sh 玩家姓名 25 | ``` 26 | 如果要在多台不同机器上运行,需要更改client.sh脚本,把127.0.0.1的IP地址改成服务器IP即可。 27 | 28 | ##如何编译 29 | clone 下本仓库。 30 | 更新 submodule ,服务器部分需要用到 skynet ;客户端部分需要用到 lsocket 。 31 | ``` 32 | git submodule update --init 33 | ``` 34 | 35 | 编译 skynet 36 | ``` 37 | cd skynet 38 | make linux 39 | ``` 40 | 41 | 编译 lsocket(如果你需要客户端) 42 | ``` 43 | make socket 44 | ``` 45 | 46 | 编译 skynet package 模块 47 | ``` 48 | make 49 | ``` 50 | -------------------------------------------------------------------------------- /client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export ROOT=$(cd `dirname $0`; pwd) 3 | 4 | $ROOT/skynet/3rd/lua/lua $ROOT/client/simpleclient.lua $ROOT "127.0.0.1" $1 5 | 6 | -------------------------------------------------------------------------------- /client/simpleclient.lua: -------------------------------------------------------------------------------- 1 | local PATH,IP,UID = ... 2 | 3 | IP = IP or "127.0.0.1" 4 | UID = UID or "alice" 5 | package.path = string.format("%s/lualib/?.lua;%s/client/?.lua;%s/skynet/lualib/?.lua", PATH, PATH, PATH) 6 | package.cpath = string.format("%s/skynet/luaclib/?.so;%s/lsocket/?.so", PATH, PATH) 7 | 8 | local socket = require "simplesocket" 9 | local message = require "simplemessage" 10 | local rules = require("ddz_rules") 11 | require("luahelper") 12 | 13 | message.register(string.format("%s/proto/%s", PATH, "proto")) 14 | 15 | message.peer(IP, 5678) 16 | message.connect() 17 | 18 | local event = {} 19 | local sitIndex = 0 20 | message.bind({}, event) 21 | 22 | local mycards 23 | function event:__error(what, err, req, session) 24 | print("error", what, err) 25 | end 26 | 27 | function event:ping() 28 | -- print("ping") 29 | end 30 | 31 | function event:signin(req, resp) 32 | print("signin", req.userid, resp.ok) 33 | if resp.ok then 34 | message.request "ping" -- should error before login 35 | message.request "login" 36 | else 37 | -- signin failed, signup 38 | message.request("signup", { userid = UID}) 39 | end 40 | end 41 | 42 | function event:signup(req, resp) 43 | print("signup", resp.ok) 44 | if resp.ok then 45 | message.request("signin", { userid = req.userid }) 46 | else 47 | error "Can't signup" 48 | end 49 | end 50 | 51 | function event:login(_, resp) 52 | print("login", resp.ok) 53 | if resp.ok then 54 | message.request "joinroom" 55 | else 56 | error "Can't login" 57 | end 58 | end 59 | 60 | function event:joinroom(_, resp) 61 | if resp.ok then 62 | sitIndex = resp.index 63 | print("successful join room, index=", sitIndex) 64 | for i=1,3 do 65 | print(resp.room["user"..i]) 66 | end 67 | 68 | message.request("oncommand", {cmd="ready",parameters="1"}) 69 | -- local cmd = io.read() 70 | -- if cmd then 71 | -- if cmd == "quit" then 72 | -- message.request("quit") 73 | -- elseif cmd == "ready" then 74 | -- message.request("oncommand", {cmd="ready",parameters="1"}) 75 | -- end 76 | -- end 77 | end 78 | end 79 | 80 | function event:heartbeat() 81 | message.request "ping" 82 | end 83 | 84 | function event:onuserready(args) 85 | print("~~~~onuserready", args.index, args.userid) 86 | end 87 | 88 | function event:onjoinroom(args) 89 | print("~~~~onjoinroom", args.index, args.userid) 90 | end 91 | 92 | function event:onleftroom(args) 93 | print("~~~~onleftroom", args.index, args.userid) 94 | end 95 | 96 | local function requestmaster() 97 | io.write("是否抢地主(0|1|2|3):") 98 | local cmd = io.read() 99 | if cmd then 100 | if cmd == "" then cmd = "0" end 101 | message.request("oncommand", {cmd="master",parameters=cmd}) 102 | end 103 | end 104 | 105 | local function dumpcards(cards) 106 | for i,v in pairs(mycards) do 107 | -- print(string.format("花色:%d,牌面:%s, 权重:%d", v.suit, v.display, v.grade)) 108 | io.write(v.display.."("..v.id..") ") 109 | -- io.write(v.id..",") 110 | end 111 | io.write("\n") 112 | end 113 | 114 | local termcards 115 | local function playcards() 116 | io.write("我的当前牌面:") 117 | dumpcards(mycards) 118 | io.write("请出牌(半角逗号分隔):") 119 | local cmd = io.read() 120 | pcall(function() 121 | if cmd then 122 | if cmd == "" then 123 | message.request("oncommand", {cmd="play",parameters=string.char(0)}) 124 | else 125 | local cards = cmd:split(",") 126 | termcards = cards 127 | message.request("oncommand", {cmd="play",parameters=string.char(table.unpack(cards))}) 128 | end 129 | 130 | end 131 | end, function(err) 132 | playcards() 133 | end) 134 | 135 | end 136 | 137 | function event:ongameready(args) 138 | local cards = {string.byte(args.cards, 1, -1)} 139 | 140 | mycards = rules.getsortedcarddetails(cards) 141 | io.write("发牌结束,初始牌面:") 142 | for i,v in ipairs(mycards) do 143 | -- print(string.format("花色:%d,牌面:%s, 权重:%d", v.suit, v.display, v.grade)) 144 | io.write(v.display.." ") 145 | end 146 | io.write("\n") 147 | 148 | if args.first == sitIndex then 149 | requestmaster() 150 | end 151 | end 152 | 153 | 154 | function event:ongamestart(args) 155 | local cards = {string.byte(args.excards, 1, -1)} 156 | print("游戏开始,地主是: "..args.master..",地主牌:") 157 | local s = rules.getsortedcarddetails(cards) 158 | for i,v in pairs(s) do 159 | -- print(string.format("花色:%d,牌面:%s, 权重:%d", v.suit, v.display, v.grade)) 160 | io.write(v.display.." ") 161 | end 162 | io.write("\n") 163 | 164 | if args.master == sitIndex then 165 | for i,v in pairs(s) do 166 | table.insert(mycards, v) 167 | end 168 | table.sort(mycards,function(a,b) return a.grade > b.grade end) 169 | playcards() 170 | end 171 | end 172 | function event:onrequestmaster(args) 173 | print("玩家"..args.index.."叫地主:"..args.rate.."分") 174 | if args.next == sitIndex then 175 | requestmaster() 176 | end 177 | end 178 | 179 | function event:onplay(args) 180 | local cards = {string.byte(args.cards, 1, -1)} 181 | if args.ok then 182 | if cards[1] == 0 then 183 | print("玩家"..args.index.."PASS!") 184 | else 185 | local s = rules.getsortedcarddetails(cards) 186 | io.write("玩家"..args.index.."出牌:") 187 | for i,v in pairs(s) do 188 | io.write(v.display.." ") 189 | end 190 | io.write("\n") 191 | 192 | if args.index == sitIndex then 193 | for i2,v2 in ipairs(termcards) do 194 | local idx = table.icontains(mycards, tonumber(v2), function(a,b) return a.id == b end) 195 | if idx then 196 | --从玩家牌堆里移除牌 197 | table.remove(mycards, idx) 198 | end 199 | end 200 | end 201 | end 202 | end 203 | 204 | 205 | if args.next == sitIndex then 206 | playcards() 207 | end 208 | 209 | end 210 | 211 | function event:ongameover(args) 212 | if args.farmerwin then 213 | print("农民胜利") 214 | else 215 | print("地主胜利") 216 | end 217 | end 218 | 219 | function event:push(args) 220 | print("server push", args.text) 221 | end 222 | 223 | message.request("signin", { userid = UID }) 224 | 225 | while true do 226 | message.update() 227 | 228 | end 229 | -------------------------------------------------------------------------------- /client/simplemessage.lua: -------------------------------------------------------------------------------- 1 | local socket = require "simplesocket" 2 | local sproto = require "sproto" 3 | 4 | local message = {} 5 | local var = { 6 | session_id = 0 , 7 | session = {}, 8 | object = {}, 9 | } 10 | 11 | function message.register(name) 12 | local f = assert(io.open(name .. ".s2c.sproto")) 13 | local t = f:read "a" 14 | f:close() 15 | var.host = sproto.parse(t):host "package" 16 | local f = assert(io.open(name .. ".c2s.sproto")) 17 | local t = f:read "a" 18 | f:close() 19 | var.request = var.host:attach(sproto.parse(t)) 20 | end 21 | 22 | function message.peer(addr, port) 23 | var.addr = addr 24 | var.port = port 25 | end 26 | 27 | function message.connect() 28 | socket.connect(var.addr, var.port) 29 | socket.isconnect() 30 | end 31 | 32 | function message.bind(obj, handler) 33 | var.object[obj] = handler 34 | end 35 | 36 | function message.request(name, args) 37 | var.session_id = var.session_id + 1 38 | var.session[var.session_id] = { name = name, req = args } 39 | socket.write(var.request(name , args, var.session_id)) 40 | return var.session_id 41 | end 42 | 43 | function message.update(ti) 44 | local msg = socket.read(ti) 45 | if not msg then 46 | return false 47 | end 48 | local t, session_id, resp, err = var.host:dispatch(msg) 49 | if t == "REQUEST" then 50 | for obj, handler in pairs(var.object) do 51 | local f = handler[session_id] -- session_id is request type 52 | if f then 53 | local ok, err_msg = pcall(f, obj, resp) -- resp is content of push 54 | if not ok then 55 | print(string.format("push %s for [%s] error : %s", session_id, tostring(obj), err_msg)) 56 | end 57 | end 58 | end 59 | else 60 | local session = var.session[session_id] 61 | var.session[session_id] = nil 62 | 63 | for obj, handler in pairs(var.object) do 64 | if err then 65 | local f = handler.__error 66 | if f then 67 | local ok, err_msg = pcall(f, obj, session.name, err, session.req, session_id) 68 | if not ok then 69 | print(string.format("session %s[%d] error(%s) for [%s] error : %s", session.name, session_id, err, tostring(obj), err_msg)) 70 | end 71 | end 72 | else 73 | local f = handler[session.name] 74 | if f then 75 | local ok, err_msg = pcall(f, obj, session.req, resp, session_id) 76 | if not ok then 77 | print(string.format("session %s[%d] for [%s] error : %s", session.name, session_id, tostring(obj), err_msg)) 78 | end 79 | end 80 | end 81 | end 82 | end 83 | 84 | return true 85 | end 86 | 87 | return message 88 | -------------------------------------------------------------------------------- /client/simplesocket.lua: -------------------------------------------------------------------------------- 1 | local lsocket = require "lsocket" 2 | 3 | local socket = {} 4 | local fd 5 | local message 6 | 7 | socket.error = setmetatable({}, { __tostring = function() return "[socket error]" end } ) 8 | 9 | function socket.connect(addr, port) 10 | assert(fd == nil) 11 | fd = lsocket.connect(addr, port) 12 | if fd == nil then 13 | error(socket.error) 14 | end 15 | 16 | lsocket.select(nil, {fd}) 17 | local ok, errmsg = fd:status() 18 | if not ok then 19 | error(socket.error) 20 | end 21 | 22 | message = "" 23 | end 24 | 25 | function socket.isconnect(ti) 26 | local rd, wt = lsocket.select(nil, { fd }, ti) 27 | return next(wt) ~= nil 28 | end 29 | 30 | function socket.close() 31 | fd:close() 32 | fd = nil 33 | message = nil 34 | end 35 | 36 | function socket.read(ti) 37 | while true do 38 | local ok, msg, n = pcall(string.unpack, ">s2", message) 39 | if not ok then 40 | local rd = lsocket.select { fd , ti } 41 | if next(rd) == nil then 42 | return nil 43 | end 44 | local p = fd:recv() 45 | if not p then 46 | error(socket.error) 47 | end 48 | message = message .. p 49 | else 50 | message = message:sub(n) 51 | return msg 52 | end 53 | end 54 | end 55 | 56 | function socket.write(msg) 57 | local pack = string.pack(">s2", msg) 58 | repeat 59 | local bytes = fd:send(pack) 60 | if not bytes then 61 | error(socket.error) 62 | end 63 | pack = pack:sub(bytes+1) 64 | until pack == "" 65 | end 66 | 67 | return socket 68 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | root = "$ROOT/" 2 | thread = 8 3 | logpath = root .. "run" 4 | harbor = 0 5 | start = "main" -- main script 6 | luaservice = root .. "service/?.lua;" .. root .."skynet/service/?.lua" 7 | lualoader = root .. "skynet/lualib/loader.lua" 8 | lua_path = root .. "lualib/?.lua;" .. root .. "skynet/lualib/?.lua;" .. root .. "skynet/lualib/?/init.lua" 9 | lua_cpath = root .. "skynet/luaclib/?.so" 10 | cpath = root .. "/cservice/?.so;"..root.."/skynet/cservice/?.so" 11 | 12 | if $DAEMON then 13 | logger = root .. "run/skynet.log" 14 | daemon = root .. "run/skynet.pid" 15 | end 16 | -------------------------------------------------------------------------------- /cservice/DUMMY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnki/ddz_skynet/403fb003e7c3dc9cf195838edf465d70b685122c/cservice/DUMMY -------------------------------------------------------------------------------- /lualib/client.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local proxy = require "socket_proxy" 3 | local sprotoloader = require "sprotoloader" 4 | local log = require "log" 5 | 6 | local client = {} 7 | local host 8 | local sender 9 | local handler = {} 10 | 11 | function client.handler() 12 | return handler 13 | end 14 | 15 | function client.dispatch( c ) 16 | local fd = c.fd 17 | proxy.subscribe(fd) 18 | local ERROR = {} 19 | while true do 20 | local msg, sz = proxy.read(fd) 21 | local type, name, args, response = host:dispatch(msg, sz) 22 | assert(type == "REQUEST") 23 | if c.exit then 24 | return c 25 | end 26 | local f = handler[name] 27 | if f then 28 | -- f may block , so fork and run 29 | skynet.fork(function() 30 | local ok, result = pcall(f, c, args) 31 | if ok then 32 | proxy.write(fd, response(result)) 33 | else 34 | log("raise error = %s", result) 35 | proxy.write(fd, response(ERROR, result)) 36 | end 37 | end) 38 | else 39 | -- unsupported command, disconnected 40 | error ("Invalid command " .. name) 41 | end 42 | end 43 | end 44 | 45 | function client.close(fd) 46 | proxy.close(fd) 47 | end 48 | 49 | function client.push(c, t, data) 50 | proxy.write(c.fd, sender(t, data)) 51 | end 52 | 53 | function client.fdpush(fd, t, data) 54 | proxy.write(fd, sender(t, data)) 55 | end 56 | 57 | function client.init(name) 58 | return function () 59 | local protoloader = skynet.uniqueservice "protoloader" 60 | local slot = skynet.call(protoloader, "lua", "index", name .. ".c2s") 61 | host = sprotoloader.load(slot):host "package" 62 | local slot2 = skynet.call(protoloader, "lua", "index", name .. ".s2c") 63 | sender = host:attach(sprotoloader.load(slot2)) 64 | end 65 | end 66 | 67 | return client -------------------------------------------------------------------------------- /lualib/ddz_rules.lua: -------------------------------------------------------------------------------- 1 | local suittype = { 2 | Spade = 1, --黑桃 3 | Heart = 2, --红桃 4 | Club = 3, --梅花 5 | Diamond = 4, --方块 6 | Joker = 5, --大小王 7 | } 8 | 9 | local CardDisplay = { 10 | "A","2","3","4","5","6","7","8","9","10","J","Q","K","Kinglet","King", 11 | } 12 | 13 | local cardtype = { 14 | dan = 1, --单张 15 | duizi = 2, --对子 16 | wangzha = 3, --王炸 17 | zhadan = 4, --炸弹 18 | shunzi = 5, --顺子 19 | liandui = 6, --连对 20 | san = 7, --三张(不带) 21 | sandaiyi = 8, --三带一 22 | sandaidui = 9, --三带一对 23 | feiji = 10, --飞机(不带) 24 | feiji2 = 11, --飞机(单带) 25 | feiji4 = 12, --飞机(带对子) 26 | sidaier = 13, --四带二 27 | sidaierdui = 14, --四带两对 28 | } 29 | 30 | local function getsuit(id) --花色 31 | local bigType = nil 32 | if id >= 1 and id <= 13 then 33 | bigType = suittype.Diamond 34 | elseif id >= 14 and id <= 26 then 35 | bigType = suittype.Club 36 | elseif id >= 27 and id <= 39 then 37 | bigType = suittype.Heart 38 | elseif id >= 40 and id <= 52 then 39 | bigType = suittype.Spade 40 | elseif id == 53 or id == 54 then 41 | bigType = suittype.Joker 42 | end 43 | return bigType 44 | end 45 | 46 | local function getdisplay(id) --牌号 47 | local display = nil 48 | if id >= 1 and id <= 52 then 49 | display = CardDisplay[(id-1) % 13 + 1] 50 | elseif id == 53 then 51 | display = CardDisplay[14] 52 | elseif id == 54 then 53 | display = CardDisplay[15] 54 | end 55 | return display 56 | end 57 | 58 | local function getgrade(id) --权级 59 | local grade = 0 60 | if id == 53 then --小王 61 | grade = 16 62 | elseif id == 54 then --大王 63 | grade = 17 64 | else 65 | local modResult = id % 13 66 | if modResult == 1 then -- A 67 | grade = 14 68 | elseif modResult == 2 then -- 2 69 | grade = 15 70 | elseif modResult >= 3 and modResult < 13 then -- 3到Q 71 | grade = modResult 72 | elseif modResult == 0 then --K 73 | grade = 13 74 | end 75 | end 76 | return grade 77 | end 78 | 79 | local AllCards = {} 80 | for i=1,54 do 81 | AllCards[i] = { 82 | id = i, 83 | grade=getgrade(i), 84 | suit=getsuit(i), 85 | display=getdisplay(i), 86 | } 87 | end 88 | 89 | local function sortcards(cards) 90 | table.sort(cards, function(a,b) 91 | return AllCards[a].grade > AllCards[b].grade 92 | end) 93 | end 94 | 95 | local function getgrades(cards) 96 | local t = {} 97 | for i,v in ipairs(cards) do 98 | local grade = AllCards[v].grade 99 | if t[grade] == nil then t[grade] = 0 end 100 | t[grade] = t[grade] + 1 101 | end 102 | return t 103 | end 104 | 105 | local function dump(cards) 106 | sortcards(cards) 107 | for i,v in ipairs(cards) do 108 | print(string.format("花色:%d,牌面:%s, 权重:%d", AllCards[v].suit, AllCards[v].display, AllCards[v].grade)) 109 | end 110 | end 111 | 112 | -- local c = {} 113 | -- for i=1,54 do 114 | -- table.insert(c, i) 115 | -- end 116 | -- sortcards(c) 117 | -- for i=1,54 do 118 | -- print(string.format("id: %d,\t 花色:%d,牌面:%s, 权重:%d",c[i], AllCards[c[i]].suit, AllCards[c[i]].display, AllCards[c[i]].grade)) 119 | -- end 120 | 121 | --单张 122 | local function isDan(cards) 123 | return #cards == 1 124 | end 125 | assert(isDan({33})) 126 | assert(isDan({23,5})==false) 127 | 128 | --对子 129 | local function isDuiZi(cards) 130 | return #cards == 2 131 | and AllCards[cards[1]].grade == AllCards[cards[2]].grade 132 | end 133 | assert(isDuiZi({30,43})) 134 | assert(isDuiZi({30,52}) == false) 135 | 136 | --大小王炸弹 137 | local function isDuiWang(cards) 138 | return #cards == 2 and cards[1]+cards[2]==107 139 | end 140 | assert(isDuiWang({53,54})) 141 | assert(isDuiWang({53,52}) == false) 142 | 143 | --三张 144 | local function isSan(cards) 145 | if #cards ~= 3 then return false end 146 | if AllCards[cards[1]].grade == AllCards[cards[2]].grade 147 | and AllCards[cards[2]].grade == AllCards[cards[3]].grade 148 | then 149 | return true 150 | else 151 | return false 152 | end 153 | end 154 | assert(isSan({30,43,17})) --4 4 4 155 | assert(isSan({30,43,15}) == false) --4 4 2 156 | 157 | --三带一 158 | local function isSanDaiYi(cards) 159 | if #cards ~= 4 then return false end 160 | sortcards(cards) 161 | if AllCards[cards[1]].grade ~= AllCards[cards[2]].grade then 162 | cards[1], cards[4] = cards[4], cards[1] 163 | end 164 | if AllCards[cards[1]].grade == AllCards[cards[2]].grade then 165 | if AllCards[cards[1]].grade == AllCards[cards[3]].grade 166 | and AllCards[cards[1]].grade ~= AllCards[cards[4]].grade then 167 | return true 168 | else 169 | return false 170 | end 171 | else 172 | return false 173 | end 174 | end 175 | assert(isSanDaiYi({30,43,17,5})) --4 4 4 3 176 | assert(isSanDaiYi({30,43,17,4}) == false) --4 4 4 4 177 | 178 | 179 | --三带一对 180 | local function isSanDaiDui(cards) 181 | if #cards ~= 5 then return false end 182 | sortcards(cards) 183 | if AllCards[cards[1]].grade ~= AllCards[cards[3]].grade then 184 | cards[1], cards[4] = cards[4], cards[1] 185 | cards[2], cards[5] = cards[5], cards[2] 186 | end 187 | if AllCards[cards[1]].grade == AllCards[cards[2]].grade then 188 | if AllCards[cards[1]].grade == AllCards[cards[3]].grade 189 | and AllCards[cards[1]].grade ~= AllCards[cards[4]].grade 190 | and AllCards[cards[4]].grade == AllCards[cards[5]].grade then 191 | return true 192 | else 193 | return false 194 | end 195 | else 196 | return false 197 | end 198 | end 199 | assert(isSanDaiDui({30,43,17,5,31})) -- 4 4 4 5 5 200 | assert(isSanDaiDui({30,43,17,3,31}) == false) --4 4 4 5 3 201 | 202 | --四张(炸弹) 203 | local function isSi(cards) 204 | if #cards ~= 4 then return false end 205 | for i=1,3 do 206 | if AllCards[cards[i]].grade ~= AllCards[cards[i+1]].grade then 207 | return false 208 | end 209 | end 210 | return true 211 | end 212 | assert(isSi({30,43,17,4})) -- 4 4 4 4 213 | assert(isSi({30,43,18,4}) == false) -- 4 4 5 4 214 | 215 | --四带二 216 | local function isSidaier(cards) 217 | if #cards ~= 6 then return false end 218 | local grades = getgrades(cards) 219 | for k,v in pairs(grades) do 220 | if v == 4 then return true end 221 | end 222 | return false 223 | end 224 | assert(isSidaier({30,43,17,4,3,5})) -- 4 4 4 4 3 5 225 | assert(isSidaier({30,43,17,4,3,16})) -- 4 4 4 4 3 3 226 | 227 | --四带两对 228 | local function isSidaierdui(cards) 229 | if #cards ~= 8 then return false end 230 | local grades = getgrades(cards) 231 | local t = false 232 | for k,v in pairs(grades) do 233 | if v == 4 then 234 | t = true 235 | elseif v ~= 2 then 236 | return false 237 | end 238 | end 239 | return t 240 | end 241 | assert(isSidaierdui({30,43,17,4,3,16,5,18})) -- 4 4 4 4 3 3 5 5 242 | assert(isSidaierdui({30,43,17,4,3,6,5,18}) == false) -- 4 4 4 4 3 6 5 5 243 | 244 | --顺子 245 | local function isShunzi(cards) 246 | if #cards < 5 then return false end 247 | sortcards(cards) 248 | if AllCards[cards[1]].grade > 14 then return false end --最大的牌不能超过A 249 | for i=1,#cards-1 do 250 | if AllCards[cards[i]].grade ~= AllCards[cards[i+1]].grade+1 then 251 | return false 252 | end 253 | end 254 | return true 255 | end 256 | assert(isShunzi({35,21,20,6,31,4})) --9 8 7 6 5 4 257 | assert(isShunzi({21,20,4,31}) == false) --9 8 7 4 5 258 | assert(isShunzi({35,21,5,6,31}) == false) --9 8 5 6 5 4 259 | 260 | --连对 261 | local function isLiandui(cards) 262 | if #cards < 6 then return false end 263 | if #cards%2 == 1 then return false end 264 | sortcards(cards) 265 | if AllCards[cards[1]].grade > 14 then return false end --最大的牌不能超过A 266 | for i=0,#cards/2-1 do 267 | if AllCards[cards[i*2+1]].grade ~= AllCards[cards[i*2+2]].grade then 268 | return false 269 | end 270 | if i<#cards/2-1 and AllCards[cards[(i+1)*2]].grade ~= AllCards[cards[(i+2)*2]].grade+1 then 271 | return false 272 | end 273 | end 274 | return true 275 | end 276 | assert(isLiandui({35,9,21,8,20,7})) -- 9 9 8 8 7 7 277 | assert(isLiandui({35,9,21,8,20,7,6,19})) -- 9 9 8 8 7 7 6 6 278 | assert(isLiandui({35,9,21,8,20,7,33}) == false) -- 9 9 8 8 7 7 7 279 | 280 | --飞机(不带) 281 | local function isFeiji(cards) 282 | if #cards ~= 6 and #cards ~= 9 then return false end 283 | local grades = getgrades(cards) 284 | local t, t2 285 | for k,v in pairs(grades) do 286 | if v ~= 3 then return false end 287 | if t == nil then t = k 288 | else 289 | if #cards == 6 then --两头飞机 290 | return t == k+1 or t == k-1 291 | elseif #cards == 9 then --三头飞机 292 | if not t2 then t2 = k 293 | else 294 | local arr = {t,t2,k} 295 | table.sort(arr, function(a,b) return a>b end) 296 | if arr[1] == arr[2]+1 and arr[2] == arr[3]+1 then 297 | return true 298 | else 299 | return false 300 | end 301 | end 302 | end 303 | end 304 | end 305 | end 306 | assert(isFeiji({35,9,22,21,8,34})) -- 9 9 9 8 8 8 307 | assert(isFeiji({35,9,22,21,8,34,7,20,33})) -- 9 9 9 8 8 8 7 7 7 308 | assert(isFeiji({35,9,22,20,7,33}) == false) -- 9 9 9 7 7 7 309 | 310 | --飞机(带单张) 311 | local function isFeiji2(cards) 312 | if #cards ~= 8 and #cards ~= 12 then return false end 313 | local grades = getgrades(cards) 314 | local t1, t2, t3 315 | for k,v in pairs(grades) do 316 | if v >= 3 then 317 | if not t1 then t1 = k 318 | elseif not t2 then t2 = k 319 | elseif not t3 then t3 = k 320 | end 321 | end 322 | end 323 | if #cards == 8 and t1 and t2 and (t1 == t2+1 or t2==t1+1) then 324 | return true 325 | elseif #cards == 12 and t1 and t2 and t3 then 326 | local arr = {t1,t2,t3} 327 | table.sort(arr, function(a,b) return a>b end) 328 | if arr[1] == arr[2]+1 and arr[2] == arr[3]+1 then 329 | return true 330 | else 331 | return false 332 | end 333 | end 334 | return false 335 | end 336 | assert(isFeiji2({35,9,22,21,8,34,3,4})) -- 9 9 9 8 8 8 3 4 337 | assert(isFeiji2({35,9,22,21,8,34,7,20,33,3,4,5})) -- 9 9 9 8 8 8 3 4 5 338 | assert(isFeiji2({35,9,22,20,7,33,3,4}) == false) -- 9 9 9 7 7 7 339 | 340 | 341 | --飞机(带对子) 342 | local function isFeiji4(cards) 343 | if #cards ~= 10 and #cards ~= 15 then return false end 344 | local grades = getgrades(cards) 345 | local t1, t2, t3 346 | for k,v in pairs(grades) do 347 | if v == 3 then 348 | if not t1 then t1 = k 349 | elseif not t2 then t2 = k 350 | elseif not t3 then t3 = k 351 | end 352 | elseif v ~= 2 then 353 | return false 354 | end 355 | end 356 | if #cards == 10 and t1 and t2 and (t1 == t2+1 or t2==t1+1) then 357 | return true 358 | elseif #cards == 15 and t1 and t2 and t3 then 359 | local arr = {t1,t2,t3} 360 | table.sort(arr, function(a,b) return a>b end) 361 | if arr[1] == arr[2]+1 and arr[2] == arr[3]+1 then 362 | return true 363 | else 364 | return false 365 | end 366 | end 367 | return false 368 | end 369 | assert(isFeiji4({35,9,22,21,8,34,3,4,16,17})) -- 9 9 9 8 8 8 3 3 4 4 370 | assert(isFeiji4({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 371 | assert(isFeiji4({35,9,22,21,8,34,3,4,16,18}) == false) -- 9 9 9 8 8 8 3 3 4 5 372 | 373 | local function getCardType(cards) 374 | local c = #cards 375 | if c == 1 then --单张 376 | return cardtype.dan 377 | elseif c == 2 then --两张只可能是王炸或一对 378 | if isDuiWang(cards) then 379 | return cardtype.wangzha 380 | elseif isDuiZi(cards) then 381 | return cardtype.duizi 382 | end 383 | elseif c == 3 then --三张只可能是单出三张 384 | if isSan(cards) then 385 | return cardtype.san 386 | end 387 | elseif c == 4 then --四张只可能是炸弹或三带一 388 | if isSi(cards) then 389 | return cardtype.zhadan 390 | elseif isSanDaiYi(cards) then 391 | return cardtype.sandaiyi 392 | end 393 | elseif c == 5 then --5张只可能是顺子或三带一对 394 | if isShunzi(cards) then 395 | return cardtype.shunzi 396 | elseif isSanDaiDui(cards) then 397 | return cardtype.sandaidui 398 | end 399 | elseif c == 6 then --6张可能是顺子、飞机(不带)、四带二、连对 400 | if isShunzi(cards) then 401 | return cardtype.shunzi 402 | elseif isFeiji(cards) then 403 | return cardtype.feiji 404 | elseif isSidaier(cards) then 405 | return cardtype.sidaier 406 | elseif isLiandui(cards) then 407 | return cardtype.liandui 408 | end 409 | elseif c == 7 or c == 11 then --7或11张只可能是顺子 410 | if isShunzi(cards) then 411 | return cardtype.shunzi 412 | end 413 | elseif c == 8 then --8张可能是顺子、连对、飞机(带单) 414 | if isShunzi(cards) then 415 | return cardtype.shunzi 416 | elseif isFeiji2(cards) then 417 | return cardtype.feiji 418 | elseif isLiandui(cards) then 419 | return cardtype.liandui 420 | end 421 | elseif c == 9 then --9张只可能是顺子、飞机(3头不带) 422 | if isShunzi(cards) then 423 | return cardtype.shunzi 424 | elseif isFeiji(cards) then 425 | return cardtype.feiji 426 | end 427 | elseif c == 10 then --10张可能是顺子、飞机(2头带对子)、连对 428 | if isShunzi(cards) then 429 | return cardtype.shunzi 430 | elseif isFeiji4(cards) then 431 | return cardtype.feiji 432 | elseif isLiandui(cards) then 433 | return cardtype.liandui 434 | end 435 | elseif c == 12 then --12张可能是顺子、飞机(3头带单张)、连对 436 | if isShunzi(cards) then 437 | return cardtype.shunzi 438 | elseif isFeiji2(cards) then 439 | return cardtype.feiji 440 | elseif isLiandui(cards) then 441 | return cardtype.liandui 442 | end 443 | elseif c == 14 then --14张只可能是连对 444 | if isLiandui(cards) then 445 | return cardtype.liandui 446 | end 447 | elseif c == 15 then --15张只可能是飞机(3头带对子) 448 | if isFeiji4(cards) then 449 | return cardtype.feiji 450 | end 451 | end 452 | return nil 453 | end 454 | assert(getCardType({54}) == cardtype.dan) 455 | assert(getCardType({54,53}) == cardtype.wangzha) 456 | assert(getCardType({3,3}) == cardtype.duizi) 457 | assert(getCardType({3,3,4,4,5,5}) == cardtype.liandui) 458 | assert(getCardType({3,4,5,6,7}) == cardtype.shunzi) 459 | assert(getCardType({13,3,4,5,6,7,8,9,10,11,12,1}) == cardtype.shunzi) 460 | assert(getCardType({3,3,3,3}) == cardtype.zhadan) 461 | assert(getCardType({3,3,3,3,4,4,4,4}) == cardtype.feiji) 462 | assert(getCardType({3,4,5,6,7,8,9}) == cardtype.shunzi) 463 | assert(getCardType({3,3,3,4,4,4,5,5,5}) == cardtype.feiji) 464 | assert(getCardType({3,3,3,1,4,4,4,2,5,5,5,7}) == cardtype.feiji) 465 | assert(getCardType({3,3,3,1,1,4,4,4,2,2,5,5,5,7,7}) == cardtype.feiji) 466 | assert(getCardType({3,3,4,4,2,2}) == nil) 467 | assert(getCardType({3,3,3,4,2}) == nil) 468 | assert(getCardType({3,3,3,4,4,4,2}) == nil) 469 | assert(getCardType({2,3,4,5,6}) == nil) 470 | assert(getCardType({3,4,5,6,7,7}) == nil) 471 | --------------------------------------------- 472 | local rule = {} 473 | 474 | rule.gettype = getCardType 475 | rule.sortcards = sortcards 476 | 477 | function rule.getcarddetail(card) 478 | return AllCards[card] 479 | end 480 | 481 | function rule.getsortedcarddetails(cards) 482 | sortcards(cards) 483 | local carddetails = {} 484 | for i,v in ipairs(cards) do 485 | carddetails[i] = rule.getcarddetail(v) 486 | end 487 | return carddetails 488 | end 489 | 490 | function rule.checkcards(precards, curcards) 491 | local ct = getCardType(curcards) 492 | if ct == nil then 493 | print("错误:不成牌型!不能出!") 494 | return false 495 | end 496 | if precards == nil then 497 | return true 498 | else 499 | local prect = getCardType(precards) 500 | if ct == cardtype.wangzha then 501 | return true 502 | elseif ct == cardtype.zhadan then --出炸弹 503 | if prect == cardtype.wangzha then 504 | print("错误:不可能比王炸还大!") 505 | return false 506 | elseif prect == cardtype.zhadan then 507 | if AllCards[precards[1]].grade < AllCards[curcards[1]].grade then 508 | return true 509 | else 510 | print("错误:炸弹不如上家大!") 511 | return false 512 | end 513 | else 514 | return true 515 | end 516 | elseif prect ~= ct then 517 | print("错误:牌型和上家不一样!") 518 | return false 519 | elseif #precards ~= #curcards then 520 | print("错误:牌型相同,但牌数不同,不能出牌!") 521 | return false 522 | else 523 | sortcards(precards) 524 | sortcards(curcards) 525 | if AllCards[precards[1]].grade < AllCards[curcards[1]].grade then 526 | return true 527 | else 528 | print("错误:牌面不如上家大!") 529 | return false 530 | end 531 | end 532 | end 533 | end 534 | return rule -------------------------------------------------------------------------------- /lualib/log.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local log = {} 4 | 5 | function log.format(fmt, ...) 6 | skynet.error(string.format(fmt, ...)) 7 | end 8 | 9 | function log.__call(self, ...) 10 | if select("#", ...) == 1 then 11 | skynet.error(tostring((...))) 12 | else 13 | self.format(...) 14 | end 15 | end 16 | 17 | return setmetatable(log, log) 18 | -------------------------------------------------------------------------------- /lualib/luahelper.lua: -------------------------------------------------------------------------------- 1 | function string.split(str, delimiter) 2 | if str==nil or str=='' or delimiter==nil then 3 | return nil 4 | end 5 | 6 | local result = {} 7 | for match in (str..delimiter):gmatch("(.-)"..delimiter) do 8 | table.insert(result, match) 9 | end 10 | return result 11 | end 12 | 13 | table.icontains = function(tbl, obj, func) 14 | if func then 15 | for i,v in ipairs(tbl) do 16 | if func(v,obj) then 17 | return i 18 | end 19 | end 20 | else 21 | for i,v in ipairs(tbl) do 22 | if v == obj then 23 | return i 24 | end 25 | end 26 | end 27 | return nil 28 | end -------------------------------------------------------------------------------- /lualib/service.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local log = require "log" 3 | 4 | local service = {} 5 | 6 | function service.init(mod) 7 | local funcs = mod.command 8 | if mod.info then 9 | skynet.info_func(function() 10 | return mod.info 11 | end) 12 | end 13 | skynet.start(function() 14 | if mod.require then 15 | local s = mod.require 16 | for _, name in ipairs(s) do 17 | service[name] = skynet.uniqueservice(name) 18 | end 19 | end 20 | if mod.init then 21 | mod.init() 22 | end 23 | skynet.dispatch("lua", function (_,_, cmd, ...) 24 | local f = funcs[cmd] 25 | if f then 26 | skynet.ret(skynet.pack(f(...))) 27 | else 28 | log("Unknown command : [%s]", cmd) 29 | skynet.response()(false) 30 | end 31 | end) 32 | end) 33 | end 34 | 35 | return service 36 | -------------------------------------------------------------------------------- /lualib/socket_proxy.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local proxyd 4 | 5 | skynet.init(function() 6 | proxyd = skynet.uniqueservice "socket_proxyd" 7 | end) 8 | 9 | local proxy = {} 10 | local map = {} 11 | 12 | skynet.register_protocol { 13 | name = "text", 14 | id = skynet.PTYPE_TEXT, 15 | pack = function(text) return text end, 16 | unpack = function(buf, sz) return skynet.tostring(buf,sz) end, 17 | } 18 | 19 | skynet.register_protocol { 20 | name = "client", 21 | id = skynet.PTYPE_CLIENT, 22 | pack = function(buf, sz) return buf, sz end, 23 | } 24 | 25 | local function get_addr(fd) 26 | return assert(map[fd], "subscribe first") 27 | end 28 | 29 | function proxy.subscribe(fd) 30 | local addr = map[fd] 31 | if not addr then 32 | addr = skynet.call(proxyd, "lua", fd) 33 | map[fd] = addr 34 | end 35 | end 36 | 37 | function proxy.read(fd) 38 | local ok,msg,sz = pcall(skynet.rawcall , get_addr(fd), "text", "R") 39 | if ok then 40 | return msg,sz 41 | else 42 | error "disconnect" 43 | end 44 | end 45 | 46 | function proxy.write(fd, msg, sz) 47 | skynet.send(get_addr(fd), "client", msg, sz) 48 | end 49 | 50 | function proxy.close(fd) 51 | skynet.send(get_addr(fd), "text", "K") 52 | end 53 | 54 | function proxy.info(fd) 55 | return skynet.call(get_addr(fd), "text", "I") 56 | end 57 | 58 | return proxy 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /proto/proto.c2s.sproto: -------------------------------------------------------------------------------- 1 | .package { 2 | type 0 : integer 3 | session 1 : integer 4 | ud 2 : string 5 | } 6 | 7 | .roominfo { 8 | user1 0: string 9 | user2 1: string 10 | user3 2: string 11 | } 12 | 13 | ping 1 {} 14 | 15 | signup 2 { 16 | request { 17 | userid 0 : string 18 | } 19 | response { 20 | ok 0 : boolean 21 | } 22 | } 23 | 24 | signin 3 { 25 | request { 26 | userid 0 : string 27 | } 28 | response { 29 | ok 0 : boolean 30 | } 31 | } 32 | 33 | login 4 { 34 | response { 35 | ok 0 : boolean 36 | } 37 | } 38 | 39 | joinroom 5 { 40 | response { 41 | ok 0: boolean 42 | room 1: roominfo 43 | index 2: integer 44 | } 45 | } 46 | 47 | oncommand 6 { 48 | request { 49 | cmd 0: string 50 | parameters 1: string 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /proto/proto.s2c.sproto: -------------------------------------------------------------------------------- 1 | .package { 2 | type 0 : integer 3 | session 1 : integer 4 | ud 2 : string 5 | } 6 | 7 | push 1 { 8 | request { 9 | text 0 : string 10 | } 11 | } 12 | 13 | onjoinroom 2{ 14 | request { 15 | index 0: integer 16 | userid 1: string 17 | } 18 | } 19 | 20 | onleftroom 3{ 21 | request { 22 | index 0: integer 23 | userid 1: string 24 | } 25 | } 26 | 27 | onuserready 4{ 28 | request { 29 | index 0: integer 30 | userid 1: string 31 | } 32 | } 33 | 34 | heartbeat 5 {} 35 | 36 | ongameready 6 { 37 | request { 38 | cards 0: string 39 | first 1: integer 40 | } 41 | } 42 | 43 | onrequestmaster 7 { 44 | request { 45 | rate 0: integer 46 | index 1: integer 47 | next 2: integer 48 | } 49 | } 50 | 51 | ongamestart 8 { 52 | request { 53 | master 0: integer 54 | excards 1: string 55 | } 56 | } 57 | 58 | onplay 9 { 59 | request { 60 | cards 0: string 61 | index 1: integer 62 | next 2: integer 63 | ok 3: boolean 64 | } 65 | } 66 | 67 | ongameover 10 { 68 | request { 69 | farmerwin 0: boolean 70 | p1score 1: integer 71 | p2score 2: integer 72 | p3score 3: integer 73 | } 74 | } -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export ROOT=$(cd `dirname $0`; pwd) 3 | export DAEMON=false 4 | 5 | while getopts "Dk" arg 6 | do 7 | case $arg in 8 | D) 9 | export DAEMON=true 10 | ;; 11 | k) 12 | kill `cat $ROOT/run/skynet.pid` 13 | exit 0; 14 | ;; 15 | esac 16 | done 17 | 18 | $ROOT/skynet/skynet $ROOT/config 19 | 20 | -------------------------------------------------------------------------------- /run/DUMMY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnki/ddz_skynet/403fb003e7c3dc9cf195838edf465d70b685122c/run/DUMMY -------------------------------------------------------------------------------- /service/agent.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local service = require "service" 3 | local client = require "client" 4 | local log = require "log" 5 | 6 | local agent = {} 7 | local data = {} 8 | local cli = client.handler() 9 | 10 | function cli:ping() 11 | assert(self.login) 12 | -- log "ping" 13 | end 14 | 15 | function cli:login() 16 | assert(not self.login) 17 | if data.fd then 18 | log("login fail %s fd=%d", data.userid, self.fd) 19 | return { ok = false } 20 | end 21 | data.fd = self.fd 22 | self.login = true 23 | log("login succ %s fd=%d", data.userid, self.fd) 24 | client.push(self, "push", { text = "welcome" }) -- push message to client 25 | return { ok = true } 26 | end 27 | 28 | function cli:joinroom() 29 | assert(self.login) --TODO: 判断是否已登录 30 | skynet.fork(function() 31 | while true do 32 | client.push(self, "heartbeat") 33 | skynet.sleep(500) 34 | end 35 | end) 36 | local roominfo, count, roomfd, index = skynet.call(service.manager, "lua", "assignroom", self.fd, data.userid, data.agentfd) 37 | data.room = roomfd 38 | return { ok = true, room = roominfo, index=index } 39 | end 40 | 41 | 42 | function cli:oncommand(args) 43 | print("oncommand:", args.cmd, args.parameters) 44 | if args.cmd == "ready" then --准备或取消准备 45 | if data.room then 46 | skynet.call(data.room, "lua", "onready", data.userid, args.parameters) 47 | else 48 | print("not join room yet.") 49 | end 50 | elseif args.cmd == "master" then --抢地主 51 | skynet.call(data.game, "lua", "askmaster", data.userid, tonumber(args.parameters)) 52 | elseif args.cmd == "play" then --出牌 53 | skynet.call(data.game, "lua", "play", data.userid, args.parameters) 54 | end 55 | end 56 | 57 | local function new_user(fd) 58 | local ok, error = pcall(client.dispatch , { fd = fd }) 59 | log("fd=%d is gone. error = %s", fd, error) 60 | client.close(fd) 61 | if data.room then 62 | skynet.call(data.room, "lua", "leftroom", data.userid) 63 | end 64 | if data.fd == fd then 65 | data.fd = nil 66 | skynet.sleep(1000) -- exit after 10s 67 | if data.fd == nil then 68 | -- double check 69 | if not data.exit then 70 | data.exit = true -- mark exit 71 | skynet.call(service.manager, "lua", "exit", data.userid) -- report exit 72 | log("user %s afk", data.userid) 73 | skynet.exit() 74 | end 75 | end 76 | end 77 | end 78 | 79 | function agent.ongameready(gamefd, args) 80 | data.game = gamefd 81 | client.fdpush(data.fd, "ongameready", args) 82 | end 83 | 84 | function agent.assign(fd, userid) 85 | if data.exit then 86 | return false 87 | end 88 | if data.userid == nil then 89 | data.userid = userid 90 | end 91 | data.agentfd = skynet.self() 92 | 93 | assert(data.userid == userid) 94 | skynet.fork(new_user, fd) 95 | return true 96 | end 97 | 98 | service.init { 99 | command = agent, 100 | info = data, 101 | require = { 102 | "manager", 103 | }, 104 | init = client.init "proto", 105 | } 106 | 107 | -------------------------------------------------------------------------------- /service/auth.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local service = require "service" 3 | local client = require "client" 4 | local log = require "log" 5 | 6 | local auth = {} 7 | local users = {} 8 | local cli = client.handler() 9 | 10 | local SUCC = { ok = true } 11 | local FAIL = { ok = false } 12 | 13 | function cli:signup(args) 14 | log("signup userid = %s", args.userid) 15 | if users[args.userid] then 16 | return FAIL 17 | else 18 | users[args.userid] = true 19 | return SUCC 20 | end 21 | end 22 | 23 | function cli:signin(args) 24 | log("signin userid = %s", args.userid) 25 | if users[args.userid] then 26 | self.userid = args.userid 27 | self.exit = true 28 | return SUCC 29 | else 30 | return FAIL 31 | end 32 | end 33 | 34 | function cli:ping() 35 | log("ping") 36 | end 37 | 38 | function auth.shakehand(fd) 39 | local c = client.dispatch { fd = fd } 40 | return c.userid 41 | end 42 | 43 | service.init { 44 | command = auth, 45 | data = users, 46 | init = client.init "proto", 47 | } 48 | -------------------------------------------------------------------------------- /service/game.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local service = require "service" 3 | local client = require "client" 4 | local log = require "log" 5 | local proxy = require "socket_proxy" 6 | local rules = require "ddz_rules" 7 | require "luahelper" 8 | local CMD = {} 9 | local players 10 | local termIndex 11 | local excards 12 | local askTimes 13 | local curterm 14 | local GAME_PLAYER = 3 15 | 16 | local precards 17 | 18 | local function getindexbyid(userid) 19 | local index 20 | for i,v in ipairs(players) do 21 | if v.userid == userid then 22 | index = i 23 | end 24 | end 25 | return index 26 | end 27 | local function shuffle(array) 28 | for i=1,#array do 29 | local r = math.random(1,54) 30 | local t = array[i] 31 | array[i] = array[r] 32 | array[r] = t 33 | end 34 | end 35 | local function dump(array) 36 | for i=1,#array do 37 | io.write(array[i].." ") 38 | end 39 | io.write("\n") 40 | end 41 | 42 | local function serialize(cards) 43 | -- local s = "" 44 | -- for i=1,#cards do 45 | -- s = s..string.format("%c", cards[i]) 46 | -- end 47 | -- return s 48 | return string.char(table.unpack(cards)) 49 | end 50 | 51 | function CMD.init(_players) 52 | local str = "" 53 | for i,v in ipairs(_players) do 54 | str = str..v.userid.."("..v.fd.."), " 55 | v.card = {} 56 | end 57 | print("game init: players", str) 58 | players = _players 59 | 60 | local cards = {} 61 | for i=1,54 do 62 | cards[i] = i 63 | end 64 | shuffle(cards) --洗牌 65 | local t = 51/GAME_PLAYER --玩家牌数目 66 | for i=1,51 do --发牌51张 67 | table.insert(players[(i-1)%GAME_PLAYER+1].card, cards[i]) 68 | end 69 | excards = {cards[52], cards[53], cards[54]} 70 | termIndex = math.random(1,GAME_PLAYER) --随机先叫地主 71 | askTimes = {} --抢地主的状态 72 | curterm = {} --当前轮的出牌状态 73 | for i,v in ipairs(players) do 74 | -- dump(v.card) 75 | print(skynet.address(v.agentfd)) 76 | local s = serialize(v.card) 77 | skynet.call(v.agentfd, "lua", "ongameready", skynet.self(), {cards=s,first=termIndex}) 78 | proxy.subscribe(v.fd) 79 | -- client.fdpush(v.fd, "ongameready", {cards=s,first=master}) --初始牌信息推送给每个玩家 80 | end 81 | end 82 | 83 | ----------规则------------- 84 | --1. 叫地主-不抢-不抢 85 | --2. 叫地主-不抢-抢地主-抢地主 86 | --3. 叫地主-不抢-抢地主-不抢 87 | --4. 叫地主-抢地主-不抢-抢地主 88 | --5. 叫地主-抢地主-不抢-不抢 89 | --6. 叫地主-抢地主-抢地主-抢地主 90 | --7. 叫地主-抢地主-抢地主-不抢 91 | --8. 不叫-不叫-不叫 92 | --9. 不叫-不叫-叫地主 93 | --10. 不叫-抢地主-不抢 94 | --11. 不叫-抢地主-抢地主-抢地主 95 | --12. 不叫-抢地主-抢地主-不抢 96 | function CMD.askmaster(userid, rate) 97 | print(userid, "ask master with rate: ", rate) 98 | local playerIndex = getindexbyid(userid) 99 | local nextIndex = playerIndex + 1 100 | 101 | 102 | table.insert(askTimes, rate) 103 | local finished = false 104 | local neednext = false 105 | 106 | --TODO:需要验证客户端数字有效性 107 | if #askTimes < 3 then 108 | neednext = true 109 | elseif #askTimes == 3 then --叫地主3次 110 | if askTimes[1] > 0 then 111 | if askTimes[2] == 0 and askTimes[3] == 0 then 112 | termIndex = termIndex --#1 113 | finished = true 114 | else 115 | neednext = true 116 | end 117 | else 118 | if askTimes[2] == 0 then 119 | if askTimes[3] == 0 then --#8 120 | --game restart 121 | else --#9 122 | termIndex = termIndex + 2 123 | finished = true 124 | end 125 | else 126 | if askTimes[3] == 0 then --#10 127 | termIndex = termIndex + 1 128 | finished = true 129 | else 130 | neednext = true 131 | nextIndex = nextIndex + 1 --跳过1号 132 | end 133 | end 134 | end 135 | else --叫地主相关的4次或以上 136 | if askTimes[1] > 0 then --1号抢地主 137 | if askTimes[2] == 0 and askTimes[3] >= askTimes[1] then --2号不抢地主,3号抢地主 138 | if askTimes[4] == 0 then --1号不抢,地主是3号 139 | termIndex = termIndex + 2 --#3 140 | finished = true 141 | elseif askTimes[4] >= askTimes[3] then --1号抢地主,地主是1号 142 | termIndex = termIndex --#2 143 | finished = true 144 | end 145 | elseif askTimes[2] >= askTimes[1] then --2号抢地主 146 | if askTimes[3] >= askTimes[2] then --3号抢地主 147 | if askTimes[4] >= askTimes[3] then 148 | termIndex = termIndex --#6 149 | finished = true 150 | elseif askTimes[4] == 0 then 151 | termIndex = termIndex + 2 --#7 152 | finished = true 153 | end 154 | elseif askTimes[3] == 0 then --3号不抢 155 | if askTimes[4] >= askTimes[2] then --1号抢地主,地主是1号 156 | termIndex = termIndex --#4 157 | finished = true 158 | elseif askTimes[4] == 0 then 159 | termIndex = termIndex + 2 --#5 160 | finished = true 161 | end 162 | end 163 | else 164 | print("错误,抢地主的倍率不能小于上一家") 165 | end 166 | else --1号不抢地主 167 | if askTimes[2] > 0 and askTimes[3] >= askTimes[2] then 168 | if askTimes[4] >= askTimes[3] then 169 | termIndex = termIndex + 1 --#11 170 | finished = true 171 | elseif askTimes[4] == 0 then 172 | termIndex = termIndex + 2 --#12 173 | finished = true 174 | end 175 | end 176 | end 177 | end 178 | if nextIndex > 3 then nextIndex = nextIndex - 3 end 179 | if termIndex > 3 then termIndex = termIndex - 3 end 180 | for i,v in ipairs(players) do 181 | client.fdpush(v.fd, "onrequestmaster", {rate=rate,index=playerIndex,next=(neednext and nextIndex or 0)}) 182 | end 183 | if finished then 184 | for i,v in ipairs(excards) do 185 | table.insert(players[termIndex].card,v) 186 | end 187 | players[termIndex].master = true 188 | for i,v in ipairs(players) do 189 | client.fdpush(v.fd, "ongamestart", {master=termIndex,excards=serialize(excards)}) 190 | end 191 | end 192 | 193 | end 194 | 195 | local function gameover(winner) 196 | skynet.sleep(200) 197 | local farmerwin = not players[winner].master 198 | --TODO:计算每个玩家的得分 199 | for i,v in ipairs(players) do 200 | client.fdpush(v.fd, "ongameover", { 201 | farmerwin=farmerwin, 202 | p1score=10, 203 | p2score=10, 204 | p3score=10 205 | }) 206 | end 207 | end 208 | 209 | function CMD.play(userid, cardstr) 210 | local pindex = getindexbyid(userid) 211 | local cards = {string.byte(cardstr, 1, -1)} 212 | local ok = true 213 | 214 | --TODO: 检查重复ID 215 | if cards[1] == 0 then -- PASS 216 | print("玩家"..pindex.."PASS!") 217 | termIndex = pindex + 1 218 | if termIndex > 3 then termIndex = 1 end 219 | if precards and precards[1] == 0 then --上一家也不要 220 | curtermcards = nil --清空precards 221 | end 222 | precards = cards 223 | else 224 | local s = rules.getsortedcarddetails(cards) 225 | io.write("玩家"..pindex.."本轮出牌:") 226 | for i,v in pairs(s) do 227 | io.write(v.display.." ") 228 | end 229 | io.write("\n") 230 | if rules.checkcards(curtermcards, cards) then --验证是否可以出牌 231 | for i2,v2 in ipairs(cards) do 232 | local idx = table.icontains(players[pindex].card, v2) 233 | if idx then 234 | --从玩家牌堆里移除牌 235 | table.remove(players[pindex].card, idx) 236 | end 237 | end 238 | 239 | if #players[pindex].card == 0 then --玩家已出完全部牌 240 | print("~~~~~gameover") 241 | termIndex = 0 242 | skynet.fork(gameover, pindex) 243 | else 244 | curtermcards = cards 245 | termIndex = pindex + 1 246 | if termIndex > 3 then termIndex = 1 end 247 | end 248 | precards = cards 249 | else --出牌无效 250 | cardstr = string.char(0) 251 | ok = false 252 | end 253 | end 254 | 255 | for i,v in ipairs(players) do 256 | client.fdpush(v.fd, "onplay", {cards=cardstr,index=pindex, next=termIndex, ok=ok}) 257 | end 258 | end 259 | 260 | service.init { 261 | command = CMD, 262 | init = client.init "proto", 263 | } -------------------------------------------------------------------------------- /service/hub.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local socket = require "socket" 3 | local proxy = require "socket_proxy" 4 | local log = require "log" 5 | local service = require "service" 6 | 7 | local hub = {} 8 | local data = { socket = {} } 9 | 10 | local function auth_socket(fd) 11 | return (skynet.call(service.auth, "lua", "shakehand" , fd)) 12 | end 13 | 14 | local function assign_agent(fd, userid) 15 | skynet.call(service.manager, "lua", "assign", fd, userid) 16 | end 17 | 18 | function new_socket(fd, addr) 19 | data.socket[fd] = "[AUTH]" 20 | proxy.subscribe(fd) 21 | local ok , userid = pcall(auth_socket, fd) 22 | if ok then 23 | data.socket[fd] = userid 24 | if pcall(assign_agent, fd, userid) then 25 | return -- succ 26 | else 27 | log("Assign failed %s to %s", addr, userid) 28 | end 29 | else 30 | log("Auth faild %s", addr) 31 | end 32 | proxy.close(fd) 33 | data.socket[fd] = nil 34 | end 35 | 36 | function hub.open(ip, port) 37 | log("Listen %s:%d", ip, port) 38 | assert(data.fd == nil, "Already open") 39 | data.fd = socket.listen(ip, port) 40 | data.ip = ip 41 | data.port = port 42 | socket.start(data.fd, new_socket) 43 | end 44 | 45 | function hub.close() 46 | assert(data.fd) 47 | log("Close %s:%d", data.ip, data.port) 48 | socket.close(data.fd) 49 | data.ip = nil 50 | data.port = nil 51 | end 52 | 53 | service.init { 54 | command = hub, 55 | info = data, 56 | require = { 57 | "auth", 58 | "manager", 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /service/main.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | skynet.start(function() 4 | skynet.error("Server start") 5 | if not skynet.getenv "daemon" then 6 | local console = skynet.newservice("console") 7 | end 8 | skynet.newservice("debug_console",8000) 9 | local proto = skynet.uniqueservice "protoloader" 10 | skynet.call(proto, "lua", "load", { 11 | "proto.c2s", 12 | "proto.s2c", 13 | }) 14 | local hub = skynet.uniqueservice "hub" 15 | skynet.call(hub, "lua", "open", "0.0.0.0", 5678) 16 | skynet.exit() 17 | end) 18 | -------------------------------------------------------------------------------- /service/manager.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local service = require "service" 3 | local log = require "log" 4 | 5 | local manager = {} 6 | local users = {} 7 | 8 | local rooms = {waittingroom={}, runningroom={}} 9 | 10 | local function new_agent() 11 | -- todo: use a pool 12 | return skynet.newservice "agent" 13 | end 14 | 15 | local function free_agent(agent) 16 | -- kill agent, todo: put it into a pool maybe better 17 | skynet.kill(agent) 18 | end 19 | 20 | function manager.assign(fd, userid) 21 | local agent 22 | repeat 23 | agent = users[userid] 24 | if not agent then 25 | agent = new_agent() 26 | if not users[userid] then 27 | -- double check 28 | users[userid] = agent 29 | else 30 | free_agent(agent) 31 | agent = users[userid] 32 | end 33 | end 34 | until skynet.call(agent, "lua", "assign", fd, userid) 35 | log("Assign %d to %s [%s]", fd, userid, agent) 36 | end 37 | 38 | --分配一个房间 39 | function manager.assignroom(fd, userid, agentfd) 40 | local room 41 | for i,v in ipairs(rooms.waittingroom) do --等待中房间 42 | local nums = skynet.call(v, "lua", "playernum") 43 | if nums < 3 then --玩家数目小于3 44 | room = v 45 | break 46 | end 47 | end 48 | if not room then --新建房间 49 | room = skynet.newservice "room" 50 | table.insert(rooms.waittingroom, room) 51 | end 52 | -- local agent = users[userid] 53 | -- agent.room = room 54 | return skynet.call(room, "lua", "joinroom", fd, userid, agentfd) 55 | end 56 | 57 | --房间开始游戏 58 | function manager.roomstart(roomfd, players) 59 | for i,v in ipairs(rooms.waittingroom) do 60 | if v == roomfd then 61 | table.remove(rooms.waittingroom, i) 62 | break 63 | end 64 | end 65 | table.insert(rooms.runningroom, roomfd) 66 | 67 | local game = skynet.newservice "game" 68 | skynet.call(game, "lua", "init", players) 69 | end 70 | 71 | function manager.exit(userid) 72 | users[userid] = nil 73 | end 74 | 75 | service.init { 76 | command = manager, 77 | data = users, 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /service/protoloader.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local sprotoparser = require "sprotoparser" 3 | local sprotoloader = require "sprotoloader" 4 | local service = require "service" 5 | local log = require "log" 6 | 7 | local loader = {} 8 | local data = {} 9 | 10 | local function load(name) 11 | local filename = string.format("proto/%s.sproto", name) 12 | local f = assert(io.open(filename), "Can't open " .. name) 13 | local t = f:read "a" 14 | f:close() 15 | return sprotoparser.parse(t) 16 | end 17 | 18 | function loader.load(list) 19 | for i, name in ipairs(list) do 20 | local p = load(name) 21 | log("load proto [%s] in slot %d", name, i) 22 | data[name] = i 23 | sprotoloader.save(p, i) 24 | end 25 | end 26 | 27 | function loader.index(name) 28 | return data[name] 29 | end 30 | 31 | service.init { 32 | command = loader, 33 | data = data 34 | } 35 | -------------------------------------------------------------------------------- /service/room.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local service = require "service" 3 | local client = require "client" 4 | local log = require "log" 5 | local proxy = require "socket_proxy" 6 | 7 | local CMD = {} 8 | local roomusers = {} 9 | 10 | local GAME_PLAYER = 3 11 | local function getroomuser(index) 12 | local users = {} 13 | local count = 0 14 | for i=1,GAME_PLAYER do 15 | if roomusers[i] then 16 | users["user"..i] = roomusers[i].userid 17 | count = count + 1 18 | end 19 | end 20 | return users, count, skynet.self(), index 21 | end 22 | 23 | local function getuserbyid(userid) 24 | for i=1,GAME_PLAYER do 25 | if roomusers[i] and roomusers[i].userid == userid then 26 | return roomusers[i], i 27 | end 28 | end 29 | end 30 | 31 | function CMD.playernum() 32 | local _, count = getroomuser() 33 | return count 34 | end 35 | 36 | function CMD.joinroom(fd, userid, agentfd) --加入房间 37 | print("user: ",fd, userid, " has joinned room ") 38 | proxy.subscribe(fd) 39 | local index = 0 40 | for i=1,GAME_PLAYER do 41 | if not roomusers[i] then --找到一个空位就坐上去 42 | roomusers[i] = {fd=fd,userid=userid,agentfd=agentfd} 43 | index = i 44 | break 45 | end 46 | end 47 | 48 | for i=1,GAME_PLAYER do 49 | if index ~= i and roomusers[i] then --已上坐玩家发送消息更新房间 50 | client.fdpush(roomusers[i].fd, "onjoinroom", {index=index, userid=userid}) 51 | end 52 | end 53 | return getroomuser(index) 54 | end 55 | 56 | function CMD.leftroom(userid) --离开房间 57 | local index = 0 58 | for i=1,GAME_PLAYER do 59 | if roomusers[i] and roomusers[i].userid == userid then 60 | roomusers[i] = nil 61 | index = i 62 | break 63 | end 64 | end 65 | 66 | for i=1,GAME_PLAYER do 67 | if roomusers[i] then --已上坐玩家发送消息更新房间 68 | client.fdpush(roomusers[i].fd, "onleftroom", {index=index, userid=userid}) 69 | end 70 | end 71 | print("user: ", userid, " has left the room") 72 | end 73 | 74 | function CMD.onready(userid, flag) 75 | local user,index = getuserbyid(userid) 76 | if flag == "1" then 77 | user.ready = true 78 | print("~~~~~", userid, "on ready") 79 | else 80 | user.ready = false 81 | print("~~~~~", userid, "unready") 82 | end 83 | 84 | 85 | for i=1,GAME_PLAYER do --向房间里其它人发送“玩家已准备“消息 86 | if roomusers[i] and roomusers[i] ~= user then 87 | client.fdpush(roomusers[i].fd, "onuserready", {index=index, userid=user.userid}) 88 | end 89 | end 90 | 91 | local readycount = 0 92 | for i=1,GAME_PLAYER do 93 | if roomusers[i] and roomusers[i].ready then 94 | readycount = readycount + 1 95 | end 96 | end 97 | 98 | if readycount == GAME_PLAYER then 99 | skynet.call(service.manager, "lua", "roomstart", skynet.self(), roomusers) 100 | end 101 | 102 | end 103 | 104 | service.init { 105 | command = CMD, 106 | require = { 107 | "manager", 108 | }, 109 | init = client.init "proto", 110 | } -------------------------------------------------------------------------------- /service/socket_proxyd.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | require "skynet.manager" 3 | require "skynet.debug" 4 | 5 | skynet.register_protocol { 6 | name = "text", 7 | id = skynet.PTYPE_TEXT, 8 | unpack = skynet.tostring, 9 | pack = function(text) return text end, 10 | } 11 | 12 | local socket_fd_addr = {} 13 | local socket_addr_fd = {} 14 | local socket_init = {} 15 | 16 | local function close_agent(addr) 17 | local fd = assert(socket_addr_fd[addr]) 18 | socket_fd_addr[fd] = nil 19 | socket_addr_fd[addr] = nil 20 | end 21 | 22 | local function subscribe(fd) 23 | local addr = socket_fd_addr[fd] 24 | if addr then 25 | return addr 26 | end 27 | addr = assert(skynet.launch("package", skynet.self(), fd)) 28 | socket_fd_addr[fd] = addr 29 | socket_addr_fd[addr] = fd 30 | socket_init[addr] = skynet.response() 31 | end 32 | 33 | local function get_status(addr) 34 | local ok, info = pcall(skynet.call,addr, "text", "I") 35 | if ok then 36 | return info 37 | else 38 | return "EXIT" 39 | end 40 | end 41 | 42 | skynet.info_func(function() 43 | local tmp = {} 44 | for fd,addr in pairs(socket_fd_addr) do 45 | if socket_init[addr] then 46 | table.insert(tmp, { fd = fd, addr = skynet.address(addr), status = "NOTREADY" }) 47 | else 48 | table.insert(tmp, { fd = fd, addr = skynet.address(addr), status = get_status(addr) }) 49 | end 50 | end 51 | return tmp 52 | end) 53 | 54 | skynet.start(function() 55 | skynet.dispatch("text", function (session, source, cmd) 56 | if cmd == "CLOSED" then 57 | close_agent(source) 58 | elseif cmd == "SUCC" then 59 | socket_init[source](true, source) 60 | socket_init[source] = nil 61 | elseif cmd == "FAIL" then 62 | socket_init[source](false) 63 | socket_init[source] = nil 64 | else 65 | skynet.error("Invalid command " .. cmd) 66 | end 67 | end) 68 | skynet.dispatch("lua", function (session, source, fd) 69 | assert(type(fd) == "number") 70 | local addr = subscribe(fd) 71 | if addr then 72 | skynet.ret(skynet.pack(addr)) 73 | end 74 | end) 75 | end) 76 | -------------------------------------------------------------------------------- /src/service_package.c: -------------------------------------------------------------------------------- 1 | #include "skynet.h" 2 | #include "skynet_socket.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define TIMEOUT "1000" // 10s 9 | 10 | struct response { 11 | size_t sz; 12 | void * msg; 13 | }; 14 | 15 | struct request { 16 | uint32_t source; 17 | int session; 18 | }; 19 | 20 | struct queue { 21 | int cap; 22 | int sz; 23 | int head; 24 | int tail; 25 | char * buffer; 26 | }; 27 | 28 | struct package { 29 | uint32_t manager; 30 | int fd; 31 | int heartbeat; 32 | int recv; 33 | int init; 34 | int closed; 35 | 36 | int header_sz; 37 | uint8_t header[2]; 38 | int uncomplete_sz; 39 | struct response uncomplete; 40 | 41 | struct queue request; 42 | struct queue response; 43 | }; 44 | 45 | static void 46 | queue_init(struct queue *q, int sz) { 47 | q->head = 0; 48 | q->tail = 0; 49 | q->sz = sz; 50 | q->cap = 4; 51 | q->buffer = skynet_malloc(q->cap * q->sz); 52 | } 53 | 54 | static void 55 | queue_exit(struct queue *q) { 56 | skynet_free(q->buffer); 57 | q->buffer = NULL; 58 | } 59 | 60 | static int 61 | queue_empty(struct queue *q) { 62 | return q->head == q->tail; 63 | } 64 | 65 | static int 66 | queue_pop(struct queue *q, void *result) { 67 | if (q->head == q->tail) { 68 | return 1; 69 | } 70 | memcpy(result, q->buffer + q->head * q->sz, q->sz); 71 | q->head++; 72 | if (q->head >= q->cap) 73 | q->head = 0; 74 | return 0; 75 | } 76 | 77 | static void 78 | queue_push(struct queue *q, const void *value) { 79 | void * slot = q->buffer + q->tail * q->sz; 80 | ++q->tail; 81 | if (q->tail >= q->cap) 82 | q->tail = 0; 83 | if (q->head == q->tail) { 84 | // full 85 | assert(q->sz > 0); 86 | int cap = q->cap * 2; 87 | char * tmp = skynet_malloc(cap * q->sz); 88 | int i; 89 | int head = q->head; 90 | for (i=0;icap;i++) { 91 | memcpy(tmp + i * q->sz, q->buffer + head * q->sz, q->sz); 92 | ++head; 93 | if (head >= q->cap) { 94 | head = 0; 95 | } 96 | } 97 | skynet_free(q->buffer); 98 | q->head = 0; 99 | slot = tmp + (q->cap-1) * q->sz; 100 | q->tail = q->cap; 101 | q->cap = cap; 102 | q->buffer = tmp; 103 | } 104 | memcpy(slot, value, q->sz); 105 | } 106 | 107 | static int 108 | queue_size(struct queue *q) { 109 | if (q->head > q->tail) { 110 | return q->tail + q->cap - q->head; 111 | } 112 | return q->tail - q->head; 113 | } 114 | 115 | static void 116 | service_exit(struct skynet_context *ctx, struct package *P) { 117 | // report manager 118 | P->closed = 1; 119 | while (!queue_empty(&P->request)) { 120 | struct request req; 121 | queue_pop(&P->request, &req); 122 | skynet_send(ctx, 0, req.source, PTYPE_ERROR, req.session, NULL, 0); 123 | } 124 | while (!queue_empty(&P->response)) { 125 | // drop the message 126 | struct response resp; 127 | queue_pop(&P->response, &resp); 128 | skynet_free(resp.msg); 129 | } 130 | skynet_send(ctx, 0, P->manager, PTYPE_TEXT, 0, "CLOSED", 6); 131 | skynet_command(ctx, "EXIT", NULL); 132 | } 133 | 134 | static void 135 | report_info(struct skynet_context *ctx, struct package *P, int session, uint32_t source) { 136 | int uncomplete; 137 | int uncomplete_sz; 138 | if (P->header_sz != 0) { 139 | uncomplete = -1; 140 | uncomplete_sz = 0; 141 | } else if (P->uncomplete_sz == 0) { 142 | uncomplete = 0; 143 | uncomplete_sz = 0; 144 | } else { 145 | uncomplete = P->uncomplete_sz; 146 | uncomplete_sz = P->uncomplete.sz; 147 | } 148 | char tmp[128]; 149 | int n = sprintf(tmp,"req=%d resp=%d uncomplete=%d/%d", queue_size(&P->request), queue_size(&P->response),uncomplete,uncomplete_sz); 150 | skynet_send(ctx, 0, source, PTYPE_RESPONSE, session, tmp, n); 151 | } 152 | 153 | static void 154 | command(struct skynet_context *ctx, struct package *P, int session, uint32_t source, const char *msg, size_t sz) { 155 | switch (msg[0]) { 156 | case 'R': 157 | // request a package 158 | if (P->closed) { 159 | skynet_send(ctx, 0, source, PTYPE_ERROR, session, NULL, 0); 160 | break; 161 | } 162 | if (!queue_empty(&P->response)) { 163 | assert(queue_empty(&P->request)); 164 | struct response resp; 165 | queue_pop(&P->response, &resp); 166 | skynet_send(ctx, 0, source, PTYPE_RESPONSE | PTYPE_TAG_DONTCOPY, session, resp.msg, resp.sz); 167 | } else { 168 | struct request req; 169 | req.source = source; 170 | req.session = session; 171 | queue_push(&P->request, &req); 172 | } 173 | break; 174 | case 'K': 175 | // shutdown the connection 176 | skynet_socket_shutdown(ctx, P->fd); 177 | break; 178 | case 'I': 179 | report_info(ctx, P, session, source); 180 | break; 181 | default: 182 | // invalid command 183 | skynet_error(ctx, "Invalid command %.*s", (int)sz, msg); 184 | skynet_send(ctx, 0, source, PTYPE_ERROR, session, NULL, 0); 185 | break; 186 | }; 187 | } 188 | 189 | static void 190 | new_message(struct package *P, const uint8_t *msg, int sz) { 191 | ++P->recv; 192 | for (;;) { 193 | if (P->uncomplete_sz >= 0) { 194 | if (sz >= P->uncomplete_sz) { 195 | memcpy(P->uncomplete.msg + P->uncomplete.sz - P->uncomplete_sz, msg, P->uncomplete_sz); 196 | msg += P->uncomplete_sz; 197 | sz -= P->uncomplete_sz; 198 | queue_push(&P->response, &P->uncomplete); 199 | P->uncomplete_sz = -1; 200 | } else { 201 | memcpy(P->uncomplete.msg + P->uncomplete.sz - P->uncomplete_sz, msg, sz); 202 | P->uncomplete_sz -= sz; 203 | return; 204 | } 205 | } 206 | 207 | if (sz <= 0) 208 | return; 209 | 210 | if (P->header_sz == 0) { 211 | if (sz == 1) { 212 | P->header[0] = msg[0]; 213 | P->header_sz = 1; 214 | return; 215 | } 216 | P->header[0] = msg[0]; 217 | P->header[1] = msg[1]; 218 | msg+=2; 219 | sz-=2; 220 | } else { 221 | assert(P->header_sz == 1); 222 | P->header[1] = msg[0]; 223 | P->header_sz = 0; 224 | ++msg; 225 | --sz; 226 | } 227 | P->uncomplete.sz = P->header[0] * 256 + P->header[1]; 228 | P->uncomplete.msg = skynet_malloc(P->uncomplete.sz); 229 | P->uncomplete_sz = P->uncomplete.sz; 230 | } 231 | } 232 | 233 | static void 234 | response(struct skynet_context *ctx, struct package *P) { 235 | while (!queue_empty(&P->request)) { 236 | if (queue_empty(&P->response)) { 237 | break; 238 | } 239 | struct request req; 240 | struct response resp; 241 | queue_pop(&P->request, &req); 242 | queue_pop(&P->response, &resp); 243 | skynet_send(ctx, 0, req.source, PTYPE_RESPONSE | PTYPE_TAG_DONTCOPY, req.session, resp.msg, resp.sz); 244 | } 245 | } 246 | 247 | static void 248 | socket_message(struct skynet_context *ctx, struct package *P, const struct skynet_socket_message * smsg) { 249 | switch (smsg->type) { 250 | case SKYNET_SOCKET_TYPE_CONNECT: 251 | if (P->init == 0 && smsg->id == P->fd) { 252 | skynet_send(ctx, 0, P->manager, PTYPE_TEXT, 0, "SUCC", 4); 253 | P->init = 1; 254 | } 255 | break; 256 | case SKYNET_SOCKET_TYPE_CLOSE: 257 | case SKYNET_SOCKET_TYPE_ERROR: 258 | if (P->init == 0 && smsg->id == P->fd) { 259 | skynet_send(ctx, 0, P->manager, PTYPE_TEXT, 0, "FAIL", 4); 260 | P->init = 1; 261 | } 262 | if (smsg->id != P->fd) { 263 | skynet_error(ctx, "Invalid fd (%d), should be (%d)", smsg->id, P->fd); 264 | } else { 265 | // todo: log when SKYNET_SOCKET_TYPE_ERROR 266 | response(ctx, P); 267 | service_exit(ctx, P); 268 | } 269 | break; 270 | case SKYNET_SOCKET_TYPE_DATA: 271 | new_message(P, (const uint8_t *)smsg->buffer, smsg->ud); 272 | skynet_free(smsg->buffer); 273 | response(ctx, P); 274 | break; 275 | case SKYNET_SOCKET_TYPE_WARNING: 276 | skynet_error(ctx, "Overload on %d", P->fd); 277 | break; 278 | default: 279 | // ignore 280 | break; 281 | } 282 | } 283 | 284 | static void 285 | heartbeat(struct skynet_context *ctx, struct package *P) { 286 | if (P->recv == P->heartbeat) { 287 | if (!P->closed) { 288 | skynet_socket_shutdown(ctx, P->fd); 289 | skynet_error(ctx, "timeout %d", P->fd); 290 | } 291 | } else { 292 | P->heartbeat = P->recv = 0; 293 | skynet_command(ctx, "TIMEOUT", TIMEOUT); 294 | } 295 | } 296 | 297 | static void 298 | send_out(struct skynet_context *ctx, struct package *P, const void *msg, size_t sz) { 299 | if (sz > 0xffff) { 300 | skynet_error(ctx, "package too long (%08x)", (uint32_t)sz); 301 | return; 302 | } 303 | uint8_t *p = skynet_malloc(sz + 2); 304 | p[0] = (sz & 0xff00) >> 8; 305 | p[1] = sz & 0xff; 306 | memcpy(p+2, msg, sz); 307 | skynet_socket_send(ctx, P->fd, p, sz+2); 308 | } 309 | 310 | static int 311 | message_handler(struct skynet_context * ctx, void *ud, int type, int session, uint32_t source, const void * msg, size_t sz) { 312 | struct package *P = ud; 313 | switch (type) { 314 | case PTYPE_TEXT: 315 | command(ctx, P, session, source, msg, sz); 316 | break; 317 | case PTYPE_CLIENT: 318 | send_out(ctx, P, msg, sz); 319 | break; 320 | case PTYPE_RESPONSE: 321 | // It's timer 322 | heartbeat(ctx, P); 323 | break; 324 | case PTYPE_SOCKET: 325 | socket_message(ctx, P, msg); 326 | break; 327 | case PTYPE_ERROR: 328 | // ignore error 329 | break; 330 | default: 331 | if (session > 0) { 332 | // unsupport type, raise error 333 | skynet_send(ctx, 0, source, PTYPE_ERROR, session, NULL, 0); 334 | } 335 | break; 336 | } 337 | return 0; 338 | } 339 | 340 | struct package * 341 | package_create(void) { 342 | struct package * P = skynet_malloc(sizeof(*P)); 343 | memset(P, 0, sizeof(*P)); 344 | P->heartbeat = -1; 345 | P->uncomplete_sz = -1; 346 | queue_init(&P->request, sizeof(struct request)); 347 | queue_init(&P->response, sizeof(struct response)); 348 | return P; 349 | } 350 | 351 | void 352 | package_release(struct package *P) { 353 | queue_exit(&P->request); 354 | queue_exit(&P->response); 355 | skynet_free(P); 356 | } 357 | 358 | int 359 | package_init(struct package * P, struct skynet_context *ctx, const char * param) { 360 | int n = sscanf(param ? param : "", "%u %d", &P->manager, &P->fd); 361 | if (n != 2 || P->manager == 0 || P->fd == 0) { 362 | skynet_error(ctx, "Invalid param [%s]", param); 363 | return 1; 364 | } 365 | skynet_socket_start(ctx, P->fd); 366 | skynet_socket_nodelay(ctx, P->fd); 367 | heartbeat(ctx, P); 368 | skynet_callback(ctx, P, message_handler); 369 | 370 | return 0; 371 | } 372 | --------------------------------------------------------------------------------