skynet开发一个猜数字游戏

skynet开发一个猜数字游戏

  • 游戏简介
  • 接口设计和实现
    • agent服务接口
    • room服务接口
    • hall服务接口
    • redis服务
    • gate服务接口
  • 编写skynet的config文件
  • 游戏演示
  • 总结

游戏简介

猜数字游戏目的是掌握 actor 模型开发思路。

规则:

  1. 满三个人开始游戏,游戏开始后不能退出,直到游戏结束。
  2. 系统会随机生成1-100 之间的数字,玩家依次猜规则内的数字。
  3. 玩家猜测正确,那么该玩家就输了;如果猜测错误,游戏继续。
  4. 直到有玩家猜测成功,游戏结束。
server
TCP
TCP
TCP
hall
agent
gate
agent
agent
room
client
client
client

游戏设计时,首先是简单可用,然后持续优化,而不是一开始就过度优化。

接口设计和实现

skynet 中,从 actor 底层看是通过消息进行通信;从 actor 应用层看是通过 api 来进行通信。

遵循接口隔离原则:

  1. 不应该强迫客户依赖于他们不用的方法。
  2. 从安全封装的角度出发,只暴露客户需要的接口。
  3. 服务间不依赖彼此的实现。

agent服务接口

agent服务主要是用户。具有如下功能:

  1. login:实现登录功能;断线重连。
  2. ready:准备,转发到大厅,加入匹配队列。
  3. guess:猜测数字,转发到房间。
  4. help:列出所有操作说明。
  5. quit:退出。
local skynet = require "skynet"
local socket = require "skynet.socket"local tunpack = table.unpack
local tconcat = table.concat
local select = selectlocal clientfd, addr = ...
clientfd = tonumber(clientfd)local halllocal function read_table(result)local reply = {}for i = 1, #result, 2 do reply[result[i]] = result[i + 1] endreturn reply
end
-- 读取redis的相关信息
local rds = setmetatable({0}, {__index = function (t, k)if k == "hgetall" thent[k] = function (red, ...)return read_table(skynet.call(red[1], "lua", k, ...))endelset[k] = function (red, ...)return skynet.call(red[1], "lua", k, ...)endendreturn t[k]end
})local client = {fd = clientfd}
local CMD = {}local function client_quit()skynet.call(hall, "lua", "offline", client.name)if client.isgame and client.isgame > 0 thenskynet.call(client.isgame, "lua", "offline", client.name)endskynet.fork(skynet.exit)    --强制关闭进程,退出
end-- 发送信息
local function sendto(arg)-- local ret = tconcat({"fd:", clientfd, arg}, " ")-- socket.write(clientfd, ret .. "\n")socket.write(clientfd, arg .. "\r\n")
end-- 用户登录
function CMD.login(name, password)if not name and not password thensendto("没有设置用户名或者密码")client_quit()returnendlocal ok = rds:exists("role:"..name)if not ok thenlocal score = 1000-- 满足条件唤醒协程,不满足条件挂起协程rds:hmset("role:"..name, tunpack({"name", name,"password", password,"score", score,"isgame", 0,}))client.name = nameclient.password = passwordclient.score = scoreclient.isgame = 0client.agent = skynet.self()elselocal dbs = rds:hgetall("role:"..name)if dbs.password ~= password thensendto("密码错误,请重新输入密码")returnendclient = dbsclient.fd = clientfdclient.isgame = tonumber(client.isgame) or 0client.agent = skynet.self()endif client.isgame > 0 thenok = pcall(skynet.call, client.isgame, "lua", "online", client)if not ok thenclient.isgame = 0sendto("请准备开始游戏。。。")endelsesendto("请准备开始游戏。。。")end
endfunction CMD.ready()if not client.name thensendto("请先登陆")returnendif client.isgame and client.isgame > 0 thensendto("在游戏中,不能准备")returnendlocal ok, msg = skynet.call(hall, "lua", "ready", client)   --发起一个远程调用,调用hall服务的readyif not ok thensendto(msg)returnendclient.isgame = okrds:hset("role:"..client.name, "isgame", ok)
endfunction CMD.guess(number)if not client.name thensendto("错误:请先登陆")returnendif not client.isgame or client.isgame == 0 thensendto("错误:没有在游戏中,请先准备")returnendlocal numb = math.tointeger(number)if not numb thensendto("错误:猜测时需要提供一个整数而不是 "..number)returnendskynet.send(client.isgame, "lua", "guess", client.name, numb)
endlocal function game_over()client.isgame = 0rds:hset("role:"..client.name, "isgame", 0)
endfunction CMD.help()local params = tconcat({"*规则*:猜数字游戏,由系统随机1-100数字,猜中输,未猜中赢。","help: 显示所有可输入的命令;","login: 登陆,需要输入用户名和密码;","ready: 准备,加入游戏队列,满员自动开始游戏;","guess: 猜数字,只能猜1~100之间的数字;","quit: 退出",}, "\r\n")socket.write(clientfd, params .. "\r\n")
endfunction CMD.quit()client_quit()
end--处理数据接受
local function process_socket_events()while true dolocal data = socket.readline(clientfd)-- "\n" read = 0,telnet的分隔符是\nif not data thenprint("断开网络 "..clientfd)client_quit()returnend-- 开始解析数据包local pms = {}for pm in string.gmatch(data, "%w+") dopms[#pms+1] = pmendif not next(pms) thensendto("error[format], recv data")goto __continue__end-- 分发命令local cmd = pms[1]if not CMD[cmd] thensendto(cmd.." 该命令不存在")CMD.help()goto __continue__endskynet.fork(CMD[cmd], select(2, tunpack(pms)))
::__continue__::end
end
-- 开始agent服务
skynet.start(function ()print("recv a connection:", clientfd, addr)rds[1] = skynet.uniqueservice("redis") --进入redis服务hall = skynet.uniqueservice("hall")     -- 进入hall服务socket.start(clientfd) -- 绑定 clientfd agent 网络消息skynet.fork(process_socket_events)  --创建协程,处理数据接受skynet.dispatch("lua", function (_, _, cmd, ...)if cmd == "game_over" thengame_over()endend)
end)

room服务接口

room服务是一个游戏空间。具有如下功能:

  1. start:初始化房间。
  2. online:用户上线,如果用户在游戏中,告知游戏进度。
  3. offline:用户下线,通知房间内其他用户。
  4. guess:猜测数字,推动游戏进程。
local skynet = require "skynet"local socket = require "skynet.socket"local CMD = {}local roles = {}local redisdlocal game = {random_value = 0,user_turn = 0,up_limit = 100,down_limit = 1,turns = {},
}local function sendto(clientfd, arg)socket.write(clientfd, arg .. "\r\n")
endlocal function broadcast(msg)for _, role in pairs(roles) doif role.isonline > 0 thensendto(role.fd, msg)endend
endfunction CMD.start(members)for _, role in ipairs(members) dorole.isonline = 1roles[role.name] = rolegame.turns[#game.turns+1] = role.nameendgame.random_value = math.random(1, 100)broadcast(("房间:%d 系统已经随机一个数字"):format(skynet.self()))local rv = math.random(1, 1500)if rv <= 500 thengame.user_turn = 1elseif rv <= 1000 thengame.user_turn = 2elsegame.user_turn = 3endlocal name = game.turns[game.user_turn]broadcast(("请玩家%s开始猜数字"):format(name))
endfunction CMD.offline(name)if roles[name] thenroles[name].isonline = 0broadcast(("%s 玩家已经掉线,请求呼叫他上线"):format(name))endskynet.retpack()
endfunction CMD.online(client)local name = client.nameif roles[name] thenroles[name] = clientroles[name].isonline = 1broadcast(("%s 玩家已经上线"):format(name))sendto(client.fd, ("范围变为 [%d - %d], 接下来由 %s 来操作"):format(game.down_limit, game.up_limit, game.turns[game.user_turn]))endskynet.retpack()
endlocal function game_over()for _, role in pairs(roles) doif role.isonline == 0 thenskynet.call(redisd, "hset", "role:"..role.name, "isgame", 0)elseskynet.send(role.agent, "lua", "game_over")sendto(role.fd, "离开房间")endendskynet.fork(skynet.exit)
endfunction CMD.guess(name, val)local role = assert(roles[name])if game.turns[game.user_turn] ~= name thensendto(role.fd, ("错误:还没轮到你操作,现在由 %s 来操作"):format(game.turns[game.user_turn]))returnendif not val or val < game.down_limit or val > game.up_limit thensendto(role.fd, ("错误:请输入[%d - %d]之间的数字"):format(game.down_limit, game.up_limit))returnendgame.user_turn = game.user_turn % 3+1local next = game.turns[game.user_turn]if val == game.random_value thenbroadcast(("游戏结束,%s猜中了数字%d,输了"):format(name, val))game_over()returnendif val < game.random_value thengame.down_limit = val+1if game.down_limit == game.up_limit thenbroadcast(("游戏结束,只剩下一个数字%d %s输了"):format(val+1, next))game_over()returnendbroadcast(("%s输入的数字太小,范围变为 [%d - %d], 接下来由 %s 来操作"):format(name, game.down_limit, game.up_limit, next))returnendif val > game.random_value thengame.up_limit = val-1if game.down_limit == game.up_limit thenbroadcast(("游戏结束,只剩下一个数字%d %s输了"):format(val-1, next))game_over()returnendbroadcast(("%s输入的数字太大,范围变为 [%d - %d], 接下来由 %s 来操作"):format(name, game.down_limit, game.up_limit, next))returnend
endskynet.start(function ()math.randomseed(math.tointeger(skynet.time()*100), skynet.self())   --生成随机数redisd = skynet.uniqueservice("redis")  --进入redis服务skynet.dispatch("lua", function (_, _, cmd, ...)local func = CMD[cmd]if not func thenreturnendfunc(...)end)
end)

hall服务接口

hall服务类似《斗地主》的大厅。具有如下功能:

  1. ready:加入匹配队列。
  2. offline:用户掉线,需要从匹配队列移除用户。
local skynet = require "skynet"
local queue = require "skynet.queue"
local socket = require "skynet.socket"local cs = queue()local tinsert = table.insert
local tremove = table.remove
-- local tconcat = table.concat
local CMD = {}local queues = {}local resps = {}local function sendto(clientfd, arg)-- local ret = tconcat({"fd:", clientfd, arg}, " ")-- socket.write(clientfd, ret .. "\n")socket.write(clientfd, arg .. "\r\n")
endfunction CMD.ready(client)if not client or not client.name thenreturn skynet.retpack(false, "准备:非法操作")endif resps[client.name] thenreturn skynet.retpack(false, "重复准备")endtinsert(queues, 1, client)resps[client.name] = skynet.response()if #queues >= 3 thenlocal roomd = skynet.newservice("room") local members = {tremove(queues), tremove(queues), tremove(queues)}for i=1, 3 dolocal cli = members[i]resps[cli.name](true, roomd)resps[cli.name] = nilendskynet.send(roomd, "lua", "start", members)returnendsendto(client.fd, "等待其他玩家加入")
endfunction CMD.offline(name)for pos, client in ipairs(queues) doif client.name == name thentremove(queues, pos)breakendendif resps[name] thenresps[name](true, false, "退出")resps[name] = nilendskynet.retpack()
endskynet.start(function ()--  消息路由skynet.dispatch("lua", function(session, address, cmd, ...)local func = CMD[cmd]if not func thenskynet.retpack({ok = false, msg = "非法操作"})returnendcs(func, ...)   --开始执行end)
end)

redis服务

用于保存玩家名称、密码、分数、游戏状态等信息。
开启redis服务,redis的键值对、数据结构操作在agent服务进行。

local skynet = require "skynet.manager"
local redis = require "skynet.db.redis"
-- 连接redis
skynet.start(function ()local rds = redis.connect({host	= "127.0.0.1",port	= 6379,db		= 0,-- auth	= "123456",})skynet.dispatch("lua", function (session, address, cmd, ...)skynet.retpack( rds[cmd:lower()](rds, ...) )end)
end)

gate服务接口

gate服务是网关,主要处理网络连接,也是游戏的入口函数文件。具有如下功能:

  1. 绑定网络连接,推送信息到agent服务。
  2. 进入redis服务。

实现:
main.lua

local skynet = require "skynet" 
local socket = require "skynet.socket"local function accept(clientfd,addr)skynet.newservice("agent",clientfd,addr)--创建一个agent服务(lua虚拟机)
endskynet.start(function()-- bodylocal listenfd=socket.listen("0.0.0.0",8888)skynet.uniqueservice("redis")skynet.uniqueservice("hall")socket.start(listenfd,accept) --绑定listenfd到accept函数
end)

编写skynet的config文件

编写的game代码放在app目录下。

thread=4	--工作线程
logger=nil
harbor=0
start="main" -- 启动第一个服务
lua_path="./skynet/lualib/?.lua;".."./skynet/lualib/?/init.lua;".."./lualib/?.lua;"
luaservice="./skynet/service/?.lua;./app/?.lua"
lualoader="./skynet/lualib/loader.lua"
cpath="./skynet/cservice/?.so"
lua_cpath="./skynet/luaclib/?.so"

游戏演示

(1)服务端:
先启动 redis,然后启动 skynet。

redis-server redis.conf
./skynet/skynet config

(2)客户端:使用telnet。

telnet <IP> 8888

guess_game

总结

  1. 这些服务接口还可以进一步进行优化,比如 agent 服务可以不要实时创建而是采用预先创建、如果某个服务相对简单,可以创建固定数量。
  2. 如果是万人同时在线游戏,agent、room 需要预先分配,长时间运行会让服务内存膨胀,同时也会造成 luagc 负担会加重。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/63385.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字人全集

Mixlab 请查阅 Mixlab社群数字人讨论合辑&#xff0c;文末附有合辑资料汇总&#xff5e; 数字人专题分享合辑目录 #01 数字人驱动方式 离线式驱动实时驱动跨平台数字人形象统一接入工具 #02 数字人虚拟偶像制作 虚拟偶像创作与运营指南虚拟形象制作开发工具 #03 数字人实业应用…

哈登砍58分遇“里程悲” 火箭破单场3分出手纪录

资料图&#xff1a;哈登遭遇“里程悲” 资料图&#xff1a;哈登遭遇“里程悲” 中新网客户端1月17日 在17日进行的美职篮常规赛中&#xff0c;虽然哈登在45分钟的出场时间里拿到58分、6次助攻以及10个篮板的恐怖数据&#xff0c;但休斯敦火箭还是以142&#xff1a;145加时不敌…

连续21场30+ 哈登砍61分创个人生涯得分记录

资料图&#xff1a;哈登。 中新网1月24日电 北京时间24日上午&#xff0c;2018-2019赛季NBA常规赛继续进行。火箭客场以114&#xff1a;110力克尼克斯&#xff0c;此役哈登砍下61分、15个篮板和5次抢断&#xff0c;创造个人职业生涯得分记录。 上半场比赛&#xff0c;哈登21投…

NBA上周最佳球员:哈登场均54分 拉塞尔首次入选

哈登近期的表现惊艳全联盟。(资料图) 中新网1月22日电 北京时间22日凌晨&#xff0c;NBA官方公布了上一周东西部最佳球员&#xff0c;布鲁克林篮网队丹吉洛-拉塞尔与休斯顿火箭队詹姆斯-哈登分别当选。这是哈登赛季第3次当选周最佳。 拉塞尔上周率领篮网队取得3胜0负&#x…

火箭主帅德安东尼赞哈登低位防守:他是控球中锋

资料图&#xff1a;哈登。 中新网北京1月28日电 北京时间1月28日&#xff0c;火箭103&#xff1a;98战胜魔术。据NBA中文网报道&#xff0c;火箭主帅德安东尼在赛后夸赞了球队当家核心哈登的低位防守。 本场比赛&#xff0c;克里斯-保罗在缺席了17场比赛之后回归&#xff0c;…

哈登骑小牛刷街是营销炒作?你可能想多了

近日&#xff0c;哈登骑小牛电动车在上海刷街的视频和图片轰炸了微博、百度等各大平台&#xff0c;广大网友插科打诨之余不免猜测&#xff0c;这肯定是小牛电动的一次精心营销&#xff1f;虽然不排除这种可能&#xff0c;但我觉得&#xff0c;这更像是哈登自己的炒作。 哈登和…

接口怎么分批量同步数据_常规赛场均34分,季后赛场均31分,数据下滑的哈登到底怎么了...

詹姆斯哈登再一次因为季后赛中不理想的表现被球迷们推上了风口浪尖&#xff0c;其实哈登在这轮系列赛中的数据表现还是挺不错的&#xff0c;场均31.8分6.8个篮板和7.8次助攻的数据已经可以和历史上很多伟大名宿相提并论了&#xff0c;但是就算打出了这样的数据&#xff0c;球队…

姚明当年这罚球有多难?哈登欧文难复制,吉诺比利:罚不进真难

体育竞技&#xff0c;是力量和技巧的比拼&#xff0c;是心态和经验的比拼&#xff01;面对勇者&#xff0c;每个体育健儿都是雄鹰&#xff0c;他们能够勇猛的搏击长空&#xff0c;让啸声响彻云霄&#xff1b;面对智者&#xff0c;每个体育健儿都是蛟龙&#xff0c;能够畅游四海…

盘一盘 Python 系列 10 - Keras (上)

本文含 12119 字&#xff0c; 64 图表截屏 建议阅读 62 分钟 0 引言 本文是 Python 系列的第十三篇&#xff0c;也是深度学习框架的第一篇 - Keras。 深度学习之 Keras深度学习之 TensorFlow深度学习之 PyTorch深度学习之 MXnet Keras 是一个高级的 (high-level) 深度学习框架…

这届全明星,把NBA又燃回来了

第一个罚球&#xff0c;戴维斯出手后&#xff0c;听到哐当医生&#xff0c;皮球掉了出来。我又紧张了。微信群了很多人开始发消息&#xff0c;说詹姆斯队又要输了。回到比赛。戴维斯当时没有任何微笑&#xff0c;我估计他内心也是紧张的&#xff0c;他有点埋怨哈登&#xff0c;…

CTR---DIN原理,及deepctr组网实现DIN

文章目录 原理小结deepctr实现DIN&#xff08;基于df的数据格式&#xff09; 原理小结 Candidate Ad item&#xff0c;在这指广告特征。 User profile features 代表用户的特征。 Context Features 代表跟场景有关的特征&#xff0c;比如时间戳之类的。 User Behaviors 代表…

第02章 PyTorch基础知识

文章目录 第02章 Pytorch基础知识2.1 张量2.2 自动求导2.3 并行计算简介2.3.1 为什么要做并行计算2.3.2 CUDA是个啥2.3.3 做并行的方法 补充&#xff1a;通过股票数据感受张量概念。 本图文是Datawhale组队学习Pytorch的学习笔记&#xff0c;主要内容包括张量的概念&#xff08…

大胆预测NBA2011-2012季后赛形势

以下都是我个人的看法,纯属猜测。希望大家不喜勿喷,有相同喜好的爱好者也可以把自己的想法写在评论上,大家一起讨论哦哈哈 NBA季后赛第一轮正在如火如荼的进行当中,除了雷霆已经以4:0的大比分淘汰了卫冕冠军小牛队之外,其他14支球队的争夺也已经到了最后的阶段了,现在先…

通过KNN算法预测数据所属NBA球员——Python实现

项目介绍 通过得分&#xff0c;篮板&#xff0c;助攻&#xff0c;出场时间四个数据来预测属于哪位球员。 选取了LeBron James,Chris Paul,James Harden,Kevin Love,Dwight Howard五位球员单场数据。 数据来源 本文使用数据全部来自于科赛网 &#xff0c;字段解释如下&#xff1…

java的后撤建_后撤步难学?做好这几点,你的后撤步也能像哈登一样强!

原标题&#xff1a;后撤步难学&#xff1f;做好这几点&#xff0c;你的后撤步也能像哈登一样强&#xff01; 后撤步投篮的一些要点&#xff0c; 可得好好学学&#xff0c; 没有一招拿得出手的后撤步&#xff0c; 如何在球场上立足。 中国孔子说"性相近也&#xff0c;习相远…

利用Python从数据分析的角度告诉你NBA2018-2019常规赛季为什么字母哥比哈登强?

目录 基于NBA2018-2019赛季常规赛球员数据进行数据挖掘 1. 挖掘背景与目标 1.1 挖掘背景 1.2 挖掘目标 2. 分析方法与过程 2.1 分析方法&#xff08;主成分分析&#xff09; 2.1 分析过程 3. 获取数据 4. 数据探索性分析与预处理 4.1探索性分析 4.1.1 条形图分析 4…

AI篮球裁判火了,走步算得特别准,就问哈登慌不慌

Alex 发自 凹非寺量子位 | 公众号 QbitAI 打篮球的友友们应该知道&#xff0c;走步是比赛中最常见的违规之一。 为了更好地监测篮球比赛中球员是否出现走步行为&#xff0c;一位网名叫Ayush Pai的小哥&#xff08;我们就叫他AP哥吧&#xff09;搞出了一个AI裁判。 如你所见&…

预测2019-2020赛季常规赛MVP

受新冠肺炎影响&#xff0c;2019-2020赛季NBA已经处于停摆状态&#xff0c;是否以及何时能复赛还不清楚。相关的各项评选如常规赛MVP、最佳阵容、最佳防守等也由于疫情暂停了。按照往年的赛程节奏&#xff0c;此时也应该进入常规赛收官阶段了。本文利用历史数据和本赛季常规赛已…

今天nba预测分析_焰神体育【NBA】赛事推荐预测分析:1月15日《开拓者》vs《步行者》...

波特兰开拓者(主) VS 印第安纳步行者 比赛时间&#xff1a;2021 1月15日 11:00 印第安纳步行者队 周四的大新闻是詹姆斯哈登在连续几周表现不佳后终于如愿以获&#xff0c;被交易到布鲁克林篮网队。 印第安纳步行者队用奥拉迪波交换莱弗里特到火箭。 凯文-普理查德可以说是今天…

前端图片显示不出来

原来的代码是 <img src"Release/warn.png">给路径加上 / 就可以了 <img src"/Release/warn.png"> 然后就正常显示了